viewDidAppear, but where is viewDidShowToUser? - iphone

I have an iphone3g with this function running in my ViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidLoad];
}
I use a TabBar iphone app. But when I click from tab 1 to tab 2 and debug the secondView Controller it is stopped before the view is actually in the users view.
So there for when you click tab 2 until every function inside - (void)viewDidAppear:(BOOL)animated is complete the user gets to see the view.
Where is the function ViewDidShowToUser? Now I have a few functions running so it's sometimes slow and you're thinking the button is not working really..

First of all, you're calling [super viewDidLoad] instead of [super viewDidAppear:animated] inside your implementation of -viewDidAppear:
Secondly, using the debugger and breakpoints gives an artificial view of how your app behaves. In real world usage, users aren't going to notice that the -viewDidAppear: method returns before actually showing the view.
The real problem is your work that takes too long to complete and makes the app appear sluggish. You should consider performing the work asynchronously, and you have a couple of options to do that.
In your viewDidAppear: implementation you could use performSelector:withObject:afterDelay: to queue up the work. This method will return immediately and schedule your selector to be called in whatever time period you specify. If you pass 0 as the delay, it'll be queued up to run on the next iteration of the run loop, effectively giving you a way to return from the method and keep the user interface responsive.
You could use blocks, if you're not targeting anything below iOS4, and harness the power of Grand Central Dispatch to thread out your work nice and safely.

you are calling super on viewDidLoad: inside of viewDidAppear: ....change the line [super viewDidLoad]; to [super viewDidAppear:animated];

There's no easy way to tell.
UIKit makes UIViews. UIViews draw to CALayers. CALayers are handled by CoreAnimation.
CoreAnimation decides when to ask UIView to draw the layers. It draws all the layers on screen and then composites them on the GPU. Only after they're composited does the screen display the updated UI. This decoupling happens in order to allow CoreAnimation to do the majority of the work independently of the UI thread, but it means that you can't easily tell what's "actually on screen".
There is no easy way to tell when the screen has actually displayed something (apart from the now-private UIGetScreenImage()). viewDidAppear: gets called after UIKit finishes constructing (and animating) the views/layers. At that point, they will be "seen" by CoreAnimation after the next run loop, and displayed shortly thereafter. If you do lots of processing in viewDidAppear:, then CoreAnimation will never see the updated "model tree".

Related

Understanding setNeedsDisplay/drawRect with Blocks

I'm trying to understand how things work in regards to concurrent programming and calling setNeedsDisplay. I basically have Three objects.
Main View - container with different UIView objects, the main one being a UIScrollView
Small Map View - a small UIView that draws a miniature version of one of the other UIView items on screem
Processor - a delegate of the Main View that calculates what's on screen and calls the Main View back with what's in view.
So a simple use case of what's going on is the user touches the ScrollView and then the processor updates what's in view of the scrollView (like calculating coordinates, center point, etc) It does this using blocks and does it asynchronously. This then posts a notification to the MainView object.
When the MainView receives the notification, it just calls
[smallMap setNeedsDisplay]; // example 1
I put some logs around this call, and I do see it gets called right away. However, the drawRect: of this function does not get called right away. It gets called after 2 seconds or so.
I remember reading that setNeedsDisplay just marks the view for redraw to happen on the next event of the run loop.
But if I add this code instead:
// example 2
dispatch_async(dispatch_get_main_queue(), ^{
[smallMap setNeedsDisplay];
});
My view gets redrawn right away.
I guess I'm confused as to why I have to ask for the main event loop to call setNeedsDisplay to immediately redraw something. Like in example 1, by me calling setNeedsDisplay, is that done in the background or something and that's why it doesn't get redrawn right away? I'm trying to understand the difference in what's going on behind the scenes so I know what to look for the in future. Like should I have all my calls that need to be immediately redrawn in something similar to the example 2 block? Or is it because I'm processing my data asynchronously that I need to then ask for the main queue? Thanks!
My guess is 1 of 2 things:
Your code that is running on a separate thread is calling your MainView methods from the separate thread instead of using performSelectorOnMainThread or a GCD call that invokes the code on the main thread. Thus your call to setNeedsDisplay is actually taking place on a background thread, which is a no-no, as the other poster said.
The second possibility is that your MainView code is running on the main thread, but it gets busy doing time-consuming processing, or waiting for a synchronous call to another thread to finish, and doesn't service the event loop.
You can rule out the first possibility by setting a breakpoint on your call to setNeedsDisplay and looking at the call trace in the debugger to see what thread it's running from.
Figuring out the second possibility will take a little more work. You might need to delve into instruments.
setNeedsDisplay is a UIKIT API call and has to be called from the main thread of the application, also known as the UI thread. That's why calling it in a background thread doesn't have any immediate effect and scheduling it on the main queue has immediate effects.
See this related question https://stackoverflow.com/a/6988115/172690 for a more detailed answer.

Are animations automatically removed from layers when switching viewcontrollers?

I've setup a simple CAKeyframeAnimation with infinite repeatcount and added it to a layer. All is fine except that the animation is removed when I switch to another tab (or very likely any other mechanism of changing the active view controller).
Is this documented anywhere, perhaps new in iOS 5, and is there another solution than starting the animation again in viewWillAppear? The timebase is irrelevant in this case, starting the animation from time 0 would be fine.
You should not perform any work with View while it is not on screen, because it's just waste of iPhone resources. Besides your application can get memory warning and view will be unloaded.
IMHO the best approach is to set up graphics in -(void) viewWillAppear and start animations in -(void) viewDidAppear.
P.S. In -(void) viewDidLoad parameters of frame of view are still not valid and if you try to use them, you can get something wrong.

UIView takes too long to update

In an iPad app, I have a bunch of UIImageViews inside a bigger UIView. Each UIImageView contains a thumbnail that is generated in a separate thread (so as not to freeze the application). After thumbnail has been successfully generated I call setNeedsDisplay on main thread, however, it doesn't update the UIImageViews as the thumbs become available (I can see them in the log), rather it takes about 5 seconds and then displays all of them at once.
here's what I am doing when a thumbnail has been created in a separate thread:
[self performSelectorOnMainThread:#selector(setNeedsDisplay)
withObject:nil
waitUntilDone:NO];
any ideas?
What's your main thread up to? If your application waits until all the thumbnails are available before redrawing, then it sounds like maybe you are inadvertently blocking on your main thread until your thumbnailing queue has emptied. How are you setting up the threads?
The problem is that you can't tell cocoa that it have to redraw "now".
With setNeedsDisplay you can onely order a redraw, because drawing is ra rather expensive procces.
setNeedsDisplay
You can use this method or the
setNeedsDisplayInRect: to notify the
system that your view’s contents need
to be redrawn. This method makes a
note of the request and returns
immediately. The view is not actually
redrawn until the next drawing cycle,
at which point all invalidated views
are updated.
Mybe its a better and more performant solution to wait for all generated thumbnials?!
I would use NSNotification. Send a notification from your thread loading the images when an image is ready. Your view controller can observe these notifications and update the view as they arrive.

viewDidAppear called twice on the same instance, but only the first time this class loads form NIB

I have a navigation controller. One of the views adds custom subviews in its viewDidAppear:. I notice that the first time I navigate to an instance of this view controller after launching the app, viewDidAppear: invokes twice. If I pop this view off the stack and navigate to it again, viewDidAppear: invokes only once per appearance. All subsequent appearances invoke viewDidAppear: once.
The problem for me is that the first time I get to this view I end up with twice the number of subviews. I work around this problem by introducing a flag variable or some such, but I'd like to understand what is happening and how come I get two invocations in these circumstances.
You should never rely on -viewWillAppear:/-viewDidAppear: being called appropriately balanced with the disappear variants. While the system view controllers will do the best they can to always bracket the calls properly, I don't know if they ever guarantee it, and certainly when using custom view controllers you can find situations where these can be called multiple times.
In short, your -viewWillAppear:/-viewDidAppear: methods should be idempotent, meaning if -viewDidAppear: is called twice in a row on your controller, it should behave properly. If you want to load custom views, you may want to do that in -viewDidLoad instead and then simply put the on-screen (if they aren't already) in -viewDidAppear:.
You could also put a breakpoint in your -viewDidAppear: method to see why it's being called twice the first time it shows up.
maybe you invoke viewDidAppear in viewDidLoad (or some other stuff is going on there), since it's invoked only once during loading the view from the memory. It would match, that it's invoked two times only the first time.
This was not an iOS 5 bug, but a hidden behavior of addChildViewController:. I should file a radar for lack of documentation, I think
https://github.com/defagos/CoconutKit/issues/4
If you have a line like this in your AppDelegate
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
make sure you DON'T have a "Main nib file base name" property in your plist set to "Window.xib" or whatever your custom window nib is named. If you do, remove that row from your plist and make sure you something like
yourRootVC = [[UIViewController alloc] init];
[window setRootViewController:yourRootVC];
in your AppDelegate after instantiating your window. In most cases, you could then safely delete the Window.xib as well.
You definitely should provide more info.
Is this the root view controller?
Maybe you initiate the navigation controller with this root view controller and then push it to the navigation controller once again?
Another solution that may have been your underlying cause: Be sure you are not presenting any new view controllers from within your viewWillAppear: method.
I was calling:
[appDel.window.rootViewController presentViewController:login animated:YES completion:nil];
from within viewWillAppear and seeing my originating VC's viewDidAppear: method called twice successively with the same stack trace as you mention. And no intermediary call to viewDidDisappear:
Moving presentViewController: to the originating VC's viewDidAppear: method cleared up the double-call issue, and so now the flow is:
Original viewDidAppear: called
Call presentViewController here
Original viewDidDisappear: called
New view is presented and no longer gives me a warning about "unbalanced VC display"
Fixed with help from this answer: https://stackoverflow.com/a/13315360/1143123 while trying to resolve "Unbalanced calls to begin/end appearance transitions for ..."
it's such an annoying problem, you'd think it runs once but then I now found out about this which is causing mayhem... This applies to all 3 (ViewDidAppear, ViewDidLoad, and ViewWillAppear), I am getting this when integrating with a payment terminal; once it finish calling the API, the window is being re-loaded when it's already on-screen and all it's memory is still there (not retained).
I resolved it by doing the following to all the routines mentioned above, below is a sample to one of them:
BOOL viewDidLoadProcessed = false;
-(void)viewDidLoad:(BOOL)animated
{
if (!viewDidLoadProcessed)
{
viewDidLoadProcessed = YES;
.
.
.
... do stuff here...
.
.
}
}
Repeat the above for all the other two, this prevents it from running twice. This never occurred before Steve Jobs died !!!
Kind Regards
Heider Sati
Adding [super viewDidAppear:animated]; worked for me:
//Called twice
- (void)viewDidAppear:(BOOL)animated{
}
//Called once
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}

How do I create a reusable Loading Screen?

How do I create a loading screen that can be reused at any given time. I'm aware of the Default.png but I need the flexibility to plug in a loading screen at any point during the application life cycle.
This is what I have thus far.
//inside a method that gets called by a UIButton
LoadingViewController* loadController = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil vertical:NO];
[self.view addSubview: loadController.view];
//some method call that takes a few seconds to execute
[self doSomething];
//This loads some other view, my final view
[self.view addSubview: someOtherView]
but it seems that the loading view is never displayed. Instead the previous view stays there until the "someOtherView" gets added. I put trace logs and the code does seem to get executed, I even replaced [self doSomething] with a sleep(2), but the intermediate loading view is never displayed.
If I remove [self.view addSubview:someOtherView]; then after a few seconds...(after doSomething finishes executing) the load view is displayed since there is no view that is pushed on top of it, however this is obviously not the functionality I want.
Can explain this behavior? Is there something about the rendering cycle that I am misunderstanding because it doesn't seem like the view (on the screen at least) is instantly updated, even though I call a [self.view addSubview: loadController.view];
Would I need to create a separate thread?
In general, for changes in the UI to be made visible to the user, control must return to the main runLoop. You are only returning to the runLoop after taking the loading view down and replacing it with the other view. One strategy for dealing with this is to move the code that does the loading onto another thread. NSOperation and NSOperationQueue can be used for this.
An even simpler approach is to use performSelectorInBackground:withObject to do the processing. Once processing is complete the UI can be updated again to show the data. It is important to remember that the UI updates must be carried out on the main thread. Use performSelectorOnMainThread:withObject:waitUntilDone: to accomplish this from the loading thread.
This sounds like a lot of complication but it is really as simple as breaking your single method up into three separate methods as follows:
Display the loading view and start the background process - this is the button action method.
Do the background loading - called from the button action function with performSelectorInBackground:withObject.
Remove the loading view and update the display with the data - called from the background thread with performSelectorOnMainThread:withObject:waitUntilDone.
I created a subclass of UIView where I initialized how my loading-view should work and look like. (My view appeared and slided in from the bottom with an nice animation).
I then added code that handled whether the loading-view should be visible or not in a subclass of UIViewController.
I then let all my viewcontrollers be an subclass of my new viewcontrollerclass which made it possible for me to do:
[self showloadingMessage:#"loading..."];
in all my viewcontrollers...