iPhone loading view from nib only works once - iphone

I'm trying to add a «loader-view» to my app which shows a spinner while doing stuff.
This works fine the first time, but it doesn't work a second time.
here's what I do:
I have a viewController for the spinner (spinnerViewController) and a nib-file which I made in IB (spinner.xib).
I load the nib in the viewDidLoad-event:
spinnerView = [[spinnerViewController alloc] initWithNibName:#"spinner" bundle:nil];
[spinnerView retain];
spinnerView is declared in the .h-file (spinnerViewController *spinnerView;)
next, I show the spinner-view:
[self.view addSubview:spinnerView.view];
[self.view bringSubviewToFront:spinnerView.view];
which works fine...
And now the trouble starts. No matter what I do, I can't show the spinner view again.
I tried just hiding it (self.view sendSubViewToBack: spinnerView.view) which works for hiding, but when I try to bring it to the front again (self.view bringSubViewToFront: spinnerView.view) it doesn't work.
I also tried removing the spinner-view and add it again with no success (within the spinnerViewController: [self.view removeFromSuperview] and to show it again [self.view addSubview... )
[EDIT]
I changed the whole setup a little and just made the spinner-view a subview in IB - again, hiding works, but showing again fails.
What I found out: After the bringSubViewToFront-command, I call some web-service to get some data. When I comment the following code out and just show the spinnerView, it works. So now I'm trying to figure out how to determine when the spinner-view appeared and then continue with the code - but naturally, this doesn't work (yet) :)
Any ideas what I'm doing wrong??? ;)

Problem solved.
This page gave the answer: http://urenjoy.blogspot.com/2009/05/uiactivityindicatorview.html
Apparently, the update has to happen in a separate thread, as the web-stuff blocks the current one, hence the view did not appear.
[NSThread detachNewThreadSelector:#selector(doWork) toTarget:self withObject:nil];
- (void) doWork {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
.....Time Consuming Code here .....
[pool release];
}

I might be not exactly on your question, but in general creating a ViewController class in order to show a spinner on the screen is a huge overkill ... just try to discover the logic behind what you do : you create a viewcontroller, but you never use it, you use the view.
So in short I believe you need only a UIView (the view property of the UIViewController)
Why don't you try something like :
... in your class interface...
UIActivityIndicator* activity;
... when the activity needs to happen ...
activity = [[UIActivityIndicator alloc] initWithActivityIndicatorStyle: ....
[activity startAnimating];
[self.view addSubview:activity];
[activity release]
... when the activity is finished
[activity removeFromSuperview]; //this will also decrease the retain count

Related

How to update a progressbar during instantiation of timeconsuming ui elements?

I would like to update a progressbar while i am instantiating some ui elements that takes some times. I first create my view during the viewLoad method and add my progress bar there. Once my view appeared in the viewDidAppear method I am making several uikit objects instantiation but i would like to update the progress bar in the mean time. I am not sure how to proceed since everything should happen in the main thread as it is ui elements.
Here is part of my code:
-(void) viewDidAppear:(BOOL)animated
{
// precompute the source and destination view screenshots for the custom segue
self.sourceScreenshotView = [[UIImageView alloc] initWithImage:[self.view pw_imageSnapshot]];
[self.progressBar setProgress:.3];
SCLViewController *rvc = [[SCLViewController alloc] init];
UIView *destinationView = rvc.view;
destinationView.frame = CGRectMake(0, 0, kWidthLandscape, kHeightLandscape);
self.destinationScreenshotView = [[UIImageView alloc] initWithImage:[destinationView pw_imageSnapshot]];
[self.progressBar setProgress:.5];
}
In the above code I just need to create two screenshots of views to use them later on. The problem is that i only see the last update (.5) when setting the progress to the progress bar. What is the proper way to do this update?
You can use the performSelectorInBackground:withObject: method in order to instantiate your heavy views. That method (the one that instantiates your views) will have to set your progress bar progress in the main thread.
So your code would look something like this:
- (void)viewDidAppear:(BOOL)animated
{
[self performSelectorInBackground:#selector(instantiateHeavyViews) withObject:nil];
}
- (void)instantiateHeavyViews
{
self.sourceScreenshotView = [[UIImageView alloc] initWithImage:[self.view pw_imageSnapshot]];
[self performSelectorOnMainThread:#selector(updateMyProgressView:) withObject:[NSNumber numberWithFloat:0.3f] waitUntilDone:YES];
SCLViewController *rvc = [[SCLViewController alloc] init];
UIView *destinationView = rvc.view;
destinationView.frame = CGRectMake(0, 0, kWidthLandscape, kHeightLandscape);
self.destinationScreenshotView = [[UIImageView alloc] initWithImage:[destinationView pw_imageSnapshot]];
[self performSelectorOnMainThread:#selector(updateMyProgressView:) withObject:[NSNumber numberWithFloat:0.5f] waitUntilDone:YES];
}
- (void)updateMyProgressView:(NSNumber *)progress
{
[self.progressBar setProgress:[progress floatValue]];
}
Edit: of course, it won't animate your progress bar (I don't know if that is what you wanted). If you want it to move on while your views are being created, you should use a delegate to be notified of the progress, and this can be a bit harder. This way you would be able to update the progress bar every time the delegate is notified.

Pushing View Controller - viewDidAppear not called

I have this piece of code to push a view controller:
// Setup the animation
[self.navigationController pushViewController:self.productView animated:YES];
self.productView.imageURL = [product imageURL];
// Set the title of the view to the product's name
self.productView.title = [product name];
// Set the label text of all the labels in the view
[self.productView.caloriesL setText:[product calories]];
[self.productView.fatL setText:[product fat]];
[self.productView.saturatesL setText:[product saturates]];
[self.productView.sugarL setText:[product sugar]];
[self.productView.fibreL setText:[product fibre]];
[self.productView.saltL setText:[product salt]];
But the delegate method viewDidAppear does not get called when the productView appears. I looked up the problem on google and theres a lot of different solutions, none of which I could apply to my problem.. I had a similar problem in a previous solution but I got around it by manually calling viewDidApear in the viewDidLoad method. Unfortunately in this case I can't do that as viewDidLoad is called only once (on the first push). Does anyone know how to fix this?
Thanks,
Jack Nutkins
EDIT:
Here is the viewDidAppear method in the productView (and selector):
- (void)viewDidAppear:(BOOL)animated{
//Start animating the activity indicator
[indicator startAnimating];
//Perform this method in background
[self performSelectorInBackground:#selector(loadImage) withObject:nil];
}
- (void) loadImage {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Load the animals image into a NSData boject and then assign it to the UIImageView
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
UIImage *image = [[UIImage alloc] initWithData:imageData];
self.imageView.image = image;
//Stop animating the activity indicator
[indicator stopAnimating];
[pool drain]; //see comment below
}
First: You definitely don't want to be calling any of the standard viewWillLoad, viewDidLoad, viewWillAppear, etc. methods manually. Let the OS do it for you.
Second: Can you show us how your viewDidAppear method is implemented in your self.productView instance? (Just a hunch, you're not expecting this method to be called on your navigation controller, right?) I just want to make sure your method signature is exactly correct. If it's not (due to a mispelling, improper args, etc.) then it definitely won't be called.
Third: I would move your pushViewController: call to after the rest of the code you provided. You don't want the view to be pushed on the screen (so the user can see it) and then have a bunch of on-screen values immediately change. Set your ivars and title property first, then push the view controller. This eliminates any weird flickering.
I solved it, though it doesn't seem conventional, can't believe I didn't try it earlier :
I put this line :
[self.productView viewDidAppear:YES];
Underneath :
// Setup the animation
[self.navigationController pushViewController:self.productView animated:YES];
I also moved the code to set the labels text to run before the above line. (As well as changing my code to send strings to the pushed controller rather that accessing its UI elements.)
Thanks for everyones help,
Jack

UIPopoverController memory question

I'm creating a UIPopoverController and setting "Editor1" as the content view controller.
When the caller receives the didDismissPopover I'm releasing the UIPopoverController.
This is the code:
- (IBAction)open1:(id)sender {
Editor1 *editor = [[Editor1 alloc] initWithNibName:#"Editor1" bundle:nil];
_popoverController = [[UIPopoverController alloc] initWithContentViewController:editor];
_popoverController.delegate = self;
[editor release];
[self.popOverController presentPopoverFromRect:self.open1Button.bounds inView:self.open1Button permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
}
- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController{
NSLog(#"popoverControllerShouldDismissPopover");
return YES;
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController{
NSLog(#"popvoerControllerDidDismissPopover");
[_popoverController release];
}
In my editor I have a UITextField where the user changes text and I save it when I get the message "editingDidEnd"
- (IBAction)editingDidEnd:(id)sender {
NSLog(#"Editing did End");
// SAVE PROCEDURE
}
My question regards the order in which the methods get called.
The order is:
2011-09-07 12:35:21.628 iosTest[1967:b603] popoverControllerShouldDismissPopover
2011-09-07 12:35:21.629 iosTest[1967:b603] popvoerControllerDidDismissPopover
2011-09-07 12:35:21.983 iosTest[1967:b603] Editing did End
2011-09-07 12:35:21.985 iosTest[1967:b603] viewWill Disappear
As you can see the popoverControllerDidDismissPopover gets called before editingDidEnd:, so this means I'm releasing the popover before I do my save procedure. This could bring me a crash problem.
Also, in my save procedure I need to ask the user for confirmation in some cases. I'm using a UIAlertView for this.
Do you have any recommendations?
Usually views are well-behaved and don't send events after they're off-screen. You can check for potential problems by enabling zombies (set the environment variable NSZombieEnabled=YES).
If there is a crash, the correct place to fix it is in -[Editor1 dealloc] (and possibly -viewDidUnload): just do textField.delegate = nil and you should stop receiving callbacks. This is not usually necessary except for web views and scroll views where it seems to be problematic (the scroll animation continues even if the VC is off-screen).
In your case, you can probably make saving happen in -popoverControllerShouldDismissPopover:, returning NO if you need to display a UIAlertView (and dismissing the popover when the button is pressed).
it seems, that _popoverController is the instance-property. in this case you can release it in viewDidUnload method of parent-controller.
Why don't you use UITextFieldDelegate protocol? Usage:
aTextField.delegate = self;
(...)
- (void)textFieldDidEndEditing:(UITextField *)textField {
NSLog(#"Editing did End");
// SAVE PROCEDURE
}
Read the documentation for more info.

Toggle between two images with sleeptime

I need to toggle a specific amount of times between two (or maybe later on more) pictures after a button was pressed, and wait a second or two for the change. When a stop-button is pressed at any time, the toggling should stop. My code by now looks like this
IBOutlet UIImageView *exerciseView;
- (void) repetitionCycle {
stopButtonPressed = NO;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (NSInteger counter = kRepetitions; counter >=0; counter--) {
exerciseView.image = [UIImage imageNamed:#"blue.jpg"];
[NSThread sleepForTimeInterval:kSleepDuration];
if (stopButtonPressed) {break;}
image = [UIImage imageNamed:kExerciseEndingPosition];
exerciseView.image = [UIImage imageNamed:#"image1.jpg"];
[NSThread sleepForTimeInterval:kSleepDuration];
if (stopButtonPressed) {break;}
}
self.stopRepetitionCycle;
[pool release];
}
exerciseView is
Besides other things, in stopRepetitionCycle I just set stopButtonPressed to YES, so it stops after it finished the "for" for the first time.
It does count down, it does stop after one cycle, but it doesn't change the picture.
While fiddling around, I set the initial picture via IB, so it finally displayed ANYTHING.. The fun part, when I hit the stop button in the right moment, the second picture is shown. So I guessed I need to set the view manually every time the image should toggle. But
[self.exerciseView addSubview:image];
gives me the error
Incompatible Objective-C types "struct UIImage *", expected "struct UIView *" when passing argument 1 of "addSubview:" from distinct Objective-C type
Also
[self.exerciseView.image addSubview:image];
doesn't do the job and gives me a
UIImage may not respond to addSubview
Any idea what I have to do here?
Thank you very much!
uuhm...
your usage of [NSThread sleep...] puzzles my...
in fact: if you are on a secondary thread (meaning, not the main thread), then you are doing something not allowed, which is trying to access the UI from a secondary thread.
this could explain the strange behavior you are seeing.
on the other hand, if this is the main thread, possibly is not a good idea to call sleep... because that way you will freeze entirely the UI, and you could not possibly intercept the click on the button...
anyhow, what I would suggest, is using NSTimers to move from one image to the next one at certain intervals of time. when the stop button is hidden you would simply cancel the timer and the slideshow would end. pretty clean.
as to the error messages you are having with your image, the fact is that UIImage is not a UIView, so you cannot add it as a subview, but this is not what does not work here...
Use UIImageView. It has built-in support for this.
imageView.animationImages = [NSArray arrayWithObjects:[UIImage imageNamed:#"blue.jpg"], [UIImage imageNamed:#"image1.jpg"], nil];
imageView.animationDuration = kSleepDuration * [imageView.animationImages count];
Wire this function to the start button
- (IBAction) startAnimation {
[imageView startAnimating];
}
and this to the stop button
- (IBAction) stopAnimation {
[imageView stopAnimating];
}
Updating the UI within a loop is almost guaranteed to fail. You should most likely be using an NSTimer to swap the image at a given interval instead.
Furthermore, [self.exerciseView addSubview:image] is failing because you're passing a UIImage and not a UIView. Create a UIImageView (which is a subclass of UIView) with your UIImage and pass that instead.
The method addSubview is used to add only UIViews. You can't use it with a UIImage class. The general syntax is :
[(UIView) addSubview:(UIView*)];
replace
[self.exerciseView addSubview:image];
with
self.exerciseView.image = image;
For the same reason
[self.exerciseView.image addSubview:image];
won't work either.

Why aren't my UILabels refreshing with new information after an NSNotificationCenter update?

I am working on a weather app, and have everything working perfectly...Except the UILabels that are displaying the data. Once the app loads for the first time, it goes through and finds the data correctly and then displays it.
This is what one of the UILabels looks like, inside my main RootViewController:
UILabel *myCityLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 30, 200, 80)];
[myCityLabel setText:[NSString stringWithFormat:#"%#",placemark.locality]];
myCityLabel.textAlignment = UITextAlignmentLeft;
myCityLabel.textColor = [UIColor blackColor];
myCityLabel.font = [UIFont fontWithName:#"Helvetica" size:24];
myCityLabel.backgroundColor = [UIColor clearColor];
[self.view addSubview:myCityLabel];
[myCityLabel release];
I have CoreLocation running in the background. Inside my appDelegate, I see these two methods get called once the app is invoked again (after closing it):
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSLog(#"applicationDidBecomeActive:");
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(deviceNotificationReceived:) name:UIApplicationDidBecomeActiveNotification object:nil];
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
lbsViewController = [[LBSViewController alloc] init];
[lbsViewController viewDidLoad];
}
- (void)deviceNotificationReceived:(NSNotification *)notification
{
NSLog(#"was this received?");
}
What I am trying to do here is to launch the viewDidLoad method of lbsViewController (my main RootViewController. I can see via the console that it is returning new information, and even that the function viewDidLoad is being called, but the labels aren't refreshing with the new data...any suggestions on what route(s) I can take to fix this problem?
I should note that the only time the UILabels are refreshing with new data is upon building the app from Xcode to my device.
You say that you solved the problem by removing multitasking. Your problem was that the NSNotification you sent from the background thread arrived on the same background thread, and you tried to update your UILabel from that background thread. That isn't allowed - you must update UI elements from the main thread.
To solve this, you can marshall the call to the main thread using:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
so, something like this (in your notification handler):
[viewController.outputLabel performSelectorOnMainThread: #selector( setText: ) withObject: currentChar waitUntilDone: YES];
note, this is the same response I gave to this question: How do I display characters to a UILabel as I loop through them?
The two lines:
lbsViewController = [[LBSViewController alloc] init];
[lbsViewController viewDidLoad];
Are allocating a brand new LBSViewController and calling viewDidLoad on it. Probably you want to call viewDidLoad on your existing lbsViewController object rather than allocating a new one (ie, remove the first of those two lines).
In any case, it'd be better to have the view controller observe the notification itself and deal with everything internally. This sort of overloads the meaning of viewDidLoad, and adding a method with an alternative name arguably distributes the logic in an odd way.