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...
Related
I've got a view controller whose view contains a UIImageView that does animation:
//AnimationViewController::ViewDidLoad event:
var ctlAnimations = new UIImageView();
ctlAnimations.AnimationImages = list.ToArray(); //<--list contains the UIImages
ctlAnimations.AnimationDuration = 1.0 * list.Count;
ctlAnimations.StartAnimating();
this.Add(ctlAnimations);
This works perfectly: when I push AnimationViewController onto the navigation stack, it displays and animates the UIImage.
But now I need to show AnimationViewController with a custom animated transition:
var transition = CATransition.CreateAnimation ();
transition.Duration = 0.3f;
transition.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut);
transition.Type = CATransition.TransitionFade;
this.View.Layer.AddAnimation (transition, "fade");
//viewController is being pushed with animated=false, because we have a custom animation
base.PushViewController (viewController, false);
this.View.Layer.RemoveAnimation("fade");
This also works perfectly, in that the new View transitions into place using the specified custom animation.
But when I push AnimationViewController onto the stack using an animated transition, it displays but the animation doesn't run. Instead, it shows the first frame of the animation (the first image in the list), and doesn't run it.
So, something about the transition is breaking the ability to animate a UIImage in the new view controller, but I simply can't figure out what to do about it.
Update: I've noticed that if I tap the NavigationController's back button, but then move off of the back button and let go (so I don't actually go back), the animation starts playing!
PushViewController works like this: Over the current view controller the next view controller is placed you can say pushed onto the stack. From Apple docs its clear that either you need to push view controllers either with animation or without.
Work around:
Set the frame of the next view controller's view's x position beyond
the screen's right
Suppose width of the screen is 320, then set the x position of next
view as 320.
Add the next view as subview to the existing one.
Now do your custom animation.
Another work around:(a bit more overhead though)
Take a snapshot programmatically of current view.
Add the snapshot image as the initial view of next view controller.
Now push view controller without animation. (User will still see the old view)
In viewDidAppear of new view controller start your custom animation.
[I have to warn you that this method of taking snapshot might give you a small delay in older devices. Newer devices are pretty fast enough you wont see any lag]
Let me know if any issues in case you are implementing any of these solutions.
Try putting the animating bit in ViewDidAppear rather than ViewDidLoad. Also, try using breakpoints and NSLogs to follow what happens after the animation, starting with the ViewDidLoad and ViewDidAppear. Try having the animation repeat forever so you can see if it has ever been animating or not.
I'm very curious what the culprit is here as well. Why is the animation not displaying correctly in some cases?
My theory is that you have placed animation code in viewWillAppear rather than viewDidAppear. Animation code does not run properly when placed in WILL or SHOULD methods.
Can you please post back what caused the issue?
Suspicion #1
I am betting that your code is not being called because it is in ViewDidLoad. I believe you are creating a customized view stack, that means you need to be using the ChildViewController methods from Cocoa.
I am unfamiliar with MonoTouch (I only write pure CocoaTouch), so this might not be 100% correct
I would be consoling out your viewDidLoad and viewDidAppear methods and absolutely make sure they are being called. It is my suspicion that viewDidLoad IS NOT. And this is causing viewDidLoad to not be called on the UIImageView.
In your code you probably need the equivalent of (from objective-c):
[self addChildViewController:viewController];
// OR?
[base addChildViewController:viewController];
This tells the 'parent' viewController that the Child has been made visible, so call the viewDidLoad/Appear and Unload/Disappear methods when appropriate. This might not exist in MonoTouch, or the Push methods might not be fully implemented, so you might need to do some hacky (bad) stuff like manually calling the viewDidLoad method manually.
Suspicion #2
It could also be that your 'list' variable (the one holding the images) is nil. If that happened the animation would not run. OR maybe it has something to do with the duration of your animation, try to set it to whatever would make it repeat forever. Be sure it isn't running REAL FAST somehow and you are just missing it.
begin philosophical musing
Either that or start learning actual Cocoa development :) Not meant as a flame, but definitely meant seriously, you are going to run into problems trying to develop applications through translation layers (custom language-bridges meant to get around writing the base language of a framework/application/platform).
Titanium/MonoTouch/PhoneGap will never produce as robust or high-quality applications as real Objective-C. And besides that, once you learn Cocoa it will change how you write everything else, and I doubt you will want to go back. As the wonderful website of the same name says, 'Cocoa is my girlfriend'
Let me tell the something about UI in IOS. In IOS access to the UI Elements is limited to a single thread.
The single thread would always be the mainThread except in the case when you are running an animation.
Hence when u are performing number of animation at the same instance you have to use
beginAnimation.
setFrame (or) some methods that changes state of UI element.
Repeat step2 for all those objects u are scheduling to animate.
comitAnimations to perform all animations at once. (using comit animations ensure all the animations are performed on same thread)
So, I guess here is what happening in ur case.
Viewcontroller started an animation to push the view controller into stack.
Image view started another animation before finishing first animation.
Look at the links to get a clear idea link1 and link2.
Well Let's get into the solution
Add an ivar and retained property named ctlAnimations to your class
In ViewDidLoad (or) ViewDidAppear
self.ctlAnimations = new UIImageView();
ctlAnimations.image=(UIImage*)[list.toArray() objectAtIndex:0];
this.Add(ctlAnimations);
[self performSelector:#selector(startAnimatingImage) afterDelay:0.1];
Create a private method named startAnimatingImage with below code
self.ctlAnimations.AnimationImages = list.ToArray();
ctlAnimations.AnimationDuration = 1.0 * list.Count;
ctlAnimations.StartAnimating();
On a brief we just shown first image for a moment when the firstanimation is taken place and then we delayed animation start for 1 second so that it performs after first animation and then starts animating the image.
Go ahead and give it a try
There is the concept for the Main thread. So UIAnimation are actually works on Main Thread and at the same time may be this was happen that another task is performing on the same Main Thread.So that was a case like IOS will give preference according to processes id of each process at a time and for the solution you need to make background thread for the same operations.
Well, I never figured out what the problem was here, but it also turned out that there was some sort of memory leak in UIImage such that on some phones my code was crashing.
So I rewrote it to do the animation manually. Now I set a timer and keep a frame index variable, and every time the timer rings I change the image myself. This amounts to the same thing, and it turns out it fixes this problem.
I am dealing with and odd bug, I have created a custom segmented control that comprises of uibuttons (the controller itself is a subclass of UIView. I change the button's images for the selected and normal control state to indicate selection.
In my parent view in IB I have set the identity of the view to my custom class. The issue is I have a tableView, when I scroll it and the scroll animation the button inside my custom view does not change its image immediately, instead it waits for the table to finish scrolling and then it updates. Any ideas?
You're not giving a lot of detail, but it sounds as if you're trying to do too much work within the current run loop. If so then the answer will be relevant:
iOS waits until your code has finished executing before it does any display updating. So any updates are, in effect, queued until your current chunk of code is completed. To get around this, the most common trick is to allow the current run loop to end and the pick up execution again after a very short delay.
So, in your case, call the code to update your custom segment control. And then, instead of calling the code to update your table, park that code in another method and call that method using [self performSelector: #selector(delayedUpdate) withObject: nil afterDelay:0.1];
To illustrate how you might change code:
BEFORE
[self updateSegmentController];
[self updateTableScrollPosition];
return;
AFTER
[self updateSegmentController];
[self performSelector: #selector(updateTableScrollPosition) withObject: nil afterDelay: 0.1];
return;
Often a delay of 0.0 works just fine; it still achieves the effect of letting the current run loop complete and the display update before calling the nominated method. Sometimes adding an extra delay improves your animation appearance.
I have a two views, 1st is a simple view with some introduction about usage and by click of a button it opens the main view. The main view has many images and two customized tables with rows consists of text and image, thus the creation of the main view is quite slow. The profiler shows most of the time is consumed by imageIO (decode_mcu, png_read_filter_row, pmap_copy_page and decompress_onepass)
I want to optimize by creating the main view immediately after the 1st view is loaded, and when I click the button, it simply set that view to visible or bring that view to front.
I tried to alloc & init the main view in the first view's viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
rootVC = [[RootViewController alloc] initWithNibName:#"ViewController" bundle:nil];
rootVC.delegate = self;
}
and do this in the button's action method
- (IBAction)buttonUp:(id)sender {
[self.view addSubview: rootVC.view];
}
But this is not working, the loading still takes very long. How could I sacrifice some memory to make better user experience?
Thanks
Leo
You should:
Use Instruments to see where your code is spending the most time, and optimize there. It may not be where you think.
If you are using a UITableView, learn how to create UITableViewCells on demand (rather than preloading a large number) and recycle instances (rather than recreating them).
Only if Instruments points to this as the bottleneck: try loading/decompressing images in on a background queue, then updating the UIImageView (or whatever) on the main queue after loading.
In iOS 4.x or later, you can do this:
NSString *path = ...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
// This is happening in the background thread.
UIImage *image = [UIImage imageWithContentsOfFile:path];
dispatch_async(dispatch_get_main_queue(), ^{
// This is happening on the main thread; all UI updates must happen here.
imageView.image = image;
});
});
P.S. By the way, UIViewController doesn't load the view on creation, but on demand. So based on your code above, the expression rootVC.view is probably triggering the view to load. If you want to force it to load earlier, just put rootVC.view somewhere else.
UIViewController tries to load its view lazily, i.e. the view isn't loaded until it's actually needed. If you really want to force your view controller to load its view, you just need to access its view property, e.g. rootVC.view.
However, you really shouldn't need to do that. Table views, for example, only create the cells that are visible on screen, so it's unlikely that the number of rows is significantly impacting the time to load the view. One caveat here is that there are some things that can really slow down a table view. For example, if you're using variable-height cells in a table with a lot of rows, that can really slow down table creation. Try not to do that.
#benzado gives some good pointers for improving performance. Trying to work against Cocoa-Touch is always a recipe for pain, so try instead to figure out why your view is taking so long to load. Where is your app spending all that time? What can you do to reduce that load?
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".
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];
}