I have a navigation controller, its root view controller is of type CollectionViewControllerA. Upon selecting an item, I have a fade out and expanding animation followed by a call to push a second view controller of type CollectionVewControllerB onto the stack:
CollectionViewControllerB *b = ... // construction of the view controller to be pushed
[UIView animateWithDuration:.3
animations:^{
self.collectionView.transform = CGAffineTransformMakeScale(1.5, 1.5);
self.collectionView.alpha = 0;
}
completion:^(BOOL s){
[self.navigationController pushViewController:b animated:NO];
}];
I pop the view controller in a similar way
[UIView animateWithDuration:.3
animations:^{
self.collectionView.transform = CGAffineTransformMakeScale(.3, .3);
self.collectionView.alpha = 0;
}
completion:^(BOOL s){
[self.navigationController popViewControllerAnimated:NO];
}];
The problem here is that the app crashes when popping the view controller. Reason:
*** -[CollectionViewControllerB scrollViewDidScroll:]: message sent to deallocated instance
I understand that the problem is because the popped view controller is destroyed, but why does scrollViewDidScroll: get called in the first place? Nothing changes the contentOffset of the collectionView in code, and there is no user interaction either. Unless changing the transform property also triggers the method to get called?
CollectionViewControllerB implements scrollViewDidScroll: because I need to disable vertical scrolling.
Meanwhile I have a very very messy hack to prevent the crash, that is before the animation, I add
self.collectionView.delegate = nil;
This stops the method from getting called. But there has to be a better way.
Can anyone shed some light on why scrollViewDidScroll: is called and how it can be stopped?
It seems like the only way to solve the problem is what I have already done... setting the delegate to nil before the animation.
self.collectionView.delegate = nil;
Hopefully this helps someone else in the future.
Set self.automaticallyAdjustsScrollViewInsets = NO; inside your view controller.
I had a problem similar to this, and found that on navigating away from the page, the contentOffset was changing by 20 every time.
I found that setting this property inside my view controller stopped this changing, and therefor scrollViewDidScroll was no longer being called. Turns out the view controller automatically adjusts content insets for changes in status bar, nav bar etc.. even when you navigate away.
I think this is a better solution and proper explanation as to why the scrolling method was being called.
You can try this in your controller .
[self setEdgesForExtendedLayout:UIRectEdgeNone];
Hope this would help you.
Related
I am working on an iOS app. The root view controller contains a UINavigationController which wraps up the main contents of the app, and a footerViewController (audio player) that will compress the main content when it animates up into view.
I’m using auto layout to show and hide this footer like so:
_footerVisibleConstraints = [… #“V:|[navControllerView][footerView(==90)]|" …];
_footerHiddenConstraints = [… #“V:|[navControllerView][footerView(==0)]|" …];
Generally this works well. But I’m struggling with one issue. I have a situation where I need to push a new view on the UINavigationController stack and animate my footer into view the same time:
[self.view layoutIfNeeded];
[UIView animateWithDuration:1.5f animations:^{
[[self view] removeConstraints:_footerHiddenConstraints];
[[self view] addConstraints:_footVisibleConstraints];
[[self view] layoutIfNeeded];
}];
[navigationController pushViewController:newViewController animated:YES];
The problem in this situation is that newViewController is animating in snapped to it's final (compressed) state, and not beginning from the full starting height of the view. So there is a gap at the bottom while the footer animates in.
I’ve slowed down the animation and posted a video here to demonstrate what I am describing.
Also, notice how when I pop back to the root view controller the content in the UINavigationController isn’t compressed either.
So, can someone explain to me what’s going on here? Is there a way to accomplish what I am after?
Just add a variable to the .h of your VC to stipulate whether the footer needs to open or not. Then add the footer animation to the didAppear method with a check on the variable. This will result in performing the actions in the order you want them to happen.
If you want both animations to happen at the same time you will need to subclass a segue and add a custom animation.
So, I want to do some basic animations of labels and later views.
I have a label, I'm trying to get it to move when a view loads, so I call the following method at the end of viewDidLoad:
- (void)animateView {
NSLog(#"animateView");
[UIView animateWithDuration:20 animations:^{
// set new position of label which it will animate to
self.dcFirstRunDaysLabel.frame = CGRectMake(20,320,280,215);
}];
}
Instead of animating, the label appears in position.
I've tried every tutorial and read through the docs. I get no errors.
Any thoughts?
Cheers.
Try calling your animateView method in viewDidAppear. Because in viewDidLoad your view isn't visible yet.
viewDidLoad:
Called after the controller’s view is loaded into memory.
viewDidAppear:
Notifies the view controller that its view was added to a view hierarchy.
I'm switching the rootViewController of the AppDelegate's window at some point of the app lifecycle. I'm trying to animate such switch this way:
[UIView transitionFromView:self.window.rootViewController.view
toView:self.otherViewController.view
duration:0.65f
options:UIViewAnimationOptionTransitionFlipFromRight
completion:^(BOOL finished){
self.window.rootViewController = self.otherViewController;
}];
Animation is performed but it looks like the second view's height does not fill the screen height, and it suddenly fits the whole screen height once the animation has finished. I hope I've explained properly... what could be happening?
Thanks
EDIT: I've noticed this behavior appears when self.otherViewController.view is a MMDrawerController. I tested the code transitioning from a UINavigationController to another UINavigationController and nothing strange is shown... Has anybody experienced this?
We may have come up with a solution here:
https://github.com/mutualmobile/MMDrawerController/issues/48
my app consists of a table view controller and a view controller. when i press a cell in the table view, the toolbar at that view slips with animation down outside the screen and when i'm in the view controller and press back, the toolbar bar slips upwards to it's original position. My problem is, i figured out a bug that when i'm in the view controller and press the home button to exit the app and then come back. the app resumes where i left but when i go back to the table view, the toolbar shifts upwards beyond it's original position. the sliding of the toolbar works fine when i'm in the app before exiting. so there's like something is being called to reset the toolbar to it's origin and thus adding the additional y-axis point to shift more upwards. does anybody know what are those methods?
Code:
i have this in the viewWillAppear method of the view controller:
[UIView animateWithDuration:0.7 animations:^{
self.navigationController.toolbar.center = CGPointMake(self.navigationController.toolbar.center.x, self.navigationController.toolbar.center.y + self.navigationController.toolbar.frame.size.height);
} completion:^(BOOL finished){
self.navigationController.toolbar.hidden = YES;
}];
and in the same view when it needs to disappear i added this in the viewWillDisappear:
[[self.navigationController toolbar] setHidden:NO];
[UIView animateWithDuration:1 animations:^{
self.navigationController.toolbar.center = CGPointMake(self.navigationController.toolbar.center.x, self.navigationController.toolbar.center.y - self.navigationController.toolbar.frame.size.height);
} completion:^(BOOL finished){
}];
i tried this as another way to animate the hiding of the toolbar but there is no animation:
- (void) viewWillAppear:(BOOL)animated
{
[self.picker setHidden:YES];
[self.navigationController setToolbarHidden:YES animated:YES];
}
- (void) viewWillDisappear:(BOOL)animated
{
[self.navigationController setToolbarHidden:NO animated:YES];
[self.course setValue:self.nameTextField.text forKey:#"courseName"];
[self.course setValue:[NSNumber numberWithInt:[self.creditsTextfield.text integerValue]] forKey:#"courseCredits"];
[self.course setValue:[NSNumber numberWithInt:[self.chaptersTextfield.text integerValue]] forKey:#"courseChapters"];
[self.course setValue:self.gradeTextfield.text forKey:#"courseGrade"];
}
For one thing, you should use frame instead of center, but replace the viewWillDisappear animation line with this:
self.navigationController.toolbar.frame = CGRectMake(0,0,self.navigationController.toolbar.frame.size.width, self.navigationController.toolbar.frame.size.height);
and tell me what happens...
That should fix your problem.
Since you are using the toolbar belonging to a UINavigationController (and not a standalone UIToolbar instantiated and added to a UIView by your own controller), it's better to use the methods that UINavigationController exposes, because you don't know how it manages and move its UIToolbar.
Try to put just this in viewWillAppear:, instead of the entire animation block
[self.navigationController setToolbarHidden:NO animated:YES];
and this in viewWillDisappear:
[self.navigationController setToolbarHidden:YES animated:YES];
i fixed it! i aded the following lines of code in viewWillDisappear:
self.navigationController.toolbar.center = CGPointMake(self.navigationController.toolbar.center.x, 458);
self.navigationController.toolbar.center = CGPointMake(self.navigationController.toolbar.center.x, self.navigationController.toolbar.center.y + self.navigationController.toolbar.frame.size.height);
since the problem seems that when the app goes background then foreground the toolbar resets to it's original position and thus after navigating back to the table view the toolbar is shifted beyond it's original position. therefore the first line i added resets the toolbar to it's original position while still hidden then shift it down. after that the block of animation is done. i did this so that the animation works on the following cases:
1. the user enters the detail view from the table view then goes back to the tableview without exiting the app.
2. the user enters the detail view from the tableview then exits the app and then resume the app and goes back to the table view.
I feel like I'm missing something obvious.
I've got a custom segue class. It does some animation. It works just fine if the current orientation is the same as the nib's orientation, but I can't make it work if the current orientation doesn't match the nib (ie current orientation is landscape but nib is portrait).
The issue is that the destination view controller's frame and orientation is wrong until viewDidAppear. But viewDidAppear is too late. Any attempt to do animation inside the custom segue must happen before viewDidAppear.
Oddly, viewDidLoad and viewWillAppear report the correct self.interfaceOrientation (while unfortunately reporting the wrong frame), but any attempt to draw in these methods will draw at the wrong orientation.
Anyone want to call me an idiot and point out the obvious mistake I'm making? I would greatly appreciate.
To answer my own question, I ultimately gave up on custom segues and opted for using a custom container controller. This doesn't get around the problem of view controllers not knowing their proper frame until after appearing, but did simplify the problem.
A more direct answer to my question is that it's the responsibility of the container controller (or presenting controller) to change the presented controller's frame. And the "best" way to do that, I've found, is to use the presenting/container controller's bounds as a starting point. The presenting/container's frame won't accurately reflect changes in orientation.
As an example in code, from my custom container controller:
- (void)slideToEdit {
// add edit to container and prep home for removal from container
[self.home willMoveToParentViewController:nil];
[self addChildViewController:self.edit];
// the vital frame setting <- this is the bit that solves the problem
self.edit.view.frame = self.view.bounds;
// prep the animation
__block CGRect frame = self.edit.view.frame;
frame.origin.x += frame.size.width;
self.edit.view.frame = frame;
// animate
[self transitionFromViewController:self.home toViewController:self.edit
duration:SLIDE_SPEED options:UIViewAnimationCurveLinear animations:^{
// slide animation
frame.origin.x -= frame.size.width;
self.edit.view.frame = frame;
} completion:^(BOOL done) {
[self.home.view removeFromSuperview];
// finalise container heirarchy
[self.home removeFromParentViewController];
[self.edit didMoveToParentViewController:self];
}];
}
This doesn't feel ideal, in that having to manually modify the frame seems wrong when it could be inferred from the nib + current orientation. But this at least works.