My app runs some tasks in the background, then in the end shows an alert asking the user if she wants to send the result by email.
In other places of the app I use the MFMailComposeViewController in the same way as it should be (shown e.g. here). Here however, as I am in the completion block of a potentially long running task, I have no reference to a UIViewController instance I could use for the following calls:
...
mailSendingController.mailComposeDelegate = self;
[self presentModalViewController:mailSendingController animated:YES];
I could probably keep the reference to the VC that invoked the task, but it might be dismissed before the task finishes.
What can I do? Create a fake hidden VC just to serve as the basis of the MFMailComposeViewController? Is there a way to show MFMailComposeViewController without an underlying VC? Use somehow the VC that is currently on top of the stack?...
Any ideas are welcome.
First of all, presentModalViewController:animated: is deprecated as of iOS6 [1] you should use presentViewController:animated:completion: [2]
I would just get the app's root view controller and then use that to present the modal view controller. That way it will be on top no matter what and you know the root view controller is always there.
Look at this question for some hints on accessing the app's root view controller.
Also, if you are going to do GUI stuff make sure you go back to the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
// Present the VC here
});
Good luck!
use ]
[UIApplication sharedApplication].keyWindow.rootViewController presentViewController: mailSendingController]
Related
for my app i want the app to animate loading progress (loading local CoreData, and syncing with game center) before entering main page.
So i created a LoadingView as my rootview, in the ViewDidLoad(), after the local loading and game center loading finishes, i then push to the main view.
I'm pretty sure it's the wrong approach since i'm getting this warning:
Presenting view controllers on detached view controllers is discouraged
Could you let me know what's a good practice?
I would suggest you create an initial root view which is very simple with a background and present your progress using a HUD. I use this one:
https://github.com/jdg/MBProgressHUD
When you have initialised everything, drop the HUD and replace the root view controller with a new one you load using storyboard. I would suggest you add a method to the app delegate you can call which does the switch for you.
You are then off and running with the new root. To change the root you use the following in your app delegate instance.
self.newController = <load with storyboard>;
self.window.rootViewController = self.newController;
[self.window makeKeyAndVisible];
I would use NSNotificationCenter to create an observer that hears an event when loading finishes:
You would place the observer in ViewDidLoad, more then likely, in order to remove the UIView that you instantiated when your CoreData load began. You will most likely need to construct a spinner view yourself. I usually use QuartzCore to do this and make sure it matches the rest of my app.
Because your core competency is not clear from your post, here is a NSHipster article that discusses the use of this concept: http://nshipster.com/nsnotification-and-nsnotificationcenter/
set your second view controller as root view controller. so you will not get any warning ect.
[UIApplication sharedApplication]delegate] window].rootviewController=secondViewCont;
only problem is there will not be any page navigation animation. You can observe with delegate or with notification to your loading process.
In my app, I am jumping navigation a bit, here is a rough outline of what my navigation does
Login -> Main Screen -> Settings
From settings, I want a logout that goes back to the login page. I do this like this:
UIViewController* requireController = [[[self navigationController] viewControllers] objectAtIndex:0];
[[self navigationController] popToViewController:requireController animated:YES];
This does return me to the login page. However, I would like to release some things in the Main Screen, for example I have a timer that runs a task every 10 seconds, which continues to run (I can see it in the logs). viewDidUnload obviously no longer gets called as of iOS6. viewDidDisapear also is not quite right because that will also get called when going into settings.
What should I do here to get rid of some tasks in my main view controller?
You can use the popToRootViewControllerAnimated: method to directly jump to the root view controller, in your case the Login controller. If you are not retaining the Main Screen or Settings controllers anywhere in your code, then moving to root view controller will invoke the dealloc method on the view controllers getting removed from navigation stack.
You can override the dealloc in your main screen controller and release the resources. Remember, if you are using ARC do not make a call to [super dealloc] as it will give error. But if you are managing memory manually make sure you add the call at the end of dealloc.
Hope that helps!
Set up a protocol on your logout view controller. Before you pop back to the main, call some method to reset the state of that view controller using the protocol (delegate) you created. On the main VC implement that method. AFTER telling the delegate to do whatever, then pop the login VC.
Here are the basics of this pattern.
http://iosdevelopertips.com/objective-c/the-basics-of-protocols-and-delegates.html
I would either use NSNotificationCenter or use the - (void)viewDidDisappear:(BOOL)animated method of UIViewController.
The main view of my app is a UIImagePickerController camera view.
When the app becomes active (in didBecomeActive), I present a modal view controller that shows some settings generated from a network request. (Note that for debugging purposes, I took the network request out and am currently just showing a dummy view)
The modal view animates in smoothly, but after loading it freezes for 3 seconds then responds normally. After dismissing the view (also animates smoothly), my image picker controller pauses for 2 seconds then resumes normally.
I have removed all functionality from the modal view controller to make sure there was no operations clogging the main thread. I am presenting the most basic of controllers, and still get the choppy ui. I would suspect that this is from my presenting view controller calling viewDidLoad/Unload or something similar, but my search did not give me any information on what delegate methods are called in the presenting view controller when a modal view is shown.
My problem can be solved by answering:
What delegate methods are called in the presenter when a modal view is shown?
(If any ^) How can I not call those methods, or make them run smoother?
What common pitfalls are associated with modal view controllers?
This is probably because you are making a lot of processing in the main thread (usually when UI stops, it's because main thread processing). Try to provide us some code, specifically the one you think is the most heavy processing code! Sorry about my poor english :P!
Try dispatching most heavy code to another thread with
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//your heavy code here =)
});
Regards,
Lucas
There are multiple methods invoked. ViewDidLoad ViewWillAppear ViewDidAppear ViewWillDisappear ViewDidDisappear. Check all of those methods. Also, check any subviews you have created and see if they are doing any thing on their thread involving image loading in the methods i stated. Also does this occur in the simulator as well as a test device?
ModalViewControllers do not have too many pitfalls but understanding how many views are allocated on things like navigation stacks and how many views you have on top of each other. When you get rid of the modal viewcontroller do you call dismissviewcontroller?
One thing that might be a contributor to some slight lag is reloading the same viewController from scratch each time.
BProfileTableViewController * _profileViewController = [[UIStoryboard storyboardWithName:#"Profile" bundle:[NSBundle chatUIBundle]] instantiateInitialViewController];
UINavigationController * profileNavigationController = [[UINavigationController alloc] initWithRootViewController:profileViewController];
[self.navigationController presentViewController:profileNavigationController animated:YES completion:nil];
You can see here that if this is on a tableView click then each time the app needs to create the viewController again. If instead we just reuse the view then it gets rid of some of that lag.
Add this in the header file
BProfileTableViewController * _profileView;
Then the modal view load code changes to:
// Open the users profile
if (!_profileView) {
_profileView = [[UIStoryboard storyboardWithName:#"Profile" bundle:[NSBundle chatUIBundle]] instantiateInitialViewController];
}
UINavigationController * profileNavigationController = [[UINavigationController alloc] initWithRootViewController:_profileView];
[self.navigationController presentViewController:profileNavigationController animated:YES completion:nil];
This means we are just reloading the view instead of recreating the view
Try
layer.masksToBounds = true
I have application with navigationController and some root viewController in it's stack. I need a way to reset application's state to default, i.e. when there is only navigationController and it's root viewController from any moment in my application. For example, I make some background data request to backend and get response, which says that I have to logout user immediately. In that moment, let's assume, user has opened a lot of other viewControllers in navigationController stack and maybe some presented viewControllers with their own navigationControllers. So what I need is a way to pull user back from wherever he is to the root viewController of application.
I'm aware of the fact that this is a bad user experience, but it's definitely better than crashing app, and anyway this should be a rare situation.
I don't think that creating a chain reaction between all viewControllers that will one-by-one dismiss them until it reaches my root viewController is a good idea for me. That's too much of identical code in every VC, and I don't want to create subclass of UIViewController and subclass all of my VCs from it to have this done.
Any suggestions?
You can use [self.navigationController popToRootViewControllerAnimated:YES]; to go to your main rootview controller from any view of your app.
Simplified question: Is there any way to restart the navigationController of an application?. I'm trying to force the application to get his initial appearance.
Long explanation
I've a pet project in iOS and I have a weird problem with the interface that I'd like to solve. I'd like to understand also the mechanics behind this behavior.
I've a simple welcome view, wich shows the splash screen of the application. After that, thread goes to sleep state for 1.5 seconds.
[NSThread sleepForTimeInterval: 1.5];
Then, I'm showing an advertisement view:
AdController *ad = [[AdController alloc] initWithNibName:nil bundle:nil];
[self.navigationController presentModalViewController: ad animated:YES];
[ad release];
And that's all the logic behind. After that, other controllers are pushed without incidence. I want to achieve that, if at any moment the user makes the application go to background (pushing the iPhone/iPad button) then all the controllers must disappear from the stack via pop. In order to get it I'm using applicationDidBecomeActive event from the delegate. The code is the following:
[self.navigationController dismissModalViewControllerAnimated:NO];
[self.navigationController popToRootViewControllerAnimated:YES];
This is driving to some weird visual behaviours. Depending of the moment that the user choose to push de button the transition to the first view is visible. In other cases the ad view is still present, so it is dismissed and then appears the splash screen.
It will be great if there is some way to reset this first controller (splash screen), in order to get all the transitions working as the first time. I've thought about pop it from the navigation controller and the reload another one, allocating again, but it seems a bit complicated.
Is there any simple way to achieve that?
Important Edit: If the user forces repeatedly the application to go background then these exceptions are thrown:
nested pop animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
The easiest way that I've found is to add to the plist file a new row with key "Application does not run in background" and with value YES.
Forces the application to be completely closed and unloaded from memory when the user pushes the button.