Total mystery - iOS methods execute after random delay - iphone

I have the variant of the following codes, triggered by a user input.
NSLog(#"WHY YOU iOS!!!");
[scanButton setSelected:YES];
[overlayImageView setImage:image];
[overlayView setHidden:YES];
The thing is that the above routine executes with seemingly random delay from five seconds to more than a minute.
Sometimes, they don't execute at all, except, mysteriously, the NSLog method. If I set a breakpoint at the NSLog line, it just executes immediately and I can see "WHY YOU ..." printed out in the console. However, even though the debugger runs smoothly past the below three lines without errors or warnings, the effect of the the next three lines takes place after random delay, or infinite delay. What's wrong? How could it be possible that they have random delay?
I suspected that UI part of Cocoa SDK suspends due to my poor program design or whatever, but even during the random delay, other buttons and functionalities work so fine, while the effect of setImage takes place suddenly in 47.2 seconds. What's wrong with iOS, or with me?
What confounds me more is that the above exact routine works fine without delay if the image parameter (UIImage*) comes from a different source. If that's the case, we can attribute the cause of the problem to the image, however, why do scanButton and overlayView also respond with the same delay, even though they have nothing to do with the image? Total mystery to me.

I have no idea on whats going on, as there is almost no hint in the code that you've provided, but why you dont try something like this:
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"WHY YOU iOS!!!");
[scanButton setSelected:YES];
[overlayImageView setImage:image];
[overlayView setHidden:YES];
[overlayImageView setNeedsDisplay];
[overlayImageView setNeedsLayout];
[scanButton setNeedsDisplay];
[scanButton setNeedsLayout];
[overlayView setNeedsDisplay];
[overlayView setNeedsLayout];
});
Basically ensure always to update de UI in the main thread (in case that you're using a background thread) and set the UI controls as needed to be redrawn.

Related

Objective C UIActivityView stops animating only after a delay

I am not sure why, but for some reason my UIActivityIndicator spinner stays visible for awhile after I ask it to stop spinning. In my app, I check for updates with a block method callback when the check is complete after which point the activity indicator is to be hidden. The NSLog is processed immediately when the block is called, but it takes maybe 5-10 seconds for the indicator to disappear. Nothing is going on in the main as far as I can tell, the app is just sitting there. I am very confused...
[self showActivityIndicator];
[[self schedulePack] checkForUpdates:^(void)
{
NSLog(#"Done updating.");
[self hideActivityIndicator];
}];
The problem is probably that you access a UI element from a separate thread. Try replacing
[self hideActivityIndicator];
with
[self performSelectorOnMainThread:#selector(hideActivityIndicator) withObject:nil waitUntilDone:NO];

UIScrollView's setContentOffset called before new subview is loaded

I have a UIView that I'm sliding in using a UIScrollView. Code looks like this:
[view setObject:object]; // updates a bunch of labels, images, etc
[scrollView addSubview:view];
[scrollView setContentOffset:CGPointMake(320,0) animated:YES];
Problem is, the scrollView doesn't seem to wait for the view to be completely loaded before animating its contentOffset, and so the animation is kinda jerky (almost non-existent on the first slide-in and on older devices). Oddly enough, switching the 3rd line to this fixes it:
[UIView animateWithDuration:0.3 animations:^{
[scrollView setContentOffset:CGPointMake(320,0) animated:NO];
}];
No lag whatsoever, perfectly smooth slide-in. However, this doesn't trigger any of the UIScrollViewDelegate calls, on which I also depend (but which aren't responsible for the lag).
Any idea of how I could tell the UIScrollView to wait for the view to be completely loaded before animating its contentOffset? Or maybe there's something else that I'm missing?
EDIT: before anyone else suggests it: yes, I did try:
[self performSelector:#selector(slideIn) withObject:nil afterDelay:1];
just to see if it would fix it. And yes it does fix the lag, but this is not an actual solution. performSelector:afterDelay: is never a solution, it's only a superficial fix. Plus, you're making the user wait extra seconds every time (since the actual delay may be much shorter than 1 second depending on the device model).
Performing selector after delay 0.01 works because the invocation is scheduled on the runloop vs. calling the method immediately. For that matter, afterDelay:0.0 may also work. Did not try in this particular case but works in may similar situations.
have you tried starting the scrolling in the "viewDidAppear" function of the viewcontroller... maybe this is called when the loading is finished.
otherwise try afterdelay with a very short time like 0.01 .. so that the call is scheduled next after the current work is done.
Try this, set contentSize after some delay (here delay is 2 second).
[view setObject:object];
[scrollView addSubview:view];
[self performSelector:#selector(contentSize) withObject:nil afterDelay:2];
}
-(void) contentSize {
[scrollView setContentOffset:CGPointMake(320,0) animated:YES];
}

UIImageView only displaying intermittently

I'm using the following code to display a page number, when the user switches pages in my iPad app. The number is supposed to show up in a nice transparent gray pane (similar to the "Build Succeeded" message in XCode). However, the image view only shows up about 5% of the time. (I haven't added the code to put a number in the pane yet). The NSLog() message appears every time. I've also set a breakpoint and stepped through, and the UIImageView code is getting called. Sadly, I can't step into the framework to see what it's doing.
The variables used are all locals or constants, so I doubt it has anything to do with a race condition. Also, I can wait minutes between clicks and not get an image, or press the button rapidly and get multiple stacked images (they're transparent, so it's easy to notice a stack).
I tried running a setNeedsDisplay on self.view after the addSubview: (even though I'm pretty sure addSubview: does it), but that didn't do anything.
Calling displayPageNumber: directly, without threading, doesn't make it appear consistently either.
Any ideas?
#define PageDisplayTime 0.5
#define PageDisplayImageName #"PageIndicator.png"
#define PageDisplayImage [UIImage imageNamed: PageDisplayImageName]
...
[NSThread detachNewThreadSelector: #selector(displayPageNumber:)
toTarget: self withObject: index];
...
- (void) displayPageNumber: (NSNumber*) _pageIndex
{
NSLog(#"Page Number: '%d'.", [_pageIndex integerValue] + 1);
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
UIImageView* glassPaneView = [[UIImageView alloc] initWithImage: PageDisplayImage];
glassPaneView.center = CGPointMake(300.0, 300.0);
[self.view addSubview: glassPaneView];
[NSThread sleepForTimeInterval: PageDisplayTime];
[glassPaneView removeFromSuperview];
[glassPaneView release];
[pool release];
}
AFAIK all UI coding must be done on the main thread. doing it on the background thread will at best have unpredictable results, at worse will crash.
Calling the method directly (thus in the main thread) doesn't work because the sleep just stops the current thread, so it is not able to do anything including drawing until you remove the view, thus preventing it from showing.
You have to refactor the method into multiple methods called on the main thread. The first just adds the subview and sets a timer, the timer handling method then removes the subview.
I figured out the issue. In case others run into this, here it is:
The addSubview: / removeFromSuperview pair with the sleepForTimeInterval: between them are basically useless. I knew sleeping blocked the thread, but apparently this thread needs to be unblocked to update the view. I assumed (incorrectly it appears) that the view update happened on the main thread.
Here's my replacement solution:
unhide the subview in the main thread and call detachNewThreadSelector: with a message that sleeps for X seconds and then hides the subview.
And I'm going to add an NSLocked counter that gets incremented on unhide, and decremented in the hider message, but only hides when the counter is down to 0. This will allow repeated activations to extend the duration.

Flashing UILabel as a

I'm trying to use a UIImage as a button which gives the impression of it being turned on then off again within about half a second. This works fine if I switch it on but if I want to switch it off again it doesn't switch on at all. I have a short loop in there to prevent it switching on and off so fast I can't see it but it doesn't switch on at all. I've tried it with and without the [flashingButton release]. Is there something I'm misunderstanding here? Can I addSubview and removeFromSuperView at the same time even with a short delay?
if ( some conditional statements in here .......) {
UIImage *estimateButton1 = [UIImage imageNamed:#"FlashingButton.png"];
flashingButton = [[UIImageView alloc] initWithImage:flashingButton1];
flashingButton.frame = CGRectMake (146,8,165,30);
[self.view addSubview:flashingButton];
// [flashingButton release];
// short loop in here to delay urning the button off
[self.flashingButton removeFromSuperview];
User interface drawing doesn't happen until later in the main run loop. Your call to addSubview adds flashingButton to self.view but doesn't draw it. Your short loop blocks the main run loop, so it still doesn't get to the drawing part. And then, you remove the button before the main run loop gets to draw it.
A solution is to let the main run loop continue after you've added the flashing button (so it will get drawn), but create a timer that will remove that button in the future. You can use performSelector:withObject:afterDelay: to do this.
[self.flashingButton performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:0.5f];
You can read about run loops in "Threading Programming Guide" and about how drawing gets done in "View Programming Guide for iOS."
Looping within the main thread will just hang the program temporarily and prevent any drawing from taking place. Instead, use an NSTimer.

UIActivityIndicator not working properly?

I have a problem regarding UIActivityIndicator. I applied [spinner startAnimating] at the IBAction on a button and then doing some process. After the process activityindicator should be stopped and then navigate to another view. But the activity indicator does not appear. When I remove the line "[spinner stopAnimating]" then the indicator appears but not at the instant button is pressed. It appears just before the other view loads, and apparently does not appear, I mean it does not appear but if we see very carefully then only it appears for an instant.
Thanx in advance for any answer.
Ole is pretty much correct, but there is a trick of you don't mind synchronous processing (often that it why you want to display the activity indicator in the first place).
First move your code that you want to process while the spinner is up to its own method. Then do
[spinner startAnimating];
[self performSelector:#selector(methodname) withObject:nil afterDelay:0];
The afterDelay:0 means on the next time through the run loop. That way the spinner gets started.
The animation will not start until your code returns control to the run loop. If your processing task blocks the main thread, the no UI updates will take place until it is finished. You should do your processing asynchronously (e.g. by starting an NSOperation).
you should run in perform selector .
for ex:
[self performSelector:#selector(animation) withObject:nil afterDelay:0]
-(void)animation
{
NSAutoreleasepool *pool = [[NSAutorepleasepool alloc]init];
[indicatorView startAnimating];
[pool release];
}
This is an old question. I leaving my answer here, so that might help someone to solve their problem.