Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
324 views
in Technique[技术] by (71.8m points)

IOS/Objective-C: Custom Segue Effect on Navigation Bar Compared with Show Segue

I have two view controllers where I use a regular Show Segue (Right to Left) to go in one direction and a custom segue (Left to Right) to go in the other direction. I don't think I should do an unwind because neither VC is subordinate to other and using these segues means one navigation controller manages things.

In the upper left hand corner of both VCs I have a common BarButton containing a profile photo.

When using the regular Right to Left Segue, the profile photo on the bar button remains unchanged. This looks great as the rest of the screen moves in but the element common to both, the profile photo stays in place.

In the other direction (Left to Right), however, using the custom segue, the entire VC screen including the navigation bar swoops in and you basically see the profile photo come in from the left edge before resting in the normal left bar button position where the same photo was a moment earlier. This looks bad.

Is there any way to force a common element in the navigation bar to stay in place during a custom segue to better mimic the behavior of the system show segue?

Thanks in advance for any suggestions.

Here is my code for the custom segue:

#import "customSegue.h"
#import "QuartzCore/QuartzCore.h"

@implementation customSegue

-(void)perform {

    UIViewController *destinationController = (UIViewController*)[self destinationViewController];

    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    CGFloat animationDuration = .40;  
    transition.duration = animationDuration;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
    transition.type = kCATransitionMoveIn;  
    transition.subtype = kCATransitionFromLeft;  

    [sourceViewController.navigationController.view.layer addAnimation:transition
                                                                forKey:kCATransition];

    UIColor *previousWindowBackgroundColor = sourceViewController.view.window.backgroundColor;

    sourceViewController.view.window.backgroundColor = destinationController.view.backgroundColor;


    [sourceViewController.navigationController pushViewController:destinationController animated:NO];
    // switch the window color back after the transition duration from above
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(animationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // make sure we still have a handle on the destination controller
        if (destinationController) {
            destinationController.view.window.backgroundColor = previousWindowBackgroundColor;
        }
    });

}

@end
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Hello again @user6631314

I don't think you're going to get what you want by applying using a CATransition and attaching it to the navigationController view's layer.

Instead I would recommend making the presentingViewController a delegate for UINavigationController and rolling your own logic for the LTR push (it will be a lot smoother with the navigation bar and you should no longer have to worry about that black bar during the transition that my previous response helped you resolve/workaround).

So to do this you'll need to set the presenting view controller (or some coordinator view controller) to be the UINavigationControllerDelegate and implement this method:

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                  animationControllerForOperation:(UINavigationControllerOperation)operation
                                               fromViewController:(UIViewController*)fromVC
                                                 toViewController:(UIViewController*)toVC

Within that method you'll want to check if the operation is a UINavigationControllerOperationPush and if so return a subclass of NSObject that conforms to the UIViewControllerAnimatedTransitioning protocol (otherwise return nil to allow all other navigation operations to be standard). Within that class will be where you handle the custom navigation controller push animation override.

The basic logic for the LTR push animation is that you want to start the toView off the screen to the left, then animate it in so that it's completely onscreen after your animation duration (0.4 from your code) -- therefore start the x position offset to the negative value of the view's width (so it's completely offscreen) then during the animation set the x position to 0 (or you could just += the view's width).

Here's the example of what the current custom segue implementation looks like (notice the navigation bar slides over as well, which is the issue you're posting about here):

enter image description here

And the transition by using the custom animation controller:

enter image description here

Here's the complete code:

#import "ViewController.h"
#import "LTRPushAnimator.h"

@interface ViewController () < UINavigationControllerDelegate>
@property (strong, nullable) UIView *profileView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationController.delegate = self;
    self.navigationItem.hidesBackButton = YES;
    self.profileView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
    UIImageView *profileImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Homer.jpg"]];
    [profileImageView setFrame:CGRectMake(0, 0, 40, 40)];
    profileImageView.layer.cornerRadius = 20.0f;
    profileImageView.layer.masksToBounds = YES;
    profileImageView.clipsToBounds = YES;
    profileImageView.layer.borderColor = [UIColor blackColor].CGColor;
    profileImageView.layer.borderWidth = 1.0f;
    [self.profileView addSubview:profileImageView];
    UIBarButtonItem *lbi = [[UIBarButtonItem alloc] initWithCustomView:self.profileView];
    self.navigationItem.leftBarButtonItem = lbi;
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
}

- (IBAction)pushFakeViewController:(id)sender {
    UIViewController *fakeViewController = [[UIViewController alloc] init];
    fakeViewController.view.backgroundColor = [UIColor redColor];
    UIBarButtonItem *lbi = [[UIBarButtonItem alloc] initWithCustomView:self.profileView];
    fakeViewController.navigationItem.leftBarButtonItem = lbi;
    fakeViewController.navigationItem.hidesBackButton = YES;
    [self.navigationController pushViewController:fakeViewController animated:YES];
    // this is just in here to pop back to the root view controller since we removed the back button, it can be removed obviously
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.navigationController popViewControllerAnimated:YES];
    });
}

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                  animationControllerForOperation:(UINavigationControllerOperation)operation
                                               fromViewController:(UIViewController*)fromVC
                                                 toViewController:(UIViewController*)toVC
{
    if (operation == UINavigationControllerOperationPush) {
        return [[LTRPushAnimator alloc] init];
    }
    // otherwise standard animation
    return nil;
}

@end

LTRPushAnimator.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface LTRPushAnimator : NSObject <UIViewControllerAnimatedTransitioning>

@end

NS_ASSUME_NONNULL_END

LTRPushAnimator.m

#import "LTRPushAnimator.h"

@implementation LTRPushAnimator

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
    return 0.4;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    [self _animatePushByFrameWithContext:transitionContext];
}

- (void)_animatePushByFrameWithContext:(id<UIViewControllerContextTransitioning>)transitionContext {
    UIViewController *toViewController   = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    CGRect toVCFrame = toViewController.view.frame;
    CGFloat viewWidth = toVCFrame.size.width;
    toVCFrame.origin.x -= viewWidth;
    [toViewController.view setFrame:toVCFrame];
    [[transitionContext containerView] addSubview:toViewController.view];
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        CGRect finalVCFrame = toViewController.view.frame;
        finalVCFrame.origin.x = 0;
        [toViewController.view setFrame:finalVCFrame];
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
}

@end

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...