iOS view.hidden = NO not always working - iphone

My viewController is receiving notifications from background threads and updates the UI accordingly.
Using this piece of code
dispatch_async(dispatch_get_main_queue(), ^{
self.myButton.hidden = NO;
[self.view addsubview:someView];
});
What I observe every now and then (2/5 times) is that myButton is not visible. someView have been added. If I press the area where myButton is it becomes visible.
What is the correct way of updating the UI like this when called from a background thread, I thought this was it but it is obviously not working as I expected.

There is not any issue with this code, You should check that you are not hiding it elsewhere.
So just search for other part or your code where you have code self.myButton.hidden

Related

Activity Indicator not showing up

I have two issues with activity indicator:
1. Activity Indicator not showing up on UIViewController
I have activity indicator added in .xib file. On button click it should start animating. and when response from server is received, before going to next page it should stop animating.
I am doing it as follows:
activityIndicator.hidden = NO;
[activityIndicator performSelector:#selector(startAnimating) withObject:nil afterDelay:0.1];
[self.view bringSubviewToFront:activityIndicator];
....rest of code here....
activityIndicator.hidden = YES;
[activityIndicator stopAnimating];
Activity Indicator not showing up on UITableView
For table view I am doing it same way but on didselectrowatindexpath...
For tableview I also tried adding activity view to cell accessory, but still not showing up
In both cases activity Indicator is not showing up.
Please help
Thanks
If all this code is in one method or in response to one event, then none of the changes to the views are going be visible until you return to the event loop. You set the activityIndicator.hidden to NO and then set it again to YES before the UI has an opportunity to even refresh.
You also apparently stop the animation before you start it.
What you need to do is make the activity indicator visible here and start its animation. Then schedule the work to be done (start an asynchronous network connection, or put some work into a queue, or whatever it is you need to get done) and return from this method so that the UI can refresh, the indicator can be drawn, and the animation can actually start.
Then later at some point after the work is complete, you can hide the indicator and stop the animation. But you can't do all of that on the main thread within one single turn of the event loop. None of your changes will be visible because no drawing at all will happen here while this method is executing (assuming this is on the main thread)
I hope that makes sense?
Now I modified the code to this:
activityIndicator.hidden = NO;
[activityIndicator startAnimating];
[self performSelector:#selector(saveClicked) withObject:nil afterDelay:0.1];
[self.view bringSubviewToFront:activityIndicator];
and it worked :)
May be, in tableView, instead of self.view , it will be self.navigationController.view ??

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.

How to solve UIThread bloack issue in iPhone

I am creating an iPhone application where I need to show a login screen for few minutes, hence I created the custom view and added to the custom view controller which is added to the window for display. Now at the same time I need to check for some background database so, I am creating that in separate delegate and while after database operation is in finished it gives an callback to the main thread to display the new screen. But the first view is never getting displayed and my application directly lands up in the new view.
Please find below my code snippet:
(void)CheckForExistingData : (DatabaseSource *)theDatabaseConnection
{
BOOL isRecordExist = theDatabaseConnection.isrecordExist;
// Release the connection....
[theDatabaseConnection release];
theDatabaseConnection = nil;
if (isRecordExist == FALSE)
{
textLabel.text = #"Preparing the application for first time use, please wait....";
[activityIndicator startAnimating];
[self setNeedsDisplay];
}
else
{
// Now all categories are successfully downloaded, launch the category screen...
sleep(2); // sleep for 1 second to allow to show the splash screen....
[self.viewController LaunchCategoryViewController:self];
}
}
Here CheckForExistingData is an callback mechanism which will be called from the other thread.
You need to exit your method to see anything displayed. Not sleep or wait on a synchronous network call.
That probably means you need to break your sequential code into multiple methods, the subsequent parts being called by a splash wait timer, the view button handler, or the async network activity completion callback.
sleep() blocks your main thread, thus the UI has no chance to update.
But you can always send messages delayed. In your case, it would look like this:
[self.viewController performSelector:#selector(LaunchCategoryViewController:) withObject:self afterDelay:2.0];

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.

Keeping a UISegmentedControl (among others) responsive

I have a segmented control being used as a toggle. When toggled, I switch a bunch of contents around in a table view which takes a tiny but noticeable amount of time (inserting/deleting sections in the table view, animating the change, etc). I want the segmented control to respond to the selection immediately. So in my action handling code for the segmented control's UIControlEventValueChanged event, I do the following:
- (IBAction)groupingChanged:(id)sender {
UISegmentedControl *seg = sender;
[tableModel toggleOn:[seg selectedSegmentIndex] == ToggleOnIndex];
[self performSelectorOnMainThread:#selector(updateGrouping)
withObject:nil
waitUntilDone:NO];
}
Where updateGrouping is:
- (void)updateGrouping {
MXAssertMainThread();
[tableView beginUpdates];
... several table updates
[tableView endUpdates];
}
Setting waitUntilDone:NO allows the groupingChanged method to complete before the updateGrouping is called, but this doesn't seem to be sufficient to repaint the view. The segmented control sticks until the table is done updating, then it switches over.
So I tried modifying groupingChanged: to create a thread for the update like so:
- (void)delayed {
[self performSelectorOnMainThread:#selector(updateGrouping)
withObject:nil
waitUntilDone:NO];
}
- (IBAction)groupingChanged:(id)sender {
UISegmentedControl *seg = sender;
[tableModel toggleOn:[seg selectedSegmentIndex] == ToggleOnIndex];
[self performSelectorInBackground:#selector(delayed) withObject:nil];
}
And this does work. The segmented control toggles instantly and the table soon follows. But I'm not at all confident of the result. Is it simply a side-effect of giving the main thread a reprieve while the new thread started up? Is this just how I need to queue updates to the UI? It's clearly hacky. I'm hoping someone has a better pattern they're following for this situation.
Think of it this way - everything you do is generally done on the main thread, including UI updates.
So in your original code, the code that updated the table view was reached before the code that did the UI update on the segment.
So, you were correct that giving the main thread a break allowed more time to complete the updates to the UI, because the main thread was allowed to complete the UI updates while the background thread handled the table updates.
An alternative you could try is to use performSelector:withObject:afterDelay with a 0.0 delay (which allows the main thread to process other things before proceeding with the selector). It may work where performSelectorOnMainThread did not, as that call may be more immediate even though they end up doing something very similar.
If you just want to make sure the segmented control gets repainted real quick, I probably wouldn't dive into threads.
Instead I would just set a timer with a low value like 0.1, and that should be sufficient enough to get the control updated without any noticeable delay to the user.
I've used this when I have a lot of work to do but need a quick UI update.
Still a little "hacky", but without the introduction of threads.
So...
- (IBAction)groupingChanged:(id)sender {
UISegmentedControl *seg = sender;
[tableModel toggleOn:[seg selectedSegmentIndex] == ToggleOnIndex];
[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(updateGrouping)
userInfo:nil
repeats:NO];
}
I hit the same problem and solved it by subclassing the UISegmentedControl to create a Delayed UISegmentedControl.
In the delayed control I overrode addTarget:action:forControlEvents: to capture the target & action. Then when the segment event occurs I run an NSTimer to launch the captured target & action after a set delay. The result is that the UI gets updated to display the segment clicked and I can use the DelayedUISegmentedControl like I would a UISegmentedControl:
// Follows all normal initialization patterns of UISegmentedControl
UISegmentedControl *segmentedControl = [[DelayedUISegmentedControl alloc]
initWithItems:[NSArray arrayWithObjects: #"First", #"Second", nil]];
// Adds a delay to the selector; default is 0.25
[segmentedControl addTarget:self action:#selector(segmentAction:)
forControlEvents:UIControlEventValueChanged];
If you want to download the control I've open sourced it on google code.