UIAnimation block crashes on conditional clause - animation

this code crashes with EXC_BAD_ACCESS (please forgive me the formatting, I seem to be unable to handle this web editor):
#implementation
BOOL imageZoomed=NO;
-(void)makeAnimation {
[UIView animateWithDuration:1.0f
animations:^{
self.myImageView.alpha=1.0f;
if (imageZoomed) {
self.zoomImageView.alpha=0.0f;
tempZoomImageView.alpha=1.0f;
}
}
completion:^(BOOL finished) {
if (imageZoomed) {
self.zoomImageView.alpha=1.0f;
[tempZoomImageView removeFromSuperview];
}
}
}
If I comment out the if-block in animations:, it works.
imageZoomed is called before and after the animation without problems.
Am I missing something with blocks and conditional clauses, or blocks and variables?
Thanks for any reply, marimba

On the surface, I don't see anything wrong with this code per se. There's nothing special WRT blocks and conditional clauses. There ARE special rules WRT variables, but looking at this code, you should probably be fine.
You probably have an object that's being over released or something along those lines. I suggest taking a close look at self, zoomImageView, and tempZoomImageView as suspects, since they're in the if block... Try enabling NSZombiesEnabled to get an exception at the point your over released object is messaged.

[UIView animateWithDuration:1.0f
animations:^{
}
completion:^(BOOL finished) {
if (finished) {
}
}];
don't forget the " ]; "

Related

PFUser currentUser nil after app restart on OS X

I've downloaded the latest Parse SDK for OS X, and I'm trying to retain my login after app restarts (obviously). I've used Parse before and I haven't faced this problem, neither on iOS nor OS X.
On my app start:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[Parse setApplicationId:#"XXX" clientKey:#"XXX"];
}
In my first view controller:
-(void)viewDidAppear{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if(![PFUser currentUser]){
[self performSegueWithIdentifier:#"login" sender:nil];
}else{
...
}
});
}
My login succeeds, and at that point [PFUser currentUser] is valid. Then, I close the app (tried both killing and gracefully quitting). When I open it again, [PFUser currentUser] is nil. I've tried this many times, it yields the same results. Why?
After struggling for a long while, I've found the solution. I need to dispatch_async the user checking block and it starts working. So instead of:
dispatch_once(&onceToken, ^{
if(![PFUser currentUser]){
[self performSegueWithIdentifier:#"login" sender:nil];
}else{
...
}
});
I did:
dispatch_once(&onceToken, ^{
dispatch_async(dispatch_get_main_queue(), ^{
if(![PFUser currentUser]){
[self performSegueWithIdentifier:#"login" sender:nil];
}else{
...
}
});
});
And it started working. Interesting to see that something is still not initialized on viewDidAppear synchronously on main queue (yes, it IS the main queue, double checked that), but is initialized somewhere after posting to the same queue asynchronously. Parse definitely needs more quality control of their SDK.

UITableView becomes unresponsive

UITableViewCell becomes unresponsive this was a very different problem with a very different solution.
My tableView which is a subView in a UIViewController initially works fine and I can select individual rows in the table. However, I have created my own popup when a row is selected (the popup is a UIView) that appears towards the bottom of the screen. As this pops-up I also create a another UIView which covers the screen behind the popup and it makes the background go dim. The third thing that happens is that i create a UITapGestureRecogniser to keep track of the user's taps, and if they tap outside the UIView then the two UIViews and the TapGestureRecogniser are removed and call the deselectRowAtIndex... method.
However, it is at this point that I cannot use the tableView, as i want to be able to select a different string within the tableView and the popup to appear again (the popup will eventually contain links that will enable the user to move to different viewControllers).
I have tried to reload the data, remove the tableview and replace it, edit the didSelectRowAtIndex, remove the deselectRowAtIndex method, however nothing I tried seems to work and i can't find anything on stackoverflow as my question seems to be quite specific (although I apologise if there is something out there).
I'll add a few parts of my code in, however, I'm not sure where the problem is and I may not have copied the right part in.
The remove overhead is the selector method from the tapGesture
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(_popOverView == nil)
{
_popOverView = [[UIView alloc]initWithFrame:CGRectMake(20, 200, 280, 150)];
_popOverView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.jpeg"]];
}
if(_mask == nil)
{
_mask = [[UIView alloc] initWithFrame:self.view.frame];
[_mask setBackgroundColor:[UIColor colorWithWhite:0.0 alpha:0.78]];
}
if (_tapDetector == nil)
{
_tapDetector= [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(removeOverHead:)];
}
[self.view addSubview:_mask];
[self.view addSubview:_popOverView];
[self.view addGestureRecognizer:_tapDetector];
}
-(void) removeOverHead:(UITapGestureRecognizer*) sender
{
CGPoint locationOfTap = [_tapDetector locationInView:self.view];
if (locationOfTap.y < (200 + 150) && locationOfTap.y > 200 && locationOfTap.x > 20 && locationOfTap.x < (20 + 280) ) {
NSLog(#"%f,%f",[_tapDetector locationInView:self.view].x,[_tapDetector locationInView:self.view].y);
}
else
{
[_mask removeFromSuperview];
[_popOverView removeFromSuperview];
[_tapDetector removeTarget:self action:#selector(removeOverHead:)];
/*this idea doesn't work :(
[self.tableView removeFromSuperview];
[self.view addSubview:_tableView];*/
}
}
I really hope the answer is in here and is very simple, and thank you in advance for taking the time to read this.
Solved it! Sorry for wasting your time. It was the wrong remove method for the gestureRecogniser. I replaced
[_tapDetector removeTarget:self action:#selector(removeOverHead:)]
with
[self.view removeGestureRecognizer:_tapDetector]
as the UIGestureRecogniser was lingering and obstructing the tableView!!
If you stick a breakpoint or NSLog() inside the else block of that remove method, do you get inside it?
It sounds like your if statement might be off. You should use CGRectContainsPoint(). However if I understand correctly, you're attempting to dismiss everything when the user taps the dimming background view. You could make this view a button or you could compare the touch's view pointer to the pointer to the background view.

Why does dispatch_semaphore_wait() return YES all the time even when I'm not scrolling?

Brad Larson delivered a solution for the CADisplayLink freeze issue when scroll views are scrolling.
My OpenGL ES draw method is called by a CADisplayLink, and I tried Brad's technique but can't make it work. The core problem is that my OpenGL ES view is hosted by a UIScrollView, and when the UIScrollView scrolls, the CADisplayLink stops firing.
The technique Brad described is supposed to let the CADisplayLink continue to fire even during scrolling (by adding it to NSRunLoopCommonModes instead of the default runloop mode), and using a fancy semaphore trick the rendering callback is supposed to ensure that it doesn't render when UIKit is too occupied.
The problem is though that the semaphore trick prevents the rendering callback from drawing, no matter what.
First, I create the serial GCD queue and semaphore in the initWithFrame method of my OpenGL ES view like this (on the main thread):
frameRenderingQueue = dispatch_queue_create("com.mycompany.crw", DISPATCH_QUEUE_SERIAL);
frameRenderingSemaphore = dispatch_semaphore_create(1);
The display link is created and added to NSRunLoopCommonModes:
CADisplayLink *dl = [[UIScreen mainScreen] displayLinkWithTarget:self selector:#selector(renderFrame)];
[dl addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
The render callback performs Brad's technique:
- (void)renderFrame {
if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0) {
NSLog(#"return"); // Gets called ALWAYS!
return;
}
dispatch_async(drawingQueue, ^{
#autoreleasepool {
// OpenGL ES drawing code
dispatch_semaphore_signal(frameRenderingSemaphore);
}
});
}
The dispatch_semaphore_wait function always returns YES and thus the render callback never renders. Even when I'm not scrolling.
I suppose that I missed something important here. Can someone point it out?
Edit: It seems to work only when I call dispatch_sync instead of dispatch_async, but according to Brad dispatch_async would give better performance here.
I had to change the structure of the code to this:
- (void)renderFrame {
dispatch_async(drawingQueue, ^{
if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0) {
return;
}
#autoreleasepool {
// Drawing code...
}
dispatch_semaphore_signal(frameRenderingSemaphore);
});
}
After I restructured it this way, the dispatch_semaphore_wait call stopped returning YES all the time. I am not sure if this is effectively just disabling Brad's semaphore wait trick or not. But it works.

UIPopoverViewController Adding Subviews is slow

I have a UIPopoverViewController that is displaying a custom UIViewController properly. When I click a button I have an action run and as a result I add a view to the view hierarchy of the UIViewController's view.
The problem is that it is very slow, and takes several seconds for the view to appear. I'm not doing anything out of the ordinary with my UIViewController's code.
- (void)showAccountChooser {
self.twitterAccountPicker = [TwitterAccountPicker new];
[self.view addSubview:self.twitterAccountPicker.view];
self.twitterAccountPicker.view.frame = self.view.bounds;
self.twitterAccountPicker.view.transform = CGAffineTransformMakeScale(.05, .05);
[UIView animateWithDuration:0.5f animations:^{
self.twitterAccountPicker.view.transform = CGAffineTransformMakeScale(1, 1);
} completion:^(BOOL finished) {
//[self.twitterAccountPicker viewDidAppear:YES];
}];
}
The UIViewController that I'm adding is trivial and does not heavy processing in the viewDidLoad or viewWill/DidAppear. I have set break points and verified that it is not doing anything bad.
Anyone else notice this when adding views?
After setting break points trying to debug this, I realized that my showAccountChooser method was being called from a block invoke, which was happening on a background thread. Moving this call to the main thread resolved the issue.

iOS: popViewController unexpected behavior

I've been searching the internet for a solution. There's nothing I could find.
So:
I'm using a UINavigationController. I am pushing two UIViewControllers onto it. In the second pushed ViewController i am executing this code:
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error {
NSLog([error localizedDescription]);
[self.navigationController popViewControllerAnimated:YES]; }
The expected thing to happen would be that the last pushed ViewController disappears. In this app I am doing this on few places and it works fine everywhere expect in this very ViewController.
What happens is that only the back button goes off screen (animated) but everything else stays on screen. In the Console Output two things are printed out when this line executes:
2011-03-14 16:32:44.580
TheAppXY[18518:207] nested pop
animation can result in corrupted
navigation bar
2011-03-14 16:32:53.507
TheAppXY[18518:207] Finishing up a
navigation transition in an unexpected
state. Navigation Bar subview tree
might get corrupted.
Two error messages I couldn't find ANY information on.
I'm using XCode 4 and iOS SDK 4.3. Maybe anyone can help me with this problem.
I came across a similar situation in my code and the message said:
nested push animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree >might get corrupted.
My finding to this issue was that I was pushing 2 view controllers one after the other in quick succession and both were animated.
In your case it seems that you might be popping multiple view controllers with animation one after the other.
Hence, while one view is undergoing animation you should not start animation on another view.
I also found that if I disabled animation on one view, the error message disappeared.
In my case it was a problem with the flow logic as I did not intend to push 2 view controllers one after the other. One was being pushed within the switch case logic and another after its end.
Hope this helps someone.
You can get this anytime that you try to pop before viewDidAppear. If you set a flag, then just check that flag in viewDidAppear, you wont have a problem.
I have created a drop-in replacement for UINavigationController that will queue animations for you and avoid this problem entirely.
Grab it from BufferedNavigationController
I had this problem, too, and here's what was causing mine:
In RootViewController, I am using several UISegmentedControl objects to determine which of many views to load next.
In that (sub/2nd) view, I was popping (by using the "Back" button) back to RootViewController.
In RootViewController, I was handling viewWillAppear to "reset" each of my UISegmentedControl objects to a selectedSegmentIndex of -1 (meaning no segment looks "pressed").
That "reset" triggered each of my UISegmentedControl objects to fire their associated (and separate) IBActions.
Since I wasn't handling for a "selection" of -1, I had multiple methods firing at the same time, all trying to push a different view.
My fix? I tightened up my if...then statements and bailed on executing any code in my UISegmentedControl IBActions when selectedSegmentIndex == -1.
I'm still not sure why I got "pop" animation errors and not "push" errors, but at least figured out my error and got it fixed!
Hope this helps someone else!
yeah, unfortunately apple did not synchronize UINavigationController's animations. Andrew's solution is excellent, but if you don't want to cover its whole functionality, there is a simpler solution, override these two methods :
// navigation end event
- ( void ) navigationController : ( UINavigationController* ) pNavigationController
didShowViewController : ( UIViewController* ) pController
animated : ( BOOL ) pAnimated
{
if ( [ waitingList count ] > 0 ) [ waitingList removeObjectAtIndex : 0 ];
if ( [ waitingList count ] > 0 ) [ super pushViewController : [ waitingList objectAtIndex : 0 ] animated : YES ];
}
- ( void ) pushViewController : ( UIViewController* ) pController
animated : ( BOOL ) pAnimated
{
[ waitingList addObject : pController ];
if ( [ waitingList count ] == 1 ) [ super pushViewController : [ waitingList objectAtIndex : 0 ] animated : YES ];
}
and create an NSMutableArray instance variable called waitingList, and you are done.
This problem happen with me when i use storyboards. I've made a mistake:
I have a UIButton with an action to performSegueWithIdentifier. So i link the push segue with Button with the other ViewController so occur this problem.
To solve:
Link the button action in UIButton and link the push segue between two ViewControllers.
Combining MilGra and Andrew's answers gave me something that works reliably and is a simpler drop-in UINavigationController replacement.
This improves on MilGra's answer to make it work with pushes and pops, but is simpler than Andrew's BufferedNavigationController. (Using BufferedNavigationController I was occasionally getting transitions that would never complete and would only show a black screen.)
This whole thing seems not to be necessary on iOS8, but was still needed for me on iOS7.
#implementation UINavigationControllerWithQueue {
NSMutableArray *waitingList;
}
-(void) viewDidLoad {
[super viewDidLoad];
self.delegate = self; // NOTE: delegate must be self!
waitingList = [[NSMutableArray alloc] init];
}
# pragma mark - Overrides
-(void) pushViewController: (UIViewController*) controller
animated: (BOOL) animated {
[self queueTransition:^{ [super pushViewController:controller animated:animated]; }];
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
UIViewController *result = [self.viewControllers lastObject];
[self queueTransition:^{ [super popViewControllerAnimated:animated]; }];
return result;
}
- (NSArray*)popToRootViewControllerAnimated:(BOOL)animated {
NSArray* results = [self.viewControllers copy];
[self queueTransition:^{ [super popToRootViewControllerAnimated:animated]; }];
return results;
}
# pragma mark - UINavigationControllerDelegate
-(void) navigationController: (UINavigationController*) navigationController
didShowViewController: (UIViewController*) controller
animated: (BOOL) animated {
[self dequeTransition];
}
# pragma mark - Private Methods
-(void) queueTransition:(void (^)()) transition {
[waitingList addObject:transition];
if (waitingList.count == 1) {
transition();
}
}
-(void) dequeTransition {
if (waitingList.count > 0) {
[waitingList removeObjectAtIndex:0];
}
if (waitingList.count > 0) {
void (^transition)(void) = [waitingList objectAtIndex:0];
if (transition) {
transition();
}
}
}
#end

Resources