I have a scene which is built as on the attached screen
Scene
Depending on the selection of a button from the top bar, I'm loading a specific view controller inside a container view. When selection is changed or the user is tapping the 'Back' button at controller then should be removed an intrinsic content of the container.
Code for adding into container view:
if (self.containerVC != nil) {
[self removeController];
}
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
FirstViewController* firstVC = [storyboard instantiateViewControllerWithIdentifier:#"First"];
[self addChildViewController:firstVC];
[self.containerVieww addSubview:firstVC.view];
firstVC.view.frame = CGRectMake(0, 0, self.containerVieww.bounds.size.width, self.containerVieww.bounds.size.height);
[firstVC.view layoutSubviews];
[firstVC didMoveToParentViewController:self];
firstVC.modalPresentationStyle = UIModalPresentationCurrentContext;
[UIView animateWithDuration:0.5 animations:^{
self.logViewHeight.constant = self.contentView.frame.size.height * 0.9;
[firstVC.view layoutSubviews];
}];
firstVC.delegate = self;
self.containerVC = firstVC;
Code for removing from container view:
[self.containerVC dismissViewControllerAnimated:YES completion:nil];
[UIView animateWithDuration:0.5 animations:^{
self.logViewHeight.constant = 0;
[self.view layoutIfNeeded];
}];
[self willMoveToParentViewController:nil];
[self removeFromParentViewController];
[self.containerVC.view removeFromSuperview]; // Caused layout destoying !!!
self.containerVC = nil;
After calling 'removeFromSuperview' is disappeared a 'Blue View' in my layout.
How to remove from the container view without destroying a complex layout?
Related
I've been trying to solve this issue for the past couple of hours, searched SO and still to no avail.
I have a UIViewController which has a Container view inside (dragged in from Storyboards). The container view has a class associated that picks one of two other view controllers to display.
-(void)setDisplayMode:(EventsDisplayMode)displayMode {
switch (displayMode) {
case EventsDisplayModeGrid: {
// show grid
if (!gridViewController) {
gridViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"gridViewController"];
}
[UIView animateWithDuration:0.8 delay:0 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
if (listViewController)
[listViewController.view removeFromSuperview];
gridViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:gridViewController.view];
listViewController = nil;
} completion:^(BOOL finished) {
}];
break;
}
case EventsDisplayModeList: {
// show list
if (!listViewController)
listViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"listViewController"];
[UIView animateWithDuration:0.8 delay:0 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
if (gridViewController)
[gridViewController.view removeFromSuperview];
[self.view addSubview:listViewController.view];
gridViewController = nil;
} completion:^(BOOL finished) {
}];
break;
}
}
}
The above is the code in the container view controller. When either mode is set though, the view bleeds off the bottom of the screen on an iPhone 5s slightly, and on an iPhone 4S it's as though the view inside hasn't resized at all for the device. I've tried all sorts of combinations to resize the views - setting up constrains inside Storyboards for each of the controllers, manually taking off amounts from the height of views - nothing seems to work. Is there something obvious I'm missing?
I have a single view App and want to show a new ViewController when pressing a nav bar button in the right hand side. I call this VC by this code:
- (IBAction)createEntryButton:(id)sender {
CreateEntryViewController *vc2 = [[CreateEntryViewController alloc] init];
[self presentViewController:vc2 animated:TRUE completion:nil];
}
This animation, however, brings the vc2 in from the bottom which seems counter-intuitive according to my UI. So my question is:
How can I make my vc2 appear from the right instead of the bottom with presentViewController?
Thanks.
the cleanest would be to use a navigationController for pushing and popping views..
if you are already in a NavigationController
[self.navigationCtroller pushViewController:vc2 animated:TRUE completion:nil]
if you aren't, adapt the code where your view controller is added to the window. If your VC is the rootWindowController and you are not using storyboarding, this is likely in your AppDelegate
if you use storyboards, adapt the storyboard so you are inside a navigation controller
ELSE if you don't want that for any reason: :) just manually animate in the 2. VC's view using [UIView animate:vc2.view ....]
written inline -- method names don't match but shows general approach:
UIView *v = vc2.view;
CGRect f = v.frame;
f.origin.x += self.view.frame.size.width; //move to right
v.frame = f;
[UIView animateWithDuration:0.5 animations:^{
v.frame = self.view.frame;
} completion:^(BOOL finished) {
[self presentViewController:vc2 animated:NO completion:nil];
}];
in the completion block present the view controller vc2 non-animated as you already did that yourself
This helped me,
- (void)presentNewViewController{
NewViewController *objNewViewController =[[NewViewController alloc]initWithNibName:#"NewViewController" bundle:nil];
UIView *tempNewVCView = [UIView new];
tempNewVCView = objNewViewController.view;
tempNewVCView.frame = self.view.frame;
CGRect initialFrame = self.view.frame;
initialFrame.origin.x = self.view.frame.size.width;
tempNewVCView.frame = initialFrame;
[self.view addSubview:tempNewVCView];
[UIView animateWithDuration:0.3 animations:^{
tempNewVCView.frame = self.view.frame;
} completion:^(BOOL finished) {
[self presentViewController:objNewViewController animated:NO completion:^{
}];
}];
}
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.
I am trying to build a custom container view controller and am trying to create my own animation. I'm starting with something simple of moving the tapped cell to the center of the screen before making it zoom in by scaling and showing the new view. I'm starting with this:
- (void)showDetailViewController:(UIView *)viewToAnimate {
UIImageView *anImageView = [[UIImageView alloc] initWithImage:[self screenShot:_homeViewController.view]];
CGRect windowBounds = [[UIScreen mainScreen] bounds];
_detailViewController.view.frame = windowBounds;
[self addChildViewController:_detailViewController];
// get a screenshot of the old view before pushing the new view controller
_detailViewController.view.alpha = 0.0;
NSLog(#"center: %#", NSStringFromCGPoint(viewToAnimate.center));
[self transitionFromViewController:_homeViewController toViewController:_detailViewController duration:0.5 options:0 animations:^{
CGPoint windowCenter = CGPointMake(CGRectGetMidX(windowBounds), CGRectGetMidY(windowBounds));
viewToAnimate.center = windowCenter;
viewToAnimate.layer.transform = CATransform3DMakeScale(2, 2, 2);
NSLog(#"center 2 : %#", NSStringFromCGPoint(viewToAnimate.center));
_detailViewController.view.alpha = 1.0;
}completion:^(BOOL finished) {
[anImageView removeFromSuperview];
}];
[_detailViewController didMoveToParentViewController:self];
}
What I don't understand is, if I comment out the _detailViewController.view.alpha = 0 and _detailViewController.view.alpha = 1, I don't get any animation. It just jumps to the new childViewController. But with those two lines, I do see an animation. Why is that? Thanks.
Edit:
This is how I set up a container controller:
In viewDidLoad of ContainerViewController:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPad" bundle:nil];
_homeViewController = [storyboard instantiateViewControllerWithIdentifier:#"HomeViewController"];
_homeViewController.homeViewControllerDelegate = self;
_detailViewController = [storyboard instantiateViewControllerWithIdentifier:#"DetailViewController"];
[self setSubViewControllers:#[ _homeViewController, _detailViewController]];
_selectedViewController.view.frame = [[UIScreen mainScreen] bounds];
[self addChildViewController:self.selectedViewController];
[self.view addSubview:_selectedViewController.view];
[self didMoveToParentViewController:self];
In the code you posted, you show yourself adding _detailViewController as a child view controller, but don't show the step of adding _homeViewController as a child view controller of self. At the point when the code you posted is executed, is _homeViewController a child of self (the view controller that is running the transition)? It needs to be.
I'm trying to work on a UIButton animation where the button moves to a point and then is set hidden to be true. However when I tried working on the following code, the button disappeared even before the animation was completed. Am I doing it correctly? Any suggestions?
[UIView animateWithDuration:0.8
animations:^{
selectedCurveIndex = 0;
[tradebutton moveTo:
CGPointMake(51,150) duration:0.8
option:curveValues[UIViewAnimationOptionCurveEaseInOut]];
}
completion:^(BOOL finished){
[tradeButton setHidden:TRUE];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"ButtonView"];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:vc animated:NO];
}];
You need to make sure that finished is set to YES before moving on.
Your button hides quickly because 0.8 is a quick animation duration. You will need to figure out another place to hide the button, or you can
Try this:
[UIView animateWithDuration:0.8
animations:^{
selectedCurveIndex = 0;
[tradebutton moveTo:
CGPointMake(51,150) duration:0.8
option:curveValues[UIViewAnimationOptionCurveEaseInOut]];
}
completion:^(BOOL finished){
if ( finished )
{
[tradeButton performSelector:#selector(setHidden:) withObject:#"YES" afterDelay:3.0];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"ButtonView"];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:vc animated:NO];
}
}];
The problem is that you create a second, inner animation block in the moveTo:duration:option: method, and you set all of the animatable properties in that inner block. You don't set any animatable properties in the outer block.
That means that the system immediately thinks that the outer animation has finished, and calls the completion block right away. Meanwhile, the inner animation block is still going.
Stop using moveTo:duration:option:. It saves you almost nothing, and ends up giving you trouble like this. Throw it out and try something like this instead:
[UIView animateWithDuration:0.8 animations:^{
tradeButton.frame = (CGRect){ CGPointMake(51, 150), tradeButton.bounds.size };
} completion:^(BOOL finished) {
tradeButton.hidden = YES;
// etc.
}];
Note that UIViewAnimationOptionCurveEaseInEaseOut is the default for most animations.