Label in UITableViewCell not changing the first time its loaded - objective-c

Ok so i have a UITableView that fetches some data from a web server and displays the data.
Depending on a text of a label the label will need to move a bit to the left if the if statement is true.
if ([gameInfoObject.GameTime isEqual: #"FT"] | ([gameInfoObject.GameTime rangeOfString:#":"].location != NSNotFound)) { // CHeck to see if its FT or string contains ":" then hide liveB
cell.liveButton.hidden = YES; //Hide the image cause gameTimeLable will take its position
CGRect frame = cell.gameTimeLabel.frame;
frame.origin.x= 27; // move the label to the left since no image will be present
cell.gameTimeLabel.frame= frame;
}
I have a timer that runs:
[self.tableview reloadData];
The problems is when the application starts the labels are not correctly positioned.
But after 5 seconds when the timer activate reloadData it changes to its accurate position.
How can i make it so the labels will change the first time?
Thnx..
----------------------------------------------------------------------------Edit 1
- (void) retrieveData{
GetData *getDataObject = [[GetData alloc] init]; // fixa detta sen
[getDataObject getAllGamesWithCompletionHandler:^(id responseObject, NSError *error) {
if (!responseObject) {
NSLog(#"failed: %#", error);
return;
}
gamesInfoArray = (NSMutableArray *)responseObject;
[self.tableView reloadData]; //first time it gets called here
}];
}

Related

UITableView memory issues

I'm adding UIView in some Cells of my UITableView. I use dequeueReusableCellWithIdentifier and it seems that when I scroll up and down and up and down, the views are never removed from memory and it keeps adding new views in front of the already loaded views. It increases the memory forever and that's not the way it should be.
How to remove these UIViews's from memory when not displayed ?
else if (indexPath.row == 0 && !isPremium) {
// Google AdMob
adCell = [tableView dequeueReusableCellWithIdentifier:adCellId];
if (adCell == nil) {
adCell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:adCellId];
}
AdMobView = [[GADBannerView alloc]initWithAdSize:kGADAdSizeSmartBannerPortrait];
AdMobView.adUnitID = #"ca-app-pub-XXXXXXXXXXXXXXX";
AdMobView.rootViewController = self;
AdMobView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
// load the ads
GADRequest *request = [GADRequest request];
request.testDevices = [NSArray arrayWithObjects:GAD_SIMULATOR_ID, #"XXXXXXXXX", nil]; // COMMENT THIS LINE WHEN IN PROD
// add gps
if ([latitude length] > 2 && [longitude length] > 2) {
[request setLocationWithLatitude:[latitude floatValue] longitude:[longitude floatValue] accuracy:50]; // accuracy [m]
}
adCell.backgroundColor = [UIColor colorWithRed:247.0/255.0 green:149.0/255.0 blue: 24.0/255.0 alpha:1];
if (!AdMobView.superview) {
[AdMobView loadRequest:request];
}
AdMobView.tag = 3000;
[adCell.contentView addSubview:AdMobView];
[adCell setSelectionStyle:UITableViewCellSelectionStyleNone];
return adCell;
}
If the cells are being reused but the views are still piling up, that would be because in your cellForRowAtIndexPath: you are adding the views even to recycled cells that already have that view. Thus the views keep increasing in number, piling up on top of each other in each cell, as you scroll up and down.
Thus (now that you've added some code) you are saying:
[adCell.contentView addSubview:AdMobView];
...for every cell, unconditionally. But if a cell is reused, it already has an AdMobView! You need to check for this so you add this subview only conditionally (and so too for any other subviews you add).

Scroll Table View with a cell that was selected with a long press

I have a table view that I would like to scroll when a selected a cell and "pop" it out and move it around the table view. The table view scrolls just fine and the long press to select a cell works, but I'm not able to scroll the table view when I have a cell selected.
I read up on UIGestureRecognizerDelegate but I'm not sure that is making a difference. I also call these method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Here is part of my code:
- (IBAction)longPressGestureRecognized:(id)sender {
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
UIGestureRecognizerState state = longPress.state;
//longPress.delegate = self;
CGPoint location = [longPress locationInView:self.personTableView];
NSIndexPath *indexPath = [self.personTableView indexPathForRowAtPoint:location];
static UIView *snapshot = nil; ///< A snapshot of the row user is moving.
static NSIndexPath *sourceIndexPath = nil; ///< Initial index path, where gesture begins.
switch (state) {
case UIGestureRecognizerStateBegan: {
if (indexPath) {
sourceIndexPath = indexPath;
UITableViewCell *cell = [self.personTableView cellForRowAtIndexPath:indexPath];
// capture the color of the cell before blacking out
savedTextColor = cell.detailTextLabel.textColor;
// Take a snapshot of the selected row using helper method.
snapshot = [self customSnapshotFromView:cell];
// Add the snapshot as subview, centered at cell's center...
__block CGPoint center = cell.center;
snapshot.center = center;
snapshot.alpha = 0.0;
[self.personTableView addSubview:snapshot];
[UIView animateWithDuration:0.25 animations:^{
// Offset for gesture location.
center.y = location.y;
snapshot.center = center;
snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05);
snapshot.alpha = 0.98;
// Black out.
cell.detailTextLabel.alpha = 0;
cell.textLabel.alpha = 0;
} completion:nil];
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint center = snapshot.center;
center.y = location.y;
snapshot.center = center;
// Is destination valid and is it different from source?
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
// ... update data source.
[dataSingelton saveUpdatedPersonList:(int)indexPath.row sourceIndex:(int)sourceIndexPath.row];
//[[dataSingelton mutableDataArray] exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];
// ... move the rows.
[self.personTableView moveRowAtIndexPath:sourceIndexPath toIndexPath:indexPath];
// ... and update source so it is in sync with UI changes.
sourceIndexPath = indexPath;
}
break;
}
default: {
// Clean up.
UITableViewCell *cell = [self.personTableView cellForRowAtIndexPath:sourceIndexPath];
[UIView animateWithDuration:0.25 animations:^{
snapshot.center = cell.center;
snapshot.transform = CGAffineTransformIdentity;
snapshot.alpha = 0.1;
cell.detailTextLabel.alpha = 1;
cell.textLabel.alpha = 1;
} completion:^(BOOL finished) {
[snapshot removeFromSuperview];
snapshot = nil;
cell.hidden = NO;
}];
sourceIndexPath = nil;
break;
}
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (UIView *)customSnapshotFromView:(UIView *)inputView {
UIView *snapshot = [inputView snapshotViewAfterScreenUpdates:YES];
snapshot.alpha = 0.1;
snapshot.layer.masksToBounds = NO;
snapshot.layer.cornerRadius = 0.0;
snapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);
snapshot.layer.shadowRadius = 5.0;
snapshot.layer.shadowOpacity = 0.4;
return snapshot;
}
When I set the delegate to self like this:
longPress.delegate = self;
The able view scrolls, but then my selected cell doesn't move. What am I missing to make this work?
* Updated *
After doing some more reading/research I think in order for me to achieve the effect I want (the scrolling effect on the TableView in Apple's weather app) I think that my long press gesture will also have to handle the scrolling of my table view. The default scrolling starts to scroll the entire table instantly, where as I need the table to scroll only when the long pressed cell is at the top or the bottom of my TableView. Am I correct in thinking like this? I've been trying to find an example to break down and study, but haven't had any luck finding anything like that.
** Updated **
So I'm trying to figure out how to trigger the scroll up timer when the user drags a cell up towards the top. The problem I'm having right now is figuring out when that cell is close to the top, when its at the very top of the tableview, thats easy I could use something like tableViewHight + 100, but this code wont work when the tableview is scrolled down. Here is what I have so far:
NSLog(#"This is table view hight %.2f", tableViewHight);
NSLog(#"This is content offset %f", (personTableView.contentOffset.y));
NSLog(#"Should be 100 place holder %.2f", (tableViewHight - tableViewHight) + personTableView.contentOffset.y + 100);
NSLog(#"location y %f", location.y);
// gets called repeatedly
// Step 1: check to see if we need to start scrolling
if (location.y < tableViewHight - tableViewHight + 100) {
// ** top **
// Step 2: make sure the current y postoin isn't greater then the previous y postion
if (location. y <= originalLocation.y) {
// NSLog(#"This is the offset y %f", self.personTableView.contentOffset.y);
// Step 4: check to see if we have a timer and if not create one
[self startTimer];
}
else{
//Step 3: they started to scroll up so end the timer
[self endTimer];
}
}
else if(location.y > tableViewHight - 100)
{
// ** bottom **
// Step 2: make sure the current y postoin isn't less then the previous y postion
if (location. y >= originalLocation.y) {
NSLog(#"its less then 0");
if (!timer) {
// Step 4: check to see if we have a timer and if not create one
[self startTimer];
}
}
else{
//Step 3: they started to scroll up so end the timer
[self endTimer];
}
}
else{
// ** middle **
// Step 2: check to see if we have a timer and if so destory it
[self endTimer];
}
Basically I want to trigger that if statement when the cell is less then 100px from the hight of the scrollable table view.
From the UILongPressGestureRecognizer you can get the current y position of the touch. From that you can calculate how close to the bottom or top of the UITableViewthe touch is. If you determine that it's close enough to begin scrolling, then start a repeating NSTimer that increments or decrements the contentOffset of your UITableView by 1 pixel, scrolling your table. Change how fast it scrolls by changing the timeInterval of the timer.
When the UILongPressGestureRecognizer ends, or if the y position of the touch moves outside of the scrolling zone, then invalidate your timer, which will stop the scrolling.
If you felt adventurous you could also increase the speed of the scrolling dynamically as the user gets closer to the edge of the UITableView by changing the timeInterval as the user moves their touch up or down, as that is generally how this type of functionality works.
Good luck!

Disabling NSView fade animation for NSView `setHidden:`

I am working on a project that has the concept of draggable controls, everything is working fine except that NSView seems to employ a fade in/out animation when calling setHidden:.
I have been able to work around the problem by changing the line session.animatesToStartingPositionsOnCancelOrFail = YES; to NO and implementing the image snapback myself with a custom animated NSWindow subclass. it looks great, but I know there must be an easier way.
I have tried:
using NSAnimationContext grouping with duration of 0 around the setHidden: calls
setting the view animations dictionary using various keys (alpha, hidden, isHidden) on the control and superview
overriding animationForKey: for both the control and its superview
I am not using CALayers and have even tried explicitly setting wantsLayer: to NO.
Does anybody know how to either disable this animation, or have a simpler solution then my animated NSWindow?
here is my stripped down altered code with the bare minimum to see what I'm talking about.
#implementation NSControl (DragControl)
- (NSDraggingSession*)beginDraggingSessionWithDraggingCell:(NSActionCell <NSDraggingSource> *)cell event:(NSEvent*) theEvent
{
NSImage* image = [self imageForCell:cell];
NSDraggingItem* di = [[NSDraggingItem alloc] initWithPasteboardWriter:image];
NSRect dragFrame = [self frameForCell:cell];
dragFrame.size = image.size;
[di setDraggingFrame:dragFrame contents:image];
NSArray* items = [NSArray arrayWithObject:di];
[self setHidden:YES];
return [self beginDraggingSessionWithItems:items event:theEvent source:cell];
}
- (NSRect)frameForCell:(NSCell*)cell
{
// override in multi-cell cubclasses!
return self.bounds;
}
- (NSImage*)imageForCell:(NSCell*)cell
{
return [self imageForCell:cell highlighted:[cell isHighlighted]];
}
- (NSImage*)imageForCell:(NSCell*)cell highlighted:(BOOL) highlight
{
// override in multicell cubclasses to just get an image of the dragged cell.
// for any single cell control we can just make sure that cell is the controls cell
if (cell == self.cell || cell == nil) { // nil signifies entire control
// basically a bitmap of the control
// NOTE: the cell is irrelevant when dealing with a single cell control
BOOL isHighlighted = [cell isHighlighted];
[cell setHighlighted:highlight];
NSRect cellFrame = [self frameForCell:cell];
// We COULD just draw the cell, to an NSImage, but button cells draw their content
// in a special way that would complicate that implementation (ex text alignment).
// subclasses that have multiple cells may wish to override this to only draw the cell
NSBitmapImageRep* rep = [self bitmapImageRepForCachingDisplayInRect:cellFrame];
NSImage* image = [[NSImage alloc] initWithSize:rep.size];
[self cacheDisplayInRect:cellFrame toBitmapImageRep:rep];
[image addRepresentation:rep];
// reset the original cell state
[cell setHighlighted:isHighlighted];
return image;
}
// cell doesnt belong to this control!
return nil;
}
#pragma mark NSDraggingDestination
- (void)draggingEnded:(id < NSDraggingInfo >)sender
{
[self setHidden:NO];
}
#end
#implementation NSActionCell (DragCell)
- (void)setControlView:(NSView *)view
{
// this is a bit of a hack, but the easiest way to make the control dragging work.
// force the control to accept image drags.
// the control will forward us the drag destination events via our DragControl category
[view registerForDraggedTypes:[NSImage imagePasteboardTypes]];
[super setControlView:view];
}
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp
{
BOOL result = NO;
NSPoint currentPoint = theEvent.locationInWindow;
BOOL done = NO;
BOOL trackContinously = [self startTrackingAt:currentPoint inView:controlView];
BOOL mouseIsUp = NO;
NSEvent *event = nil;
while (!done)
{
NSPoint lastPoint = currentPoint;
event = [NSApp nextEventMatchingMask:(NSLeftMouseUpMask|NSLeftMouseDraggedMask)
untilDate:[NSDate distantFuture]
inMode:NSEventTrackingRunLoopMode
dequeue:YES];
if (event)
{
currentPoint = event.locationInWindow;
// Send continueTracking.../stopTracking...
if (trackContinously)
{
if (![self continueTracking:lastPoint
at:currentPoint
inView:controlView])
{
done = YES;
[self stopTracking:lastPoint
at:currentPoint
inView:controlView
mouseIsUp:mouseIsUp];
}
if (self.isContinuous)
{
[NSApp sendAction:self.action
to:self.target
from:controlView];
}
}
mouseIsUp = (event.type == NSLeftMouseUp);
done = done || mouseIsUp;
if (untilMouseUp)
{
result = mouseIsUp;
} else {
// Check if the mouse left our cell rect
result = NSPointInRect([controlView
convertPoint:currentPoint
fromView:nil], cellFrame);
if (!result)
done = YES;
}
if (done && result && ![self isContinuous])
[NSApp sendAction:self.action
to:self.target
from:controlView];
else {
done = YES;
result = YES;
// this bit-o-magic executes on either a drag event or immidiately following timer expiration
// this initiates the control drag event using NSDragging protocols
NSControl* cv = (NSControl*)self.controlView;
NSDraggingSession* session = [cv beginDraggingSessionWithDraggingCell:self
event:theEvent];
// Note that you will get an ugly flash effect when the image returns if this is set to yes
// you can work around it by setting NO and faking the release by animating an NSWindowSubclass with the image as the content
// create the window in the drag ended method for NSDragOperationNone
// there is [probably a better and easier way around this behavior by playing with view animation properties.
session.animatesToStartingPositionsOnCancelOrFail = YES;
}
}
}
return result;
}
#pragma mark - NSDraggingSource Methods
- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
switch(context) {
case NSDraggingContextOutsideApplication:
return NSDragOperationNone;
break;
case NSDraggingContextWithinApplication:
default:
return NSDragOperationPrivate;
break;
}
}
- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
// now tell the control view the drag ended so it can do any cleanup it needs
// this is somewhat hackish
[self.controlView draggingEnded:nil];
}
#end
There must be a layer enabled somewhere in your view hierarchy, otherwise there wouldn't be a fade animation. Here is my way of disabling such animations:
#interface NoAnimationImageView : NSImageView
#end
#implementation NoAnimationImageView
+ (id)defaultAnimationForKey: (NSString *)key
{
return nil;
}
#end
The solution you already tried by setting the view animations dictionary should work. But not for the keys you mention but for the following. Use it somewhere before the animation is triggered the first time. If you have to do it on the window or view or both, I don't know.
NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[[theViewOrTheWindow animator] animations];
[animations setObject:[NSNull null] forKey: NSAnimationTriggerOrderIn];
[animations setObject:[NSNull null] forKey: NSAnimationTriggerOrderOut];
[[theViewOrTheWindow animator] setAnimations:animations];
Or also just remove the keys if they are there (might not be the case as they are implicit / default):
NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[[theViewOrTheWindow animator] animations];
[animations removeObjectForKey:NSAnimationTriggerOrderIn];
[animations removeObjectForKey:NSAnimationTriggerOrderOut];
[[theViewOrTheWindow animator] setAnimations:animations];
Ok. I figured out that the animation I'm seeing is not the control, the superview, nor the control's window. It appears that animatesToStartingPositionsOnCancelOrFail causes NSDraggingSession to create a window (observed with QuartzDebug) and put the drag image in it and it is this window that animates back to the origin and fades out before the setHidden: call is executed (i.e. before the drag operation is concluded).
Unfortunately, the window that it creates is not an NSWindow so creating a category on NSWindow doesn't disable the fade animation.
Secondly, there is no public way that I know of to get a handle on the window, so I can't attempt directly manipulating the window instance.
It looks like maybe my workaround is the best way to do this, after all its not far from what AppKit does for you anyway.
If anybody knows how to get a handle on this window, or what class it is I would be interested to know.

Image generator ( generateCGImagesAsynchronouslyForTimes method) doesn't give live results

Pretty new to cocoa development and really stuck with probably a fundamental problem.
So in short, my app UI looks like a simple window with a nsslider at the bottom. What I need is to generate N images and place them, onto N nsviews in my app window.
What it does so far:
I'm clicking on the slider (holding it) and dragging it. While I'm dragging it nothing happens to my views (pictures are not generated). When I release the slider the pictures got generated and my view get filled with them.
What I want:
- I need the views to be filled with pictures as I'm moving the slider.
I figured out the little check box on the NSSlider properties, which is continuous, and I'm using it, but my image generator still doesn't do anything until I release the slider.
Here is my code:
// slider move action
- (IBAction)sliderMove:(id)sender
{
[self generateProcess:[_slider floatValue];
}
// generation process
- (void) generateProcess:(Float64) startPoint
{
// create an array of times for frames to display
NSMutableArray *stops = [[NSMutableArray alloc] init];
for (int j = 0; j < _numOfFramesToDisplay; j++)
{
CMTime time = CMTimeMakeWithSeconds(startPoint, 60000);
[stops addObject:[NSValue valueWithCMTime:time]];
_currentPosition = initialTime; // set the current position to the last frame displayed
startPoint+=0.04; // the step between frames is 0.04sec
}
__block CMTime lastTime = CMTimeMake(-1, 1);
__block int count = 0;
[_imageGenerator generateCGImagesAsynchronouslyForTimes:stops
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime,AVAssetImageGeneratorResult result, NSError *error)
{
if (result == AVAssetImageGeneratorSucceeded)
{
if (CMTimeCompare(actualTime, lastTime) != 0)
{
NSLog(#"new frame found");
lastTime = actualTime;
}
else
{
NSLog(#"skipping");
return;
}
// place the image onto the view
NSRect rect = CGRectMake((count+0.5) * 110, 500, 100, 100);
NSImageView *iView = [[NSImageView alloc] initWithFrame:rect];
[iView setImageScaling:NSScaleToFit];
NSImage *myImage = [[NSImage alloc] initWithCGImage:image size:(NSSize){50.0,50.0}];
[iView setImage:myImage];
[self.windowForSheet.contentView addSubview: iView];
[_viewsToRemove addObject:iView];
}
if (result == AVAssetImageGeneratorFailed)
{
NSLog(#"Failed with error: %#", [error localizedDescription]);
}
if (result == AVAssetImageGeneratorCancelled)
{
NSLog(#"Canceled");
}
count++;
}];
}
}
If you have any thoughts or ideas, please share with me, I will really appreciate it!
Thank you
In order to make your NSSlider continuous, open your window controller's XIB file in Interface Builder and click on the NSSlider. Then, open the Utilities area
select the Attributes Inspector
and check the "Continuous" checkbox
under the Control header. Once you've done this, your IBAction sliderMove: will be called as the slider is moved rather than once the mouse is released.
Note: Alternatively, with an
NSSlider *slider = //...
one can simply call
[slider setContinuous:YES];

forcing a button to swap the images

I'm working on a matching program for iPad and when a user selects a button an image is "uncovered" and then when the user selects a second button,another image is uncovered. I then programmatically check for a match and if not, revert both button images back to their initial state.
This is working fine except when a match is NOT made, the switch happens so fast that you do not have time to see what you have "uncovered". I tried to make it sleep but the image doesn't ever swap to the uncovered state... Thoughts?
The code for this is as follows:
//Take action on the tap of one of the buttons
if(isFirstSelection)
{
firstSelection = [(UIButton *)sender tag];
tempImageItem = [tileArray objectAtIndex:firstSelection];
tempImage = [tempImageItem tileImage];
firstSelectionName = [[NSString alloc] initWithString:[tempImageItem tileName]];
[(UIButton *)sender setImage:tempImage forState:UIControlStateNormal];
tempButton = sender;
isFirstSelection = NO;
}else{
secondSelection = [(UIButton *)sender tag];
tempImageItem = [tileArray objectAtIndex:secondSelection];
tempImage = [tempImageItem tileImage];
secondSelectionName = [[NSString alloc] initWithString:[tempImageItem tileName]];
[(UIButton *)sender setImage:tempImage forState:UIControlStateNormal];
//Two game pieces have been removed so check to see if they are a match
if([firstSelectionName isEqualToString:secondSelectionName])
{
//Match found
//do something
}else{
**//NO MATCH FOUND
[NSThread sleepForTimeInterval:3];
//Display the checker board pieces again
[(UIButton *)sender setImage:[UIImage imageNamed:#"originalImage"] forState:UIControlStateNormal];**
}
//Reset isFirstSelection Flag to YES for next selection
isFirstSelection = YES;
}
From what i understand you want to put the button's original image after 3 seconds so the user has the time to see what happened.
You should look at the NSTimer class to trigger that code
[(UIButton *)sender setImage:[UIImage imageNamed:#"originalImage"] forState:UIControlStateNormal];
in 3 seconds from now.
For "run this code in X seconds" I prefer:
[self performSelector:#selector(spinWheel:) withObject:[NSNumber numberWithUnsignedInt:0] afterDelay:delay];
no need to muck around with timer objects.