Using [self.view removeFromSuperview] in UIView animation completion block removes view immediately - ios

Fixed
It turns out the animation was being called twice. I've now fixed it so the animation is only called once and it works perfectly.
I suspect it might be something related to my use of child view controllers, but I'm noticing some strange behaviour when I try to remove the child view controller's view with an animation.
I'm adding the child view controller to the parent, along with the child view controller's view, and animating that view from the bottom of the screen. This works perfectly — my issue is when I try to remove the child view/view controller. I'm animating the child's view to the bottom of the screen, and then calling [self.view removeFromSuperview] in the completion block, but the view is removed immediately (so no animation takes place). If I delete the [self.view removeFromSuperview] line, the animation works correctly, but then the view isn't removed from the parent view controller's view.
Adding the child view controller
(this works as intended)
ChildViewController *myChildViewController = [[ChildViewController alloc] init];
myChildViewController.view.frame = CGRectMake(0.f, self.view.frame.size.height, self.view.frame.size.width, self.view.frame.size.height - 64.f);
[self addChildViewController:myChildViewController];
[self.view addSubview:myChildViewController.view];
[myChildViewController didMoveToParentViewController:self];
[UIView animateWithDuration:0.75f delay:0.f usingSpringWithDamping:0.6f initialSpringVelocity:0.75f options:0 animations:^{
myChildViewController.view.frame = CGRectMake(0.f, 64.f, self.view.frame.size.width, self.view.frame.size.height - 64.f);
} completion:nil];
Removing the child view controller
(this doesn't work — the view is removed immediately, instead of after completion)
[UIView animateWithDuration:0.75f delay:0.f usingSpringWithDamping:0.6f initialSpringVelocity:0.75f options:0 animations:^{
self.view.frame = CGRectMake(0.f, self.view.superview.frame.size.height, self.view.frame.size.width, self.view.frame.size.height);
} completion:^(BOOL finished) {
if (finished)
{
[self willMoveToParentViewController:self.parentViewController];
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
}];
I've even tried removing [self willMoveToParentViewController:self.parentViewController] and [self removeFromParentViewController] just to see if that changed anything, but as long as [self.view removeFromSuperview] is there, the view disappears immediately.

Add the in the Parent class
- (void) moveToAddJobVC:(NSString*)storyboardId {
ChildViewController *destVC = [self.storyboard instantiateViewControllerWithIdentifier:storyboardId];
destVC.completionHandler = ^(NSString* string) {
NSLog(#"Returned strng.....%#",string);
//[self viewWillAppear:YES];
};
[destVC.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self addChildViewController:destVC];
[self.view addSubview:destVC.view];
[destVC didMoveToParentViewController:self];
[UIView animateWithDuration:0.3 animations:^{
[destVC.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
}];
}
And in child VC write
#property (nonatomic,copy) void (^completionHandler)(NSString *string);
- (IBAction)dismissBtnAction:(id)sender{
if (self.completionHandler)
{
self.completionHandler(#"Return data");
}
[self removeFromParentViewController];
[self.view removeFromSuperview];
}

Related

UIView animateWithDuration ignores duration

My goal is simple and I have achieved this previously which is why I'm resorting to asking my question here.
I am trying to animate a subview. Simple enough. The view ends up exactly where I want it to be after the animation is over. The only problem is:
whatever I do the animation duration is completely ignored. It happens instantly.
- (void)callToDismissView:(UIView *)view {
[self.view addSubview:view];
[self dismissViewControllerAnimated:NO completion:nil];
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
CGRect frame = self.view.frame;
frame.origin.x = frame.size.width;
view.frame = frame;
} completion:^(BOOL finished){
[view removeFromSuperview];
}];
}
Now I have tried different frames and durations to see if the view was animated at all and it always ends up where I need it to be. Only instantly...
EDIT:
The above code is being called by a delegate in the dismissed view controller like so:
- (void)dismissSelf {
if (self.delegate && [self.delegate respondsToSelector:#selector(callToDismissScan)]) {
[self.delegate callToDismissScan];
}
}
And that method itself is triggered by a notification.
I must admit that some things are happening during the transition between the two controllers that I don't fully understand. (And I would very much like to understand them...)
You can try
[self.view layoutIfNeeded];
like this
- (void)callToDismissView:(UIView *)view {
[self.view addSubview:view];
[self dismissViewControllerAnimated:NO completion:nil];
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
CGRect frame = self.view.frame;
frame.origin.x = frame.size.width;
view.frame = frame;
[self.view layoutIfNeeded];
} completion:^(BOOL finished){
[view removeFromSuperview];
}];
}
Maybe you need try like this.
- (void)callToDismissView:(UIView *)view {
[self.view addSubview:view];
CGRect frame = self.view.frame;
frame.origin.x = frame.size.width;
[UIView animateWithDuration:0.3 delay:0.3
options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
view.frame = frame;
}
completion:^(BOOL finished){
[view removeFromSuperview];
//[self dismissViewControllerAnimated:NO completion:nil];//uncomment if need.
}];
}
It seems there was no way in hell I could animate the transition in the presenting view controller and so the simplest way to resolve the issue was to animate the transition in the presented view controller before it is being dismissed.
The way I did this was by casting the delegate to a view controller. It was then easy to add it's view as a sub-view of my presented view controller and animate it like so:
- (void)dismissSelf {
UIView *dummyView = [ScreenCapper captureScreenIn: self];
UIViewController *presentingViewController = (UIViewController *)self.delegate;
[self.view.window addSubview: presentingViewController.view];
[self.view.window addSubview: dummyView];
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
dummyView.frame = CGRectMake(dummyView.frame.size.width, dummyView.frame.origin.y, dummyView.frame.size.width, dummyView.frame.size.height);
} completion:^(BOOL finished){
if (self.delegate && [self.delegate respondsToSelector:#selector(callToDismissView)]) {
[self.delegate callToDismissView];
}
}];
}
And in the presenting view controller all that was left to do was to dismiss the presented view controller without animations.
- (void)callToDismissView {
[self dismissViewControllerAnimated:NO completion:nil];
}

No touch events on child view controller

Alright so I am trying to create a slide out side view for navigation in my app, similar to Facebook and many others. I have that part working but the navigation controller doesn't respond to any touch events. I have a feeling this is because I am having my subclassed Navigation Controller add the child view controller and subview, instead of having the present view display the child. Here is the code I run on my "FeaturedViewController" that calls to "MainNavigationController" to add "MenuViewController" as a child of "MainNavigationController"
//ignoring compiler error as we know the parentViewController will be of type MainNavigationController at runtime.
MainNavigationController *main = self.parentViewController;
if (self.categories != nil)
main.categories = self.categories;
if (main.showingLeftPanel)
[main movePanelToOriginalPosition];
else
[main movePanelRight];
From there:
- (void)movePanelRight // to show left panel
{
UIView *childView = [self getLeftView];
//_leftPanelViewController.categories = self.categories;
[self.view sendSubviewToBack:childView];
[UIView animateWithDuration:SLIDE_TIMING delay:0 options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
_centerViewController.view.frame = CGRectMake(self.view.frame.size.width - PANEL_WIDTH, 0, self.view.frame.size.width, self.view.frame.size.height);
}
completion:^(BOOL finished) {
if (finished) {
//_centerViewController.leftButton.tag = 0;
}
}];
}
- (UIView *)getLeftView
{
// init view if it doesn't already exist
if (_leftPanelViewController == nil)
{
// this is where you define the view for the left panel
_leftPanelViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MenuViewController"];
[self addChildViewController:_leftPanelViewController];
[self.view addSubview:_leftPanelViewController.view];
[_leftPanelViewController didMoveToParentViewController:self];
_leftPanelViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}
_leftPanelViewController.categories = self.categories;
self.showingLeftPanel = YES;
// set up view shadows
[self showCenterViewWithShadow:YES withOffset:-2];
UIView *view = self.leftPanelViewController.view;
return view;
}

Issue with UIView Auto Layout Animation

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];
}];
}

Why modal presentation is empty

I have two view controllers. In the first one I have a button and when it is clicked I want to show the other view controller in the right of the screen to login like modal present form sheet.
For the moment I have the second view controller with a label but when I click it appears empty, I don't know way because I put a label inside, and of course in the middle of the screen.
LoginViewController *loginController = [[LoginViewController alloc] init];
loginController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:loginController animated:YES completion:nil];
loginController.view.superview.frame = CGRectMake(0, 0, 300, 1000);
loginController.view.superview.center = self.view.center;
Any suggestion to see the label? To change the position?
I think it would be best to use the custom container controller methods to do this. In the example code I have below, I made a login controller that was the size of a form sheet (540 x 600) and slid it in from the right onto the main view of ViewController so that it was centered vertically, and up against the right side.
In ViewController.m, I have this:
-(IBAction)showLogin:(id)sender {
LoginController *login = [self.storyboard instantiateViewControllerWithIdentifier:#"Login"];
login.view.frame = CGRectMake(768, 202, 540, 600);//centered vertically and offscreen to the right
[self addChildViewController:login];
[self.view addSubview:login.view];
[UIView animateWithDuration:.5 animations:^{
login.view.frame = CGRectMake(228, 202, 540, 600);
} completion:^(BOOL finished) {
[login didMoveToParentViewController:self];
}];
}
And to remove the view, I have this in the LoginController.m:
-(IBAction)goBackToMain:(id)sender {
[UIView animateWithDuration:.5 animations:^{
self.view.frame = CGRectMake(768, 202, 540, 600);
} completion:^(BOOL finished) {
[self.view removeFromSuperview];
[self removeFromParentViewController];
}];
}
After Edit:
If you want to remove the login controller from a button in ViewController, you can do it like this:
-(IBAction)goBackToMain:(id)sender {
LoginController *login = self.childViewControllers.lastObject;
[UIView animateWithDuration:.5 animations:^{
login.view.frame = CGRectMake(768, 202, 540, 600);
} completion:^(BOOL finished) {
[login.view removeFromSuperview];
[login removeFromParentViewController];
}];
}

When to call addChildViewController

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.

Resources