I am trying to override removeFromSuperview for a custom view i created. The view is intended to contain a UIActivityIndicatorView and i managed to animate its appearance successfully overriding the initWithFrame method. Now i would like to override also its removeFromSuperview adding in it a fade out animation so that from the view controller i can simply call [self.loadingView removeFromSuperview]; Unfortunately the loadingView is removed without the animation, probably because of the [super removeFromSuperview]; i need to call when overriding. Here is the code i'm using, is there a way i can fix it?
-(void)removeFromSuperview
{
[super removeFromSuperview];
[UIView animateWithDuration:0.5
animations:^{self.alpha = 0.0;}
completion:^(BOOL finished){[self removeFromSuperview];}];
}
Try putting the [super removeFromSuperview] in the completion block for the animation:
-(void)removeFromSuperview
{
[UIView animateWithDuration:0.5
animations:^{self.alpha = 0.0;}
completion:^(BOOL finished){[super removeFromSuperview];}];
}
Related
Starting in iOS 11, UIViewController's transitionFromViewController:toViewController:duration:options:animations:completion: method appears to not call its completion block anymore.
Sample code snippet below:
[self addChildViewController:toVC];
[fromVC willMoveToParentViewController:nil];
[self transitionFromViewController:fromVC
toViewController:toVC
duration:0.4
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{}
completion:^(BOOL finished) {
NSLog(#"Completion called"); // this completion is never executed
}];
This is causing me all kinds of issues in getting my views to transition and animate correctly. Has anyone else run into this behavior, and/or discovered a workaround?
So it turns out that I wasn't explicitly adding toVC.view as a subview to self.view after adding toVC as a child view controller to self. Odd that this behaves differently in iOS 11 vs. previous versions, but this did the trick:
[self addChildViewController:toVC];
[self.view addSubview:toVC.view]; // This line is what is needed
[fromVC willMoveToParentViewController:nil];
[self transitionFromViewController:fromVC
toViewController:toVC
duration:0.4
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{}
completion:^(BOOL finished) {
NSLog(#"Completion called");
}];
I am using the following code to override the perform method in a custom segue to achieve sliding effect when moving between view controllers.
- (void)perform
{
MasterController *sourceController = (MasterController *)self.sourceViewController;
MasterController *destinationController = (MasterController *)self.destinationViewController;
CGRect frame = sourceController.view.frame;
[sourceController.view addSubview:destinationController.view];
[destinationController.view setFrame:CGRectOffset(frame, frame.size.width, 0)];
[UIView animateWithDuration:0.5 animations:^{
[sourceController.view setFrame:CGRectOffset(frame, -frame.size.width, 0)];
} completion:^(BOOL finished) {
[sourceController presentViewController:destinationController animated:NO completion:nil];
[destinationController.view removeFromSuperview];
}];
}
Similar to this code exists all over the internet. the problem is that "sometimes" after the animation finish the screen flashes/blinks/flickers then gets back normal.
removing [destinationController.view removeFromSuperview]; line of code seems to solve the problem. but, that doesn't look right! right?
Any ideas how to solve this?
Yes, simply remove [destinationController.view removeFromSuperview];. It will be done for you. At the end of the segue, destinationController.view will have a new superview and will be removed from sourceController.view.
I just tried my making a custom transition between view controllers. It basically spins the next one into view, and it works. Except that the source view controller flickers briefly back into visibility right as the animation completes, just as the destination view controller achieves its final position.
I'm also getting a warning about Unbalanced calls to begin/end appearance transitions which I'm still working on fixing-- I don't know if they're related.
Does anyone see anything here that jumps out as not quite right that would cause a flicker?
I then just assigned a button to do a custom segue via storyboard editor.
-(void)perform
{
UIViewController *source = self.sourceViewController;
UIViewController *destination = self.destinationViewController;
[source.view addSubview:destination.view];
destination.view.transform = CGAffineTransformMakeRotation(M_PI / 2);
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^{
destination.view.transform = CGAffineTransformMakeRotation(0);
}completion:^(BOOL finished){
[destination.view removeFromSuperview];
[source presentViewController:destination animated:NO completion:NULL];
}];
}
Take out the removeFromSuperview.
This is how I am using the containment API. According to the docs is its correct.
[self.childViewController willMoveToParentViewController:nil];
[UIView animateWithDuration:0.25 animations:^{
self.childViewController.view.frame = ... // View Animation
} completion:^(BOOL finished) {
[self.childViewController.view removeFromSuperview]; // triggers `viewWillDisappear`
[self.childViewController removeFromParentViewController];
}];
I expect viewWillDisappear to be called before the animation begins and viewDidDisappear to be called after the animation completes. However, they are both called in quick succession after the animation is complete.
Moving [self.childViewController.view removeFromSuperview]; to the animation block fixes this, but the code looks wrong:
[self.childViewController willMoveToParentViewController:nil];
[UIView animateWithDuration:0.25 animations:^{
self.childViewController.view.frame = ... // View Animation
[self.childViewController.view removeFromSuperview]; // triggers `viewWillDisappear`
} completion:^(BOOL finished) {
[self.childViewController removeFromParentViewController];
}];
Does anyone know what the proper way to get viewWillDisappear to be called at the correct time is?
The answer was to use – beginAppearanceTransition:animated: & endAppearanceTransition
If you are implementing a custom container controller, use this method to tell the child that its views are about to appear or disappear. Do not invoke viewWillAppear:, viewWillDisappear:, viewDidAppear:, or viewDidDisappear: directly.
The corrected code:
[self.childViewController willMoveToParentViewController:nil];
[self.childViewController beginAppearanceTransition:NO animated:YES];
[UIView animateWithDuration:0.25 animations:^{
self.childViewController.view.frame = ...
} completion:^(BOOL finished) {
[self.childViewController.view removeFromSuperview];
[self.childViewController removeFromParentViewController];
[self.childViewController endAppearanceTransition];
}];
You are not notifying that the childViewController moved to the parentViewController.
[yourViewController didMoveToParentViewController:self]
And in [self.childViewController willMoveToParentViewController:nil]; You are not mentioning who will be the parentViewController. It is a good practice to indicate when the childViewController was added. Then iOS should know when to trigger viewWillDisappear
You are notifying that a viewController will move to a parentViewController but you are not indicating who is the parentViewController if you indicate which the parentViewController is your code will work as expected.
ViewWillDissappear will get fired when the view is ready to leave the parentView. This will happens in the next willLayoutSubviews execution of your parentViewController or viewController depending in the hierarchy of your viewControllers
[UIView animateWithDuration:0.25 animations:^{
[self.childViewController.view removeFromSuperview];
self.childViewController.view.frame = ... // View Animation
[self.view addSubView:self.childViewController.view];
} completion:^(BOOL finished) {
[self.childViewController didMoveToParentViewController:self];
// triggers `viewWillDisappear`
[self.childViewController removeFromParentViewController];
}];
Try that
Here is a Swift version.
It place beginAppearanceTransition in animation closure so you get similar behavior like UINavigationController: You can adjust animatable view properties in child view controller's viewWillDisappear and get animation automatically.
let animator = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 1) {
childViewController.willMove(toParentViewController: self)
childViewController.beginAppearanceTransition(false, animated: true)
childViewController.view.frame = ... // View Animation
}
animator.addCompletion { _ in
childViewController.view.removeFromSuperview()
childViewController.endAppearanceTransition()
childViewController.removeFromParentViewController()
}
animator.startAnimation()
Notice the endAppearanceTransition must be called after view.removeFromSuperview otherwise system will send duplicate viewWillDisappear viewDidDisappear methods to your child view controller.
I find myself in need of access to a viewcontroller from its view.
Here is the method
-(void)changePageView:(UIViewController*)newviewcont withtransitiontype:(int)t andtransitionspeed:(int)s
{
//Remove whatever view is currently loaded at index 0, this index is only to be used by "page" views
UIView *oldview = [self.view.subviews objectAtIndex:0];
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:s];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:YES];
[newviewcont viewWillAppear:YES];
//[oldview viewWillDisappear:YES];
[oldview removeFromSuperview];
[self.view insertSubview:newviewcont.view atIndex:0];
//[oldview viewDidDisappear:YES];
[newviewcont viewDidAppear:YES];
}
Basically, I am trying to write a generic view switch method that is called by the root controller to swap out subviewcontorllers views from the rootcontrollers view.
I pass in a subviewcontroller and am able to remove the current subview. But in order to do proper view switching animation i need access to the current views view controller. Is this the wrong approach and can it be done?
I added a member to the rootcontroller that hold onto the current sub view controller (currentController) and refers to it when a controller swap is done
-(void)changePageView:(UIViewController*)newviewcont withtransitiontype:(int)t andtransitionspeed:(int)s
{
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:s];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:YES];
[newviewcont viewWillAppear:YES];
[self.currentController viewWillDisappear:YES];
[self.currentController.view removeFromSuperview];
[self.view insertSubview:newviewcont.view atIndex:0];
[self.currentController viewDidDisappear:YES];
[newviewcont viewDidAppear:YES];
[UIView commitAnimations];
self.currentController = newviewcont;
}
The changeView() method belongs in the viewcontroller. It would solve you problem of having the view knowing about it's controller (which it shouldn't) and it makes more sense.
Also unless you are doing something fancy in changeView() that can't be done using the methods in a UIViewController object then you should just use it instead, if it is neccesary to implement your own view switching method then you can extend UIViewController instead of implemtning part of the view controlelr in your view.
my 2 cents :)
I believe your approach is wrong. You should look into UINavigationController I believe.