How can I detect the end of the animation of a modal view (when I do a dismiss)? (I'm talking about MFMailComposeViewController which is not created by myself...)
Thanks
Your modal view controller has a -viewDidDisappear: method that is automatically invoked whenever the view is removed from the screen. You can override this method in your modal view controller to do whatever you like.
Also, you may want to consider implementing the -viewDidAppear: method in the view controller whose view gets revealed by your modal view disappearing.
You can subclass MFMailComposeViewController and overload its -viewDidDisappear:.
#interface MyCtrler : MFMailComposeViewController
#end
#implementation MyCtrler
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// do anything you like
}
#end
I needed to do something after dismissing a modal view, and only when it is sure that the modal view is really gone (been completely dealloc-ed). So viewDidDisappear and its friends in the modal view were too early for me.
The easiest I found was to just delay my code with a NSTimer. When modal view calls its delegate and the delegate invokes removing the modal view, it also queues up the code to be run when the modal view is gone. The timing was something like 300ms or 400ms. (Is there a way to retrieve the actual timing from somewhere?)
Normally to be notified when an animation is complete you set a delegate by sending setAnimationDelegate: to the UIView class.
When a VC is dismissed using [someVC dismissModalViewControllerAnimated:YES] you can't set the animation delegate, but if you send NO instead and do your own animation of the VC's view you can set the delegate and be notified when the animation is completed.
Related
I have this method in my MainViewController:
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
NSLog(#"MAIN CONTAINER WILL ANIMATE");
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
And when I rotate, the NSLog shows up, and everything is perfect. However, if I present a modalViewController from my MainViewController, and then I rotate, the NSLog no longer appears, and my MainViewController never knows that the device got rotated, so when the modalView is dismissed, the interface is not adjusted for the rotation.
Any ideas as to why a modal view can prevent the parent from receiving rotation updates? Is this typical, or must there be something wrong with my setup?
And just to make sure, I tried presenting the modalViewController as a subview via [mainViewController.view addSubview:modalView.view], and the rotation updates took effect properly. It's only when I do [mainViewController presentModalViewController:modalViewController]; that the updates don't take effect.
Surely when a view controller is being presented modally, no other view controllers receive any messages at all. That is what modal means in this context.
The obvious solution would be to check the orientation in the viewWillAppear method of your modeless view controller.
You have to check the implementation of:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Overriden to allow any orientation.
return YES;
}
In both views.
You need to adjust your layout to the current interface orientation in viewWillAppear. Obviously, because the view is not visible, the animation functions will not be called while the modal view controller is up.
I have a view in my app that displays a UITableView. This view is created in a nib file and has a custom view controller. The UIViewController subclass for this view acts as the Datasource and Delegate for the UITableView.
My UITableView displays several rows based on my data. Then, the last row displays different text: "Add another...". If the last row is selected, I present a modal view controller (to allow the user to add more data). When I dismiss the modal view controller, I again see the original view (as expected) and all appears to be well. However, when I try to interact with this view, the app crashes.
From placing several NSLog() statements through the UIViewController (for the UITableView), I have determined that the -dealloc method is being called just after the modal view is dismissed. This explains the crash when I try to do something with that view. However, I have no idea why -dealloc is being called on this view controller.
To dismiss the modal view controller, I have:
[self dismissModalViewController:YES];
As the code in an IBAction method in the modal view controller's UIViewController. This action is tied to a cancel button in the corresponding nib file.
In addition, my understanding from the View Controller Programming Guide is that it's OK to dismiss the modal controller from within itself, but it's more robust to use delegates. I was initially using a delegate, but took the delegate out to simplify debugging. I just put the delegate back in to double-check, and the same behavior occurs when using delegates. The modal controller's action method calls is implemented as:
[[self delegate] myModalViewController:self didAddObject:obj];
The delegate implementation in the parent view controller is:
[self dismissModalViewController:YES]
If anyone has seen this before or has any suggestions of what could be happening or how to debug this, I would greatly appreciate it.
If -dealloc is being called, something is releasing the view controller. Try implementing -release in your view controller:
-(void)release {
NSLog(#"view controller released");
[super release];
}
so that you can use the debugger to inspect the call stack when this unexpected release message happens.
Its dangerous to call dismissModalViewController from the modal view controller itself (message will be forwarded to parent view controller), if you have not retained it elsewhere. Normally, the parent view controller is responsible for dismissing the modal view controller it presented.
How can I make a custom view in iOS which appears above the existing view,but smaller? It should be like UIAlertView, but taken from a .xib file. After user taps a certain button, the small view vanishes and the normal view appears.
If this is posiible, how can I do it? And.. if it's not hard for you, please, include code.
Thanks in advance!
I think what you're looking for is a modal view. Modal views make it easy to have a view take over the screen for a little while, then when they get dismissed have the background view resume where it left off without having to worry about who's on top or handling events in partially-obscured views.
Here is Apple's article describing it.
They key is to have the controller class for your view call [self presentModalViewController:popupController animated:YES]; where "popupController" is the view controller for the view you want to show up. When you're ready to make it go away, call [self dismissModalViewControllerAnimated: YES];
You can just use the addSubview: method on your UIWindow or your visible UIViewController's view to show the UIView, and you can hide/remove it later by calling removeFromSuperview on the presented UIView.
For custom animations in my app, I can use the setAnimationDidStopSelector: method to respond to the event that an animation has finished. Is there a similar kind of mechanism for detecting that the animation has finished for a standard View Controller animation transition for pushes and pops?
(i.e. [self.navigationController pushViewController:vc animated:YES])
I think you can try to overide the method:
- (void)viewDidAppear:(BOOL)animated
This method will be called after your view appeared
In the interface comment for the code:
- (void)viewWillAppear:(BOOL)animated; // Called when the view is about to made visible. Default does nothing
- (void)viewDidAppear:(BOOL)animated; // Called when the view has been fully transitioned onto the screen. Default does nothing
So I think that if you override the viewDidAppear and put your logic here, the code will be executed exactly after the transition finished
More in viewWillAppear and viewDidAppear
I'm looking for a way to slide the keyboard into view from the right, like what happens in the Contacts application when you edit a note.
My problem is that when I call [someTextView becomeFirstResponder] in viewWillAppear, the keyboard immediatly pops up with no animation. And when I call it in viewDidAppear, the view first slides in from the right (UINavigationController does the sliding), and then the keyboard slides in from the bottom.
Is it possible to have the keyboard slide in from the right, together with the view?
Solution
In iOS 7, calling becomeFirstResponder on _textView in viewDidLayoutSubviews works.
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[_textView becomeFirstResponder];
}
Note: Doing it in viewWillLayoutSubviews also works.
Explanation
Read the discussion in the docs for becomeFirstResponder.
You may call this method to make a responder object such as a view the first responder. However, you should only call it on that view if it is part of a view hierarchy. If the view’s window property holds a UIWindow object, it has been installed in a view hierarchy; if it returns nil, the view is detached from any hierarchy.
When using a navigation controller to push your custom view controller onscreen, self.view.window is still nil by the time either viewDidLoad or viewWillAppear: is called. So, _textView.window is also nil in the same methods, since _textView is a subview of self.view, i.e., they're both in the same window. No matter how you present your custom view controller, self.view.window (and thus _textView.window) is also nil in initWithNibName:bundle:. self.view.window is set by the time viewDidAppear: is called, but that's too late because by that time, the navigation controller has already completed the animation of pushing the view onscreen.
self.view.window is also set by the time either viewWillLayoutSubviews or viewDidLayoutSubviews is called and these methods are called before the push animation of the navigation controller begins. So, that's why it works when you do it in either of those methods.
Unfortunately, viewWillLayoutSubviews and viewDidLayoutSubviews get called a lot more than just on the initial navigation controller push. But, navigationController:willShowViewController: and willMoveToParentViewController: get called too soon (after viewDidLoad but before self.view.window is set) and navigationController:didShowViewController: and didMoveToParentViewController: get called too late (after the push animation).
The only other way I can think of doing it is to somehow observe the window property of _textView so that you get notified when it changes, but I'm not sure how to do that since window is readonly.
All you need to do is tell the text view in question to become the first responder in the viewDidLoad method of the view controller you're pushing onto the navigation stack:
override func viewDidLoad() {
super.viewDidLoad()
someTextView.becomeFirstResponder()
}
This works in iOS 8. The keyboard slides in from the right along with the view.
In iOS 7 (or any version before) you can make a simple thing in loadView, viewDidLoad or viewWillAppear
[yourTextView performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0.0];
In this case you will get left-to-right appearance of the keyboard aligned with the motion of pushing view controller.
For iOS 7 I've found the following solution to work the best for me:
-Import UIResponder-KeyboardCache to your project.
-Add [UIResponder cacheKeyboard:YES]; to the viewDidLoad of the view before the keyboard view. It might be better to do this immediately when the application loads or during a time convenient when you can afford it (during an HTTP request, for example). In most cases, simply in the view before is sufficient.
-Add the following to the viewDidLoad of the keyboard view.
dispatch_async(dispatch_get_main_queue(), ^{
[_textField becomeFirstResponder];
});
To explain, this will preload the keyboard view, which will remove the delay from the first call of the keyboard view. Calling becomeFirstResponder on the text field in the main queue causes it to slide in with the view instead of animating upward before the view slides in.
You could try sending the becomeFirstResponder message to the new view controller before you push it onto the stack. For example:
-(void)functionWhereYouPushTheNewViewController {
yourNewViewController *newVC = [[yourNewViewController alloc] init];
[newVC.yourTextView becomeFirstResponder];
[self.navigationController pushViewController:newVC animated:YES];
}
I have found that changing animations on things like they keyboard is pretty tough though, and if you read the Human Interface Guidelines Apple makes it pretty clear that they want certain things to act in certain ways, all the time. There are ways to change the behaviors of certain animations but they often involve undocumented API calls and are grounds for rejection from the app store. It would be a violation of HIG to have pushed views slide up from the bottom, for example.
Hope this helps.