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.
Related
The Photo Sharing part is a ViewController I wrote, when I press the right item, the PhotoSharingViewController will appear animatedly.
Here is my code:
PhotoSharingViewController *vc = [[PhotoSharingViewController alloc] init];
[self addChildViewController:vc];
[self.view addSubview:vc.view];
[UIView animateWithDuration:0.3 animations:^{
vc.view.frame = CGRectMake(0, 0, WIDTH, WIDTH * 0.8);
} completion:^(BOOL finished) {
}];
However, I do not think it is a good way. I prefer to "present" the viewController, like UIAlertController, or UIActivityViewController. How could I do that, please?
You want to provide a custom transition. That way, when presentViewController is called, you get to provide the UIPresentationController as well as the animation. You are in complete charge of both where the presented view goes and how it animates to get there.
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.
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];}];
}
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.
How is it possible that I change the transition for presenting a modal view controller. Is it possible that the presenting transition is using the default UIModalTransitionStyle....
UIModalTransitionStyleCoverVertical = 0,
UIModalTransitionStyleFlipHorizontal,
UIModalTransitionStyleCrossDissolve,
UIModalTransitionStylePartialCurl,
but the dismiss transition is using the UIViewAnimationTransitionCurlUp transition. Important is that I don't want to use the UIModalTransitionStylePartialCurl it should be the CurlUp one.
Sadly the following code doesn't work:
[UIView transitionFromView:self.view toView:self.parentViewController.parentViewController.parentViewController.view duration:1.0 options:UIViewAnimationTransitionCurlUp completion:^(BOOL finished) {....}];
Maybe it has something to do that the view controller is displayed in modal mode.
It would be nice if someone can help.
It feels kludgy, but you can do this by animating the transition of adding your destination view controller's view, but on the completion of that, you immediately remove it again and then properly transition to it via the presentViewController (this time without animation, since the visual effect has already been rendered).
I do this in a subclassed custom UIStoryboardSegue (you didn't say NIBs or storyboard, but the concept is the same):
- (void)perform
{
UIViewController *src = self.sourceViewController;
UIViewController *dst = self.destinationViewController;
[UIView transitionWithView:src.navigationController.view
duration:0.75
options:UIViewAnimationOptionTransitionCurlUp | UIViewAnimationOptionCurveEaseInOut
animations:^{
[src.navigationController.view addSubview:dst.view];
}
completion:^(BOOL finished){
[dst.view removeFromSuperview];
[src presentViewController:dst animated:NO completion:nil];
}];
}
Clearly, if your source view controller doesn't have a navigation controller, you would replace those "src.navigationController.view" with just "src.view". But hopefully this gives you the idea.
And, anticipating the logical follow-up question, when dismissing the view controller, I have a button hooked up to an IBAction:
- (IBAction)doneButton:(id)sender
{
[UIView transitionWithView:self.view.superview
duration:0.75
options:UIViewAnimationOptionTransitionCurlDown
animations:^{
[self dismissViewControllerAnimated:NO completion:nil];
}
completion:nil];
}