We have a viewcontroller which has two modes of view - list and map.
Earlier we were doing the toggle by using our custom tabcontroller. But now, as we didnt want to show tabs and do some more things, we are doing it by adding childviewcontrollers.
But there are few subviews which are common in both child view, like filters.
So, this is how we are doing it now:
- (void)switchView:(UIButton *)sender {
if(!self.mapVisible) {
self.mapVisible = YES;
_mapViewController.view.frame = self.view.bounds;
[self addChildViewController:_mapViewController];
[_listViewController willMoveToParentViewController:nil];
[self transitionFromViewController:_listViewController
toViewController:_mapViewController
duration:0.7
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:nil
completion:^(BOOL done){
[_mapViewController didMoveToParentViewController:self];
[_listViewController removeFromParentViewController];
[self setupFooterAfterFlip];
}];
} else {
self.mapVisible = NO;
_listViewController.view.frame = self.view.bounds;
[self addChildViewController:_listViewController];
[_mapViewController willMoveToParentViewController:nil];
[self transitionFromViewController:_mapViewController
toViewController:_listViewController
duration:0.7
options:UIViewAnimationOptionTransitionFlipFromRight
animations:nil
completion:^(BOOL done){
[_listViewController didMoveToParentViewController:self];
[_mapViewController removeFromParentViewController];
[self setupFooterAfterFlip];
}];
}
}
We call the switchview method every time we toggle between views. As you can see, we are setting up the footer everytime from scratch. Is their a more efficient way so that self.view keeps the footer and only toggles between listview and mapview?
I figured out a possible solution. Rather than calling the method setupFooterAfterFlip every time, and creating filters from scratch, I just gave filters a tag id. Now, every time user toggles the view, I remove filters from superview and add it again to new view.
// remove footer
UIView *footer = (UIView *)[self.view viewWithTag:22];
[footer removeFromSuperview];
//change view, write some code, add footer again
[self.view addSubview:footer];
Related
I have a UIView that flies from the top when I open that view controller. I've set the Y Constraint of the UIView to -200 and when the view loads, the below is called and everything works fine:
- (void)updateViewConstraints
{
[super updateViewConstraints];
self.popUpViewYConstraint.constant = 37.0f;
self.closeButtonYConstraint.constant = 28.0f;
[self.popUpBaseView setNeedsUpdateConstraints];
[self.closeButton setNeedsUpdateConstraints];
[UIView animateWithDuration:0.25f animations:^{
[self.popUpBaseView layoutIfNeeded];
[self.closeButton layoutIfNeeded];
[self.view layoutIfNeeded];
}];
}
But now I have a close button that should animate the UIView back to the -200 position and then remove the view controller from the screen. But this animation is not taking place. The view controller is getting removed directly. Here's what I'm doing:
- (IBAction)closePressed:(id)sender
{
NSMutableArray *navigationArray = [[NSMutableArray alloc] initWithArray: self.navigationController.viewControllers];
self.navigationController.viewControllers = navigationArray;
[UIView animateWithDuration:2.0f animations:^{
self.popUpViewYConstraint.constant = -200.0f;
[self.popUpBaseView layoutIfNeeded];
} completion:^(BOOL finished){
[navigationArray removeObjectAtIndex: 1];
[self.baseView removeFromSuperview];
[self.view removeFromSuperview];
}];
}
I referred to this link. It seems to be working for them but doesn't work for me.
Please help.
This line:
self.popUpViewYConstraint.constant = -200.0f;
Should be called before the animation block. Also i'm not sure about hierarchy of your views, but make sure you are calling the correct view with layoutIfNeeded call.
How about this
- (IBAction)closePressed:(id)sender
{
NSMutableArray *navigationArray = [[NSMutableArray alloc]:initWithArray:self.navigationController.viewControllers];
self.navigationController.viewControllers = navigationArray;
// I am sure the constant should be set outside the animation block. It won't happen until next run loop, which will be inside the block.
self.popUpViewYConstraint.constant = -200.0f;
// setNeedsUpdateConstraints should be called on the view to which the constraint is added, not the view affected by the constraint.
[self.baseView setNeedsUpdateConstraints];
[UIView animateWithDuration:2.0f animations:^{
// I think this should be topmost view
[self.view layoutIfNeeded];
} completion:^(BOOL finished){
[navigationArray removeObjectAtIndex: 1];
[self.baseView removeFromSuperview];
[self.view removeFromSuperview];
}];
}
I have a view controller with multiple child view controllers in it (set up using Storyboards), and I move the one on top to the right (with the status bar as well) to display the underlying sidebar view controller.
This works perfectly with frames, as shown below:
- (void)displaySidebar {
self.fullScreenSnapshotOverlay = [self takeFullScreenSnapshot];
[self.postsView addSubview:self.fullScreenSnapshotOverlay];
[self hideStatusBar];
[UIView animateWithDuration:0.4 animations:^{
CGRect newFrame = self.postsView.frame;
newFrame.origin.x += 200.0;
self.postsView.frame = newFrame;
}];
}
(hideStatusBar simply called the UIApplication method and layoutIfNeeded.)
Giving me this (perfect) result:
However, if in the Storyboard I go to the container view controller and make a constraint from its leading space to the left of the view controller it's embedded in, and then adjust that constant, it really messes up the navigation bar, I assume due to hiding the status bar and taking a screenshot. I'm using this code:
- (void)displaySidebar {
self.fullScreenSnapshotOverlay = [self takeFullScreenSnapshot];
[self.postsViewController.view addSubview:self.fullScreenSnapshotOverlay];
[self hideStatusBar];
[UIView animateWithDuration:0.4 animations:^{
self.postsViewControllerDistanceFromLeftSideConstraint.constant = 270.0;
[self.view layoutIfNeeded];
}];
}
Giving me this messed up result:
Now I know the simple thing to do would be to just continue with frames, but I'd like to learn how do it properly with Auto Layout. What am I doing wrong here?
I'm not sure how what I did is any different from what you're doing. I modified the code I posted to your other question (Why does hiding my status bar completely break my simple animation?) to what's below, and it worked fine.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self performSelector:#selector(displaySidebar) withObject:nil afterDelay:1];
}
-(void)displaySidebar {
self.snapshotView = [self takeSnapshot];
[self.PostsView addSubview:self.snapshotView];
[self hideStatusBar];
self.leftCon.constant = 270;
[UIView animateWithDuration:1.0 animations:^{
[self.view layoutIfNeeded];
}];
}
-(void)moveOutMenu { // called from a button in the menu controller
self.leftCon.constant = 0;
[UIView animateWithDuration:1.0 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
[[UIApplication sharedApplication] setStatusBarHidden:NO];
[self.snapshotView performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:0.01];
}];
}
-(UIView *)takeSnapshot {
UIView *v = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
return v;
}
-(void)hideStatusBar {
[[UIApplication sharedApplication] setStatusBarHidden:YES];
[self.view layoutIfNeeded];
}
What are all the constraints you have on the container view that you're moving? Maybe there's something different there (I have top, leading, bottom and width constraints on mine -- leftCon is the outlet to the leading constraint).
I have a View (CupsViewController) with a Label and when it's clicked, TableView (AddressTableController) slides from the bottom of the screen and stays in the lower half.
In TableView, when Ok Buttonis pressed, I want to change value of Labeland remove TableViewwith animation (that is, slide to the bottom).
Here is my code:
CupsViewController
Method called when click on Label
- (IBAction)showPop:(id)sender
{
addressTableController = [[AddressTableController alloc]initWithStyle:UITableViewStyleGrouped];
[addressTableController setDelegate:self];
[[self view] addSubview:[addressTableController myTableView]];
[UIView animateWithDuration:1.0 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[[addressTableController myTableView] setFrame:CGRectMake(0, kScreenHeight * 0.5, kScreenWidth, kScreenHeight * 0.5)];
} completion:nil];
}
Method called from AddressTableControllerwhen Buttonis pressed
- (void)addressTableController:(AddressTableController *)viewController didChooseValue:(int)value {
direccionLabel.text = [[[[myAppDelegate usuarioActual] cups] objectAtIndex:value] direccion];
[addressTableController.view removeFromSuperview];
}
Image
As you can see, I've tried with removeFromSuperviewbut it does nothing. How can I slide TableViewto the bottom?
It seems that you myTableView property returns a different view, not the one that is referenced in the view property. So you basically add the former as a subview ([[self view] addSubview:[addressTableController myTableView]];), but try to remove the latter ([addressTableController.view removeFromSuperview];).
Just do
[[addressTableController myTableView] removeFromSuperview];
instead of
[addressTableController.view removeFromSuperview];
Here you go. Cheers, mate! :)
P.S.
if you want to animate the view out, you can do it like this
[UIView animateWithDuration:1.0 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
// animation code...
} completion:^(BOOL finished) {
[[addressTableController myTableView] removeFromSuperview];
}];
I'm trying to have something similar to a UINavigationController so I can customize the animations. To start, I'm just using Apple stock animations. Here's my containerViewController:
- (void)loadView {
// Set up content view
CGRect frame = [[UIScreen mainScreen] bounds];
_containerView = [[UIView alloc] initWithFrame:frame];
_containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.view = _containerView;
}
- (id)initWithInitialViewController:(UIViewController *)vc {
self = [super init];
if (self) {
_currentViewController = vc;
[self addChildViewController:_currentViewController];
[self.view addSubview:_currentViewController.view];
[self didMoveToParentViewController:self];
_subViewControllers = [[NSMutableArray alloc] initWithObjects:_currentViewController, nil];
}
return self;
}
- (void)pushChildViewController:(UIViewController *)vc animation:(UIViewAnimationOptions)animation {
vc.view.frame = _containerView.frame;
[self addChildViewController:vc];
[self transitionFromViewController:_currentViewController toViewController:vc duration:0.3 options:animation animations:^{
}completion:^(BOOL finished) {
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
[self.subViewControllers addObject:vc];
}];
}
- (void)popChildViewController:(UIViewController *)vc WithAnimation:(UIViewAnimationOptions)animation {
// Check that there is a view controller to pop to
if ([self.subViewControllers count] <= 0) {
return;
}
NSInteger idx = [self.subViewControllers count] - 1;
UIViewController *toViewController = [_subViewControllers objectAtIndex:idx];
[vc willMoveToParentViewController:nil];
[self transitionFromViewController:vc toViewController:toViewController duration:0.3 options:animation animations:^{
}completion:^(BOOL finished) {
[vc.view removeFromSuperview];
[vc removeFromParentViewController];
[self didMoveToParentViewController:toViewController];
[self.subViewControllers removeObjectAtIndex:idx];
}];
}
I have this ContainerViewcontroller as my rootViewController of the window. I can add my initial viewController and push a view controller. When I try to pop though, I get
ContainerViewController[65240:c07] Unbalanced calls to begin/end appearance transitions for <SecondViewController: 0x8072130>.
I'm wondering what I am doing wrong. I figured my initialViewController is still underneath the secondViewController. Any thoughts? Thanks!
I don't know if this is what's causing your problem, but shouldn't this:
[self didMoveToParentViewController:toViewController];
be:
[toViewController didMoveToParentViewController:self];
Also, I'm not sure what you're doing with the subViewControllers array. It seems to be a duplication of the childViewControllers array that is already a property of a UIViewController.
One other thing I'm not sure is right. In your pop method your toViewController is the last controller in the _subViewControllers array. Don't you want it to be the second to last? Shouldn't the last be the one you're popping? You're popping vc, which is a controller you're passing in to the method, I don't understand that.
This is the way I've made a navigation like controller. In its containment behavior, it acts like a navigation controller, but without a navigation bar, and allows for different transition animations:
#implementation ViewController
-(id)initWithRootViewController:(UIViewController *) rootVC {
if (self = [super init]) {
[self addChildViewController:rootVC];
rootVC.view.frame = self.view.bounds;
[self.view addSubview:rootVC.view];
}
return self;
}
-(void)pushViewController:(UIViewController *) vc animation:(UIViewAnimationOptions)animation {
vc.view.frame = self.view.bounds;
[self addChildViewController:vc];
[self transitionFromViewController:self.childViewControllers[self.childViewControllers.count -2] toViewController:vc duration:1 options:animation animations:nil
completion:^(BOOL finished) {
[vc didMoveToParentViewController:self];
NSLog(#"%#",self.childViewControllers);
}];
}
-(void)popViewControllerAnimation:(UIViewAnimationOptions)animation {
[self transitionFromViewController:self.childViewControllers.lastObject toViewController:self.childViewControllers[self.childViewControllers.count -2] duration:1 options:animation animations:nil
completion:^(BOOL finished) {
[self.childViewControllers.lastObject removeFromParentViewController];
NSLog(#"%#",self.childViewControllers);
}];
}
-(void)popToRootControllerAnimation:(UIViewAnimationOptions)animation {
[self transitionFromViewController:self.childViewControllers.lastObject toViewController:self.childViewControllers[0] duration:1 options:animation animations:nil
completion:^(BOOL finished) {
for (int i = self.childViewControllers.count -1; i>0; i--) {
[self.childViewControllers[i] removeFromParentViewController];
}
NSLog(#"%#",self.childViewControllers);
}];
}
After Edit: I was able to duplicate the back button function with this controller by adding a navigation bar to all my controllers in IB (including in the one that is the custom container controller). I added a bar button to any controllers that will be pushed, and set their titles to nil (I got some glitches if I left the title as "item"). Deleting that title makes the button disappear (in IB) but you can still make connections to it in the scene list. I added an IBOutlet to it, and added this code to get the function I wanted:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.isMovingToParentViewController) {
self.backButton.title = [self.parentViewController.childViewControllers[self.parentViewController.childViewControllers.count -2] navigationItem].title;
}else{
self.backButton.title = [self.parentViewController.childViewControllers[self.parentViewController.childViewControllers.count -3] title];
}
}
I've shown two different ways that worked to access a title -- in IB you can set a title for the controller which I used in the else clause, or you can use the navigationItem title as I did in the if part of the clause. The "-3" in the else clause is necessary because at the time viewWillAppear is called, the controller that is being popped is still in the childViewControllers array.
addChildViewController should be called first
For adding / removing, you can refer to this great category and have no worry when to call it:
UIViewController + Container
- (void)containerAddChildViewController:(UIViewController *)childViewController {
[self addChildViewController:childViewController];
[self.view addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
}
- (void)containerRemoveChildViewController:(UIViewController *)childViewController {
[childViewController willMoveToParentViewController:nil];
[childViewController.view removeFromSuperview];
[childViewController removeFromParentViewController];
}
In addition to rdelmar's answer you should not be calling addView/removeFromSuperview transitionFromViewController does this for you, from the documentation:
This method adds the second view controller’s view to the view
hierarchy and then performs the animations defined in your animations
block. After the animation completes, it removes the first view
controller’s view from the view hierarchy.
It's my first post on stackoverflow. I'm a iOS developer newbie and I'm not a native English speaker, so I will do my best to explain my problem.
Problem:
I have added two views to my AppDelegate window and I want to flip from one to the other using:
UIView transitionFromView:toView:
The first view (MainScreenView) has its own ViewController. On the MainScreenView .xib file I have a button with an action that calls the method "goShow" implemented in my AppDelegate. In that method I use UIView transitionFromView:toView: to transition to the second view. So far everything is working fine.
My second view (a scrollview) is declared programmatically in my AppDelegate and has a bunch of pictures inside it (picturesViewController) and on top of those, has a UIPinchGestureRecognizer.
I'm using a gesture recognizer to flip back to my MainScreenView. That is where the problem is. When I do a pinch gesture on the scrollview the MainScreenView.view appears immediately, before the animation, so the flip animation looks wrong.
The code I'm using is:
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
mainScreen = [[MainScreenViewController alloc] initWithNibName:#"MainScreenViewController" bundle: [NSBundle mainBundle]];
CGRect frame = self.window.bounds;
int pageCount = 10;
scrollView = [[UIScrollView alloc] initWithFrame:frame];
scrollView.contentSize = CGSizeMake(320*pageCount, 480);
scrollView.pagingEnabled = YES;
scrollView.showsHorizontalScrollIndicator = FALSE;
scrollView.showsVerticalScrollIndicator = FALSE;
scrollView.delegate = self;
[...] 'While' adding pictures to de scrollView
UIPinchGestureRecognizer *twoFingerPinch = [[[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(goBackToMain)] autorelease];
[scrollView addGestureRecognizer:twoFingerPinch];
[self.window addSubview: scrollView];
[scrollView setHidden:TRUE];
[self.window addSubview: mainScreen.view];
[self.window makeKeyAndVisible];
return YES;
}
-(void) goShow{
[UIView transitionFromView:mainScreen.view
toView:scrollView
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionShowHideTransitionViews
completion:NULL];
[UIView commitAnimations];
}
-(void) goBackToMain {
[UIView transitionFromView:scrollView
toView:mainScreen.view
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionShowHideTransitionViews
completion:NULL];
[UIView commitAnimations];
}
I'm using show/hide views instead of addSubview/removeFromSuperView because I tried the add and remove and got an app crash in the pinch gesture, exactly in same step that is failing the animation. Probably it is the same error, but I'm unable to find the reason for this. Any help would be appreciated.
Thanks.
Ok. With Adrian's help, here's the UIPinchGesture code that solved my problem:
[...]
UIPinchGestureRecognizer *twoFingerPinch = [[[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(goBackToMain:)] autorelease];
[scrollView addGestureRecognizer:twoFingerPinch];
-(void)goBackToMain:(UIPinchGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded)
{
[UIView transitionFromView:scrollView
toView:mainScreen.view
duration:0.4
options:UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionShowHideTransitionViews
completion:nil];
[UIView commitAnimations];
}
First, you cannot mix the old method beginAnimation commitAnimation combination with the new block method transitionFromView.
Second, when using block method animation, make sure you use a container (probably a UIView) that will be the parent of the two views you want to switch. Without the container you will be animating the whole view instead. Make sure the container have the same size as the subviews that will switch.
Example:
[container addSubView:frontView];
[container addSubView:backView];
[self.view addSubView:container];
[UIView transitionFromView:backView toView:frontView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:nil];
Read more about animations in iOS.
In your example you forgot [UIView beginAnimations].