Is there a way to get the view controller i'm transitioning out from inside :
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
When I'm using navigationController.topViewController I'm getting the one I'm moving into.
Using the navigationController.viewControllers can suffice in case I'm getting deeper in the navigation system (cause I can look for the previous controller inside the array), but if I move "outside", this won't work, can the view controller I need is no longer there.
Any general way of doing it ?
I need it so I can add to it a subview just during the animation, then of course it's gone.
Thanks
if you want to find a particular ViewController from array of navigation controller then try this.........
for(UIViewController * VC in [self.navigationController viewControllers])
{
if([VC isKindOfClass:[ViewController class]])
{
//YOUR CODE .......
}
}
Enjoy coding.........
I took a look a UINavigationControllerDelegate, and I'm pretty sure that if you try to implement this using the delegate, you're going to go through a lot of trouble to get this working right, when you could do it more easily and cleanly by using a subclass ofUINavigationController that overrides:
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (UIViewController *)popViewControllerAnimated:(BOOL)animated;
and if you plan to use them
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated;
and
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated;
You will also need to store the view to be added to the transitioning view, in the navigation controller, as an instance variable. In the example I will call it viewForTransition.
an example of one of the overridden methods:
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Submit an animation which adds the viewForTransitioning to the view about to be pushed off screen.
// Upon completion, the animation will remove the viewForTransitioning from it's superview.
// You also need to figure out the proper duration and delay and store them in constants or as instance variables.
[UIView animateWithDuration:theDuration delay:theDelay options:UIViewAnimationOptionCurveLinear animations:^{
[self.topViewController.view addSubview:self.viewForTransition];
} completion:^{
[self.viewForTransition removeFromSuperview];
}];
// after submitting the animation, call super.
// if this makes the view appear for a few moments and then disappear before animating to the next view controller, you might try calling super after calling addSubview.
// if that doesn't work you can try storing the old top view controller in an instance variable so that it can be accessed by the delegate when navigationController:willShowViewController:animated: is called.
[super pushViewController:viewController animated:animated];
}
I'm not totally sure that this will do what you want. But it's worth a try.
Related
I'm using a custom-made container view controller in my dictionary app. Basically, the container view controller contains a custom navigation bar on top (NOT a UINavigationBar--just a UIView with back and forward UIButtons, a UISearchBar, and a bookmark UIButton at the right), and a tab bar controller at the bottom.
My problem is this: I use the back and forward buttons to push and pop view controllers in one of the tabs (a UINavigationController) so the user can navigate through the dictionary browsing history. However, if I press the back or forward buttons too fast, I get this message in the log pane and some of the screens don't appear at all:
Unbalanced calls to begin/end appearance transitions for
<DefinitionViewController: 0x8e5d230>.
Looking around StackOverflow, I understood that this is because clicking on the back or forward buttons too fast calls the push/pop methods of the UINavigatonController in the active tab, but it does not let the animation finish. https://stackoverflow.com/a/17440074/855680
Pushing or popping view controllers without the animations solves the problem, but I do want to keep the animations. How can I approach this problem? I looked at the UINavigationController class reference to see if there are any delegate methods or properties that indicate that it's in the middle of an animation, but there doesn't seem to be any.
Fixed it myself. The solution was to create a property in my container view controller which indicates whether the UINavigationController transition animations are still happening:
#property (nonatomic, getter = isStillAnimatingTransition) BOOL stillAnimatingTransition;
Now, for all the UIViewController classes that I push into the UINavigationController, I set this flag to YES or NO in each of the view controllers' viewWillDisappear and viewDidAppear methods, like this:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.containerViewController.stillAnimatingTransition = NO;
}
- (void)viewWillDisappear:(BOOL)animated
{
self.containerViewController.stillAnimatingTransition = YES;
[super viewWillDisappear:animated];
}
And my container view controller only ever allows the execution of the back and forward buttons if the animation flag is set to NO, like this:
- (void)backButtonClicked
{
if (!self.isStillAnimatingTransition) {
// Do whatever.
}
}
- (void)forwardButtonClicked
{
if (!self.isStillAnimatingTransition) {
// Do whatever.
}
}
Maybe you can take advantage of the UINavigationControllerDelegate class and handle the events there.
In your main class that holds the navigation controller, set the delegate to yourself and handle the interactions there.
i.e. in the .h file:
#interface yourClass : UIViewController <UINavigationControllerDelegate> {
UINavigationController *content;
}
and then in the .m file:
content = [[UINavigationController alloc] initWithRootViewController:yourRootViewController];
content.delegate = self;
After that you can listen to the transition events via the following functions, and set your animation flags accordingly.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
stillAnimatingTransition = NO;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
stillAnimatingTransition = YES;
}
You can find more references about the delegate protocol from apple
https://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationControllerDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UINavigationControllerDelegate/navigationController:willShowViewController:animated:
to show a modal uiview out of my mainView I use:
[self presentModalViewController:myController animated:YES];
and in MyController I close that view with:
[self dismissModalViewControllerAnimated:YES];
But how can I know in the mainView that the modal was finished (to redraw my table)?
Currently I set a local variable to YES in my mainView after starting the modal view an react on viewWillAppear:
[self presentModalViewController:myController animated:YES];
_reloadTableData = YES;
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (_reloadTableData) {
_reloadTableData = NO;
[_tableView reloadData];
}
}
Is there a better way to do so ?
Generally speaking, it's not appropriate to dismiss the modal view by the modal view itself.
Instead, you should set your main view as the delegate of the modal view. When you modal view finishes its task, it can let its delegate know and let its delegate dismiss it. This is the very common so-called delegate design pattern in Objective-C.
btw, you may want to consult with some code samples to gain a better understanding of this delegate pattern. I suggest you take a look at one of Xcode's default templates - the Utility Application template. It has a very succinct and simple and straightforward delegate structure built inside.
I want to do some saving stuff when the user hits the back button on a navigation controller. Is this only possible by implementing
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
int index = [[self.navigationController.viewControllers] indexOfObject:[self.navigationController.visibleViewController]];
if(viewController == [[self.navigationController.viewControllers] objectAtIndex:index-1])
//saving code here
so the delegate gets called when it's about to show the previous view controller. Is there a more elegant way of knowing when the view controller will be popped?
and I can't use viewWillDisappear because there's a button that displays a UIImagePickerController, and I don't want the saving to be done then. Any thoughts?
Or
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
//insert your back button handling logic here
// let the pop happen
return YES;
}
You normally do things like that in the "viewWillDisappear:" method of a view controller.
Yes it also will activate if you are going forward, but you can flag that to let the method know if you meant to launch something else - and it's probably a good idea to save no matter what at that point anyway...
I have an event which calls a view to appear, but the -viewdidload event isn't appearing as expected each time it's called. Here's the method I use to call it...
[self presentModalViewController:addItemViewController animated:YES];
then inside the addItemViewController, the method is
- (void)viewDidLoad {
NSLog(#"alright, lad!");
}
To close the view, I have a button with the code
- (IBAction)cancel {
[self dismissModalViewControllerAnimated:YES];
}
the "alright, lad" log is shown the first time the view appears, but never again when it's launched. Is there a method I can use to let the app "forget" about the view? Or should I be using another load method? I tried loadView (I think) but that had a blank screen...
Thanks for any help!
viewDidLoad is only called when the view is first instantiated. If you're not recreating the view controller each time, you'll only get it called once (and called again if you get a memory warning, and the view is nil'd out). You probably want to use viewWillAppear: or viewDidAppear:.
Make sure you call the superclass in each of those methods, e.g.
- (void)viewWillAppear:(BOOL)animated {
NSLog(#"view appeared");
[super viewWillAppear:animated];
}
I have two view controllers in a tabbar which can both edit data. Therefore, I need to call a reload_data function whenever the user makes a switch on the tabbar. How can I catch the switch or the appearance of the viewcontroller. Somehow viewDidAppear is not called on a tabbar switch. And I do not want to use the tabbarController delegate for this, because several viewControllers are affected (and I cannot set them all as delegate). What is a good way to solve this?
e.g. this didn't work:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
[self reloadData];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
[self reloadData];
}
If you're using Interface Builder, make sure the class for the viewController your expecting to reload is defined (Select the ViewController in IB, then CMD-4, make sure class is defined to be the class you want viewWillAppear and viewDidAppear to be called in).
If you're not using IB, post your code for init/calling the viewController.