ios - كيفية تغيير الرسوم المتحركة Push و Pop في تطبيق قائم على التنقل




animation uinavigationcontroller (20)

كنت في الآونة الأخيرة أحاول القيام بشيء مماثل. قررت أنني لم أحب الرسوم المتحركة المنزلقة لـ UINavigationController ، لكنني أيضًا لم أرغب في القيام بالرسوم المتحركة التي تمنحك UIView كالتجعد أو أي شيء من هذا القبيل. كنت أرغب في عمل تلاشي عرضي بين المشاهدات عند الدفع أو النقر.

وتنطوي المشكلة هناك على حقيقة أن العرض يقوم حرفياً بإزالة العرض أو عرضه على الجزء العلوي من العرض الحالي ، لذا لا يعمل الخفوت. الحل الذي جئت إليه للمشاركة في عرض ملفي الجديد وإضافته كعرض فرعي إلى العرض العلوي الحالي على مكدس UIViewController. أقوم بإضافته مع ألفا من 0 ، ثم القيام بالتداخل. عند انتهاء تسلسل الرسوم المتحركة ، أقوم بدفع العرض على المكدس بدون تحريكه. ثم أعود إلى topView القديمة وتنظيف الأشياء التي قد تغيرت.

انها أكثر تعقيدا من ذلك ، لأنك لديك عناصر الملاحة لديك لتعديل لجعل الانتقال تبدو صحيحة. وأيضًا ، في حالة إجراء أي تدوير ، يتعين عليك عندئذٍ ضبط أحجام الإطارات عند إضافة العروض كمشاهدات فرعية حتى تظهر بشكل صحيح على الشاشة. إليك بعض التعليمات البرمجية التي استخدمتها. أنا subclassed UINavigationController وطغت على الدفع وطرق البوب.

-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
      UIViewController *currentViewController = [self.viewControllers lastObject];
      //if we don't have a current controller, we just do a normal push
      if(currentViewController == nil)
      {
         [super pushViewController:viewController animated:animated];
         return;
      }
      //if no animation was requested, we can skip the cross fade
      if(!animation)
      {
         [super pushViewController:viewController animated:NO];
         return;
      }
      //start the cross fade.  This is a tricky thing.  We basically add the new view
//as a subview of the current view, and do a cross fade through alpha values.
//then we push the new view on the stack without animating it, so it seemlessly is there.
//Finally we remove the new view that was added as a subview to the current view.

viewController.view.alpha = 0.0;
//we need to hold onto this value, we'll be releasing it later
    NSString *title = [currentViewController.title retain];

//add the view as a subview of the current view
[currentViewController.view addSubview:viewController.view];
[currentViewController.view bringSubviewToFront:viewController.view];
UIBarButtonItem *rButtonItem = currentViewController.navigationItem.rightBarButtonItem;
UIBarButtonItem *lButtonItem = currentViewController.navigationItem.leftBarButtonItem;

NSArray *array = nil;

//if we have a right bar button, we need to add it to the array, if not, we will crash when we try and assign it
//so leave it out of the array we are creating to pass as the context.  I always have a left bar button, so I'm not checking to see if it is nil. Its a little sloppy, but you may want to be checking for the left BarButtonItem as well.
if(rButtonItem != nil)
    array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,rButtonItem,nil];
else {
    array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,nil];
}

//remove the right bar button for our transition
[currentViewController.navigationItem setRightBarButtonItem:nil animated:YES];
//remove the left bar button and create a backbarbutton looking item
//[currentViewController.navigationItem setLeftBarButtonItem:nil animated:NO];

//set the back button
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:title style:kButtonStyle target:self action:@selector(goBack)];
[currentViewController.navigationItem setLeftBarButtonItem:backButton animated:YES];
[viewController.navigationItem setLeftBarButtonItem:backButton animated:NO];
[backButton release];

[currentViewController setTitle:viewController.title];

[UIView beginAnimations:@"push view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePushDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[viewController.view setAlpha: 1.0];
[UIView commitAnimations];
}

-(void)animationForCrossFadePushDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{

UIViewController *c = [(NSArray*)context objectAtIndex:0];
UIViewController *n = [(NSArray*)context objectAtIndex:1];
NSString *title     = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:3];
UIBarButtonItem *r = nil;
//not all views have a right bar button, if we look for it and it isn't in the context,
//we'll crash out and not complete the method, but the program won't crash.
//So, we need to check if it is there and skip it if it isn't.
if([(NSArray *)context count] == 5)
    r = [(NSArray *)context objectAtIndex:4];

//Take the new view away from being a subview of the current view so when we go back to it
//it won't be there anymore.
[[[c.view subviews] lastObject] removeFromSuperview];
[c setTitle:title];
[title release];
//set the search button
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//set the next button
if(r != nil)
    [c.navigationItem setRightBarButtonItem:r animated:NO];


[super pushViewController:n animated:NO];

 }

كما ذكرت في التعليمة البرمجية ، لدي دائماً عنصر زر شريط الأيسر ، لذلك لا أتحقق مما إذا كان لا شيء قبل وضعه في الصفيف الذي أقوم بتمريره كسياق لمفوض الرسوم المتحركة. إذا قمت بذلك ، قد ترغب في إجراء هذا التحقق.

كانت المشكلة التي وجدتها أنه إذا تعطلت على الإطلاق في طريقة التفويض ، فلن يؤدي ذلك إلى تعطل البرنامج. يتوقف الأمر فقط عن إتمام المندوب ولكنك لا تحصل على أي نوع من التحذير.
لذلك ، بما أنني كنت أقوم بعملية التنظيف في روتين المندوب ، كان ذلك يسبب بعض السلوك المرئي الغريب حيث لم ينته من عملية التنظيف.

الزر الخلفي الذي أقوم بإنشائه يستدعي طريقة "goBack" ، وهذه الطريقة تستدعي روتين البوب ​​فقط.

-(void)goBack
{ 
     [self popViewControllerAnimated:YES];
}

أيضا ، هنا هو روتين البوب ​​الخاص بي.

-(UIViewController *)popViewControllerAnimated:(BOOL)animated
{
    //get the count for the number of viewControllers on the stack
int viewCount = [[self viewControllers] count];
//get the top view controller on the stack
UIViewController *topViewController = [self.viewControllers objectAtIndex:viewCount - 1];
//get the next viewController after the top one (this will be the new top one)
UIViewController *newTopViewController = [self.viewControllers objectAtIndex:viewCount - 2];

//if no animation was requested, we can skip the cross fade
if(!animated)
{
    [super popViewControllerAnimated:NO];
            return topViewController;
}



//start of the cross fade pop.  A bit tricky.  We need to add the new top controller
//as a subview of the curent view controler with an alpha of 0.  We then do a cross fade.
//After that we pop the view controller off the stack without animating it.
//Then the cleanup happens: if the view that was popped is not released, then we
//need to remove the subview we added and change some titles back.
newTopViewController.view.alpha = 0.0;
[topViewController.view addSubview:newTopViewController.view];
[topViewController.view bringSubviewToFront:newTopViewController.view];
NSString *title = [topViewController.title retain];
UIBarButtonItem *lButtonItem = topViewController.navigationItem.leftBarButtonItem;
UIBarButtonItem *rButtonItem = topViewController.navigationItem.rightBarButtonItem;

//set the new buttons on top of the current controller from the new top controller
if(newTopViewController.navigationItem.leftBarButtonItem != nil)
{
    [topViewController.navigationItem setLeftBarButtonItem:newTopViewController.navigationItem.leftBarButtonItem animated:YES];
}
if(newTopViewController.navigationItem.rightBarButtonItem != nil)
{
    [topViewController.navigationItem setRightBarButtonItem:newTopViewController.navigationItem.rightBarButtonItem animated:YES];
}

[topViewController setTitle:newTopViewController.title];
//[topViewController.navigationItem.leftBarButtonItem setTitle:newTopViewController.navigationItem.leftBarButtonItem.title];

NSArray *array = nil;
if(rButtonItem != nil)
    array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,rButtonItem,nil];
else {
    array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,nil];
}


[UIView beginAnimations:@"pop view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePopDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[newTopViewController.view setAlpha: 1.0];
[UIView commitAnimations];
return topViewController;

 }

 -(void)animationForCrossFadePopDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
 {

UIViewController *c = [(NSArray *)context objectAtIndex:0];
//UIViewController *n = [(NSArray *)context objectAtIndex:1];
NSString *title = [(NSArray *)context objectAtIndex:1];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *r = nil;



//Not all views have a right bar button.  If we look for one that isn't there
// we'll crash out and not complete this method, but the program will continue.
//So we need to check if it is therea nd skip it if it isn't.
if([(NSArray *)context count] == 4)
    r = [(NSArray *)context objectAtIndex:3];

//pop the current view from the stack without animation
[super popViewControllerAnimated:NO];

//if what was the current veiw controller is not nil, then lets correct the changes
//we made to it.
if(c != nil)
{
    //remove the subview we added for the transition
    [[c.view.subviews lastObject] removeFromSuperview];
    //reset the title we changed
    c.title = title;
    [title release];
    //replace the left bar button that we changed
    [c.navigationItem setLeftBarButtonItem:l animated:NO];
    //if we were passed a right bar button item, replace that one as well
    if(r != nil)
        [c.navigationItem setRightBarButtonItem:r animated:NO];
    else {
        [c.navigationItem setRightBarButtonItem:nil animated:NO];
    }


 }
}

هذا الى حد كبير ذلك. ستحتاج إلى بعض الشفرات الإضافية إذا كنت تريد تنفيذ التناوب. ستحتاج إلى تعيين حجم الإطار لمشاهدتك التي تضيفها كمشاهدات فرعية قبل عرضها ، وإلا ستواجه مشكلات في الاتجاه العام ، ولكن في المرة الأخيرة التي شاهدت فيها العرض السابق كانت صورة. لذا ، يمكنك إضافته كمشهد فرعي وتختفي فيه ولكنه يظهر كصورة ، ثم عندما نبدأ دون رسوم متحركة ، فإن نفس العرض ، ولكن المشهد الموجود في التكديس ، هو الآن منظر طبيعي. كل شيء يبدو غير تقليدي بعض الشيء. يختلف تطبيق الجميع للتناوب قليلاً ، لذا لم أضمِ شفرتي لذلك هنا.

آمل أن يساعد بعض الناس. لقد نظرت في كل شيء لشيء من هذا القبيل ولم أستطع العثور على أي شيء. I don't think this is the perfect answer, but it is working real well for me at this point.

لدي تطبيق قائم على التنقل وأريد تغيير الرسوم المتحركة الخاصة بالرسم المتحرك البسيط. كيف لي أن افعل ذلك؟


تذكر أنه في Swift ، فإن الإضافة هي بالتأكيد أصدقائك!

public extension UINavigationController {

    /**
     Pop current view controller to previous view controller.

     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func pop(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.popViewControllerAnimated(false)
    }

    /**
     Push a new view controller on the view controllers's stack.

     - parameter vc:       view controller to push.
     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func push(viewController vc: UIViewController, transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.pushViewController(vc, animated: false)
    }

    private func addTransition(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        let transition = CATransition()
        transition.duration = duration
        transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        transition.type = type
        self.view.layer.addAnimation(transition, forKey: nil)
    }

}

ردًا علىMagnus ، عندها فقط لـ Swift (2.0)

    let transition = CATransition()
    transition.duration = 0.5
    transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    transition.type = kCATransitionPush
    transition.subtype = kCATransitionFromTop
    self.navigationController!.view.layer.addAnimation(transition, forKey: nil)
    let writeView : WriteViewController = self.storyboard?.instantiateViewControllerWithIdentifier("WriteView") as! WriteViewController
    self.navigationController?.pushViewController(writeView, animated: false)

بعض sidenotes:

يمكنك القيام بذلك أيضًا مع Segue ، فقط قم بتطبيق ذلك في prepareForSegue أو يجب أن يكون shouldPerformSegueWithIdentifier . ومع ذلك ، فإن هذا سيبقي الرسوم المتحركة الافتراضية فيه أيضًا. لإصلاح هذا ، عليك الانتقال إلى لوحة العمل ، والنقر على Segue ، وإلغاء تحديد المربع "Animates". ولكن هذا سيحد من تطبيقك على IOS 9.0 وما فوق (على الأقل عندما أقوم بذلك في Xcode 7).

عند القيام بعملية "segue" ، يجب استبدال آخر سطرين بـ:

self.navigationController?.popViewControllerAnimated(false)

على الرغم من أنني أخطأ ، فإنه يتجاهل ذلك.


It's very simple

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft

كيفية تغيير الرسوم المتحركة Push و Pop في تطبيق قائم على التنقل ...

لعام 2018 ، "الجواب النهائي!"

Preamble لنفترض أنك جديد على تطوير iOS ، ربما من Android. ومن المربك للغاية ، أن شركة آبل تقدم تحويلين (2) يمكن استخدامها بسهولة. هذه هي: "crossfade" و "flip". الآن ، أكثر عمليتي انتقال غير مجدية في كل الحوسبة هما "crossfade" و "flip" - التي لا أحد يستخدمها. إذا كنت تريد إجراء معظم التحولات العادية ، مثل "انزلق" ، فعليك أن تفعل كمية كبيرة من العمل. هذا العمل ، هو موضح في هذا المنصب!

  • أولا ، إذا كنت ترغب في استخدام واحد من اثنين من الرسوم المتحركة التي تقدمها شركة أبل (التداخل ، تقلب) ، فإنه من السهل - استخدام الحل من PeterDeWeese أعلاه.

  • ثانيًا ، هناك عملية QuickTrack السريعة والقذرة القديمة. وهذا موضح بالتفصيل هنا . حقا لا يعمل ، وليس حلا واقعيا.

خلاف ذلك ، من المستغرب ، لديك لجعل الجهد للقيام بعملية انتقال مخصصة كاملة .

لاعادتها:

حتى إذا كنت ترغب فقط في الانتقال الأبسط والأكثر شيوعًا للانتقال / التحريك ، فأنت بحاجة إلى تنفيذ انتقال مخصص كامل للأفضل أو الأسوأ.

هيريس كيفية القيام بذلك ...

1. أنت بحاجة إلى UIViewControllerAnimatedTransitioning مخصصة

  1. أنت في حاجة إلى popStyle خاص بك مثل popStyle . (هل هو الظهور ، أو تفرقع؟)

  2. يجب أن تقوم بتضمين animateTransition (تافه) والمكالمة الرئيسية ، animateTransition

  3. في الواقع سوف تحتاج إلى كتابة animateTransition مختلفين من أجل animateTransition داخل. واحد للدفع ، وواحد للبوب. ربما اسم لهم animatePush و animatePop . داخل animateTransition ، مجرد فرع على popStyle إلى الروتينين

  4. المثال التالي يقوم بحركة / تحريك بسيط

  5. في animatePush و animatePop . يجب أن تحصل على "من العرض" و "لعرض". (كيف يتم إظهاره في المثال.)

  6. والشيء الرئيسي الذي يجب عليك فعله هو ، في الواقع ، addSubview لعرض "جديد" الجديد.

  7. يجب عليك استدعاء completeTransition في نهاية أنيمي الخاص بك

وبالتالي ..

  class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning {

        var popStyle: Bool = false

        func transitionDuration(
            using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.20
        }

        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

            if popStyle {

                animatePop(using: transitionContext)
                return
            }

            let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!

            let f = transitionContext.finalFrame(for: tz)

            let fOff = f.offsetBy(dx: f.width, dy: 55)
            tz.view.frame = fOff

            transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view)

            UIView.animate(
                withDuration: transitionDuration(using: transitionContext),
                animations: {
                    tz.view.frame = f
            }, completion: {_ in 
                    transitionContext.completeTransition(true)
            })
        }

        func animatePop(using transitionContext: UIViewControllerContextTransitioning) {

            let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!

            let f = transitionContext.initialFrame(for: fz)
            let fOffPop = f.offsetBy(dx: f.width, dy: 55)

            transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view)

            UIView.animate(
                withDuration: transitionDuration(using: transitionContext),
                animations: {
                    fz.view.frame = fOffPop
            }, completion: {_ in 
                    transitionContext.completeTransition(true)
            })
        }
    }

وثم ...

2. استخدمها في جهاز العرض الخاص بك.

لاحظ أنه يجب عليك القيام بذلك فقط في وحدة تحكم العرض "الأول".

الذي تدخله في القمة ، لا تفعل شيئًا. سهل.

لذا فصلك ...

class SomeScreen: UIViewController {
}

يصبح ...

class FrontScreen: UIViewController,
        UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {

    let simpleOver = SimpleOver()


    override func viewDidLoad() {

        super.viewDidLoad()
        navigationController?.delegate = self
    }

    func navigationController(
        _ navigationController: UINavigationController,
        animationControllerFor operation: UINavigationControllerOperation,
        from fromVC: UIViewController,
        to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {

        simpleOver.popStyle = (operation == .pop)
        return simpleOver
    }
}

هذا هو.

دفع والبوب ​​بالضبط كالمعتاد ، أي تغيير. يدفع ...

let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
          .instantiateViewController(withIdentifier: "nextScreenStoryboardID")
          as! NextScreen
navigationController?.pushViewController(n, animated: true)

ولإعدادها ، يمكنك إذا كنت ترغب في القيام بذلك فقط على الشاشة التالية:

class NextScreen: TotallyOrdinaryUIViewController {

    @IBAction func userClickedBackOrDismissOrSomethingLikeThat() {

        navigationController?.popViewController(animated: true)
    }
}

انها "بهذه البساطة": O


See my answer to this question for a way to do it in far fewer lines of code. This method allows you to animate a pseudo-"Push" of a new view controller any way you like, and when the animation is done it sets up the Navigation Controller just as if you had used the standard Push method. My example lets you animate either a slide-in from the left or from the right. Code repeated here for convenience:

-(void) showVC:(UIViewController *) nextVC rightToLeft:(BOOL) rightToLeft {
    [self addChildViewController:neighbor];
    CGRect offscreenFrame = self.view.frame;
    if(rightToLeft) {
        offscreenFrame.origin.x = offscreenFrame.size.width * -1.0;
    } else if(direction == MyClimbDirectionRight) {
        offscreenFrame.origin.x = offscreenFrame.size.width;
    }
    [[neighbor view] setFrame:offscreenFrame];
    [self.view addSubview:[neighbor view]];
    [neighbor didMoveToParentViewController:self];
    [UIView animateWithDuration:0.5 animations:^{
        [[neighbor view] setFrame:self.view.frame];
    } completion:^(BOOL finished){
        [neighbor willMoveToParentViewController:nil];
        [neighbor.view removeFromSuperview];
        [neighbor removeFromParentViewController];
        [[self navigationController] pushViewController:neighbor animated:NO];
        NSMutableArray *newStack = [[[self navigationController] viewControllers] mutableCopy];
        [newStack removeObjectAtIndex:1]; //self, just below top
        [[self navigationController] setViewControllers:newStack];
    }];
}

للدفع

CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;

[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController pushViewController:ViewControllerYouWantToPush animated:NO];

للبوب

CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;

[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController popViewControllerAnimated:NO];

هكذا استطعت دائمًا إتمام هذه المهمة.

للدفع:

MainView *nextView=[[MainView alloc] init];
[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[nextView release];

للبوب:

[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
[UIView commitAnimations];

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:0.375];
[self.navigationController popViewControllerAnimated:NO];
[UIView commitAnimations];


لا أزال أتلقى الكثير من التعليقات من هذا ، لذا سأعمل على تحديثه لاستخدام مقاطع الرسوم المتحركة ، وهي طريقة Apple الموصى بها لإجراء الرسوم المتحركة على أي حال.

للدفع:

MainView *nextView = [[MainView alloc] init];
[UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [self.navigationController pushViewController:nextView animated:NO];
                             [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
                         }];

للبوب:

[UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
                         }];
[self.navigationController popViewControllerAnimated:NO];

فعلت ما يلي وهو يعمل بشكل جيد .. وهو بسيط وسهل الفهم ..

CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
//transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[[self navigationController] popViewControllerAnimated:NO];

ونفس الشيء للدفع ..

الإصدار 3.0 Swift:

let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey: nil)
_ = self.navigationController?.popToRootViewController(animated: false)

مجرد استخدام:

ViewController *viewController = [[ViewController alloc] init];

UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
navController.navigationBarHidden = YES;

[self presentViewController:navController animated:YES completion: nil];
[viewController release];
[navController release];

نظرًا لأن هذه هي أفضل نتيجة على Google ، فكرت أن أشارك ما أعتقد أنه أكثر طريقة منطقية. وهو استخدام واجهة برمجة تطبيقات النقل iOS 7+. لقد نفذت هذا لنظام iOS 10 مع Swift 3.

من السهل دمج هذا مع كيفية تحريك UINavigationController بين وحدتي عرض إذا قمت بإنشاء فئة فرعية من UINavigationController وإرجاع مثيل لفئة تتوافق مع بروتوكول UIViewControllerAnimatedTransitioning .

على سبيل المثال ، هنا هو الفئة الفرعية UINavigationController بي:

class NavigationController: UINavigationController {
    init() {
        super.init(nibName: nil, bundle: nil)

        delegate = self
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension NavigationController: UINavigationControllerDelegate {

    public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return NavigationControllerAnimation(operation: operation)
    }

}

يمكنك ملاحظة أنني قمت بتعيين UINavigationControllerDelegate لنفسه ، وفي ملحق على الفئة الفرعية الخاصة بي ، قمت بتطبيق الطريقة في UINavigationControllerDelegate التي تسمح لك بإرجاع وحدة تحكم حركة مخصصة (بمعنى ، NavigationControllerAnimation ). ستحل وحدة التحكم المخصصة في الرسوم المتحركة محل الرسوم المتحركة الخاصة بالسهم.

ربما تتساءل عن سبب انتقالي في العملية إلى مثيل NavigationControllerAnimation عبر مُهيئتها. أفعل هذا حتى في تنفيذ NavigationControllerAnimation من UIViewControllerAnimatedTransitioning البروتوكول وأنا أعلم ما هي العملية (أي "دفع" أو "البوب"). هذا يساعد على معرفة أي نوع من الرسوم المتحركة يجب أن أفعله. معظم الوقت ، تريد إجراء حركة مختلفة اعتمادا على العملية.

والباقي هو المعيار الجميل. قم بتنفيذ الدالتين المطلوبتين في بروتوكول UIViewControllerAnimatedTransitioning بالطريقة التي تريدها:

class NavigationControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning {

    let operation: UINavigationControllerOperation

    init(operation: UINavigationControllerOperation) {
        self.operation = operation

        super.init()
    }

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }

    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
            let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return }
        let containerView = transitionContext.containerView

        if operation == .push {
            // do your animation for push
        } else if operation == .pop {
            // do your animation for pop
        }
    }
}

من المهم أن تتذكر أنه بالنسبة لكل نوع من أنواع العمليات المختلفة (على سبيل المثال ، "الدفع" أو "البوب") ، ستكون وحدات التحكم من و إلى العرض مختلفة. عندما تكون في عملية دفع ، سيكون جهاز التحكم في العرض هو الذي يتم دفعه. عندما تكون في عملية منبثقة ، ستكون وحدة التحكم في العرض هي تلك التي يتم نقلها إلى ، وستكون وحدة التحكم في العرض هي تلك التي يتم بروزها.

أيضًا ، يجب إضافة وحدة التحكم في العرض كعرض فرعي containerView في سياق النقل.

عند اكتمال الرسوم المتحركة ، يجب عليك الاتصال transitionContext.completeTransition(true) . إذا كنت تقوم بفترة انتقالية تفاعلية ، completeTransition(didComplete: Bool) إلى إعادة Bool ديناميكيًا completeTransition(didComplete: Bool) ، اعتمادًا على اكتمال النقل في نهاية الرسم المتحرك.

أخيرًا ( قراءة اختيارية ) ، قد ترغب في معرفة كيف أجريت النقل الذي كنت أعمل عليه. هذا الكود هو أكثر حماسية قليلاً وكتبته بسرعة ، لذا لن أقول إنه رمز متحرك رائع ، لكنه لا يزال يعرض كيفية عمل جزء الرسوم المتحركة.

المنجم كان انتقال بسيط حقا. أردت تقليد نفس الرسوم المتحركة التي عادةً ما يقوم بها UINavigationController ، ولكن بدلاً من الرسوم المتحركة "الصفحة التالية في الجزء العلوي" ، أردت تنفيذ رسم متحرك 1: 1 لوحدة تحكم العرض القديمة بعيدًا في نفس الوقت كعرض جديد يظهر تحكم. هذا له تأثير جعل وحدات التحكم عرض اثنين تبدو كما لو كانت مثبتة إلى بعضها البعض.

بالنسبة لعملية الدفع ، يتطلب ذلك أولاً تعيين مصدر عرض toViewController على شاشة x-axis off ، مضيفًا إليه كعرض فرعي containerView ، origin.x على الشاشة من خلال تعيين ذلك origin.x إلى الصفر. في نفس الوقت ، أقوم fromViewController عرض fromViewController بعيدًا عن طريق تحديد origin.x من على الشاشة:

toViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.size.width, dy: 0.0)

containerView.addSubview(toViewController.view)

UIView.animate(withDuration: transitionDuration(using: transitionContext),
               delay: 0,
               options: [ UIViewAnimationOptions.curveEaseOut ],
               animations: {
                toViewController.view.frame = containerView.bounds
                fromViewController.view.frame = containerView.bounds.offsetBy(dx: -containerView.frame.size.width, dy: 0)
},
               completion: { (finished) in
                transitionContext.completeTransition(true)
})

عملية البوب ​​هي في الأساس معكوس. قم بإضافة toViewController كعرض toViewController من containerView ، وتحريك بعيدا fromViewController إلى اليمين كما كنت تحريك في toViewController من اليسار:

containerView.addSubview(toViewController.view)

UIView.animate(withDuration: transitionDuration(using: transitionContext),
               delay: 0,
               options: [ UIViewAnimationOptions.curveEaseOut ],
               animations: {
                fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0)
                toViewController.view.frame = containerView.bounds
},
               completion: { (finished) in
                transitionContext.completeTransition(true)
})

وهنا جوهر مع ملف سريع كله:

https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be


I found a mildly recursive way to do this that works for my purposes. I have an instance variable BOOL that I use to block the normal popping animation and substitute my own non-animated pop message. The variable is initially set to NO. When the back button is tapped, the delegate method sets it to YES and sends a new non-animated pop message to the nav bar, thereby calling the same delegate method again, this time with the variable set to YES. With the variable is set to YES, the delegate method sets it to NO and returns YES to allow the non-animated pop occur. After the second delegate call returns, we end up back in the first one, where NO is returned, blocking the original animated pop! It's actually not as messy as it sounds. My shouldPopItem method looks like this:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item 
{
    if ([[navigationBar items] indexOfObject:item] == 1) 
    {
        [expandedStack restack];    
    }

    if (!progPop) 
    {
        progPop = YES;
        [navBar popNavigationItemAnimated:NO];
        return NO;
    }
    else 
    {
        progPop = NO;
        return YES;
    }
}

تناسبني.


يعد استخدام المكالمات الخاصة فكرة سيئة لأن Apple لم تعد توافق على التطبيقات التي تفعل ذلك. ربما يمكنك تجربة هذا:

//Init Animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.50];


[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.navigationController.view cache:YES];

//Create ViewController
MyViewController *myVC = [[MyViewController alloc] initWith...];

[self.navigationController pushViewController:myVC animated:NO];
[myVC release];

//Start Animation
[UIView commitAnimations];

Realising this is an old question. I still would like to post this answer, as I had some problems popping several viewControllers with the proposed answers. My solution is to subclass UINavigationController and override all the pop and push methods.

FlippingNavigationController.h

@interface FlippingNavigationController : UINavigationController

@end

FlippingNavigationController.m:

#import "FlippingNavigationController.h"

#define FLIP_DURATION 0.5

@implementation FlippingNavigationController

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [UIView transitionWithView:self.view
                      duration:animated?FLIP_DURATION:0
                       options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromRight
                    animations:^{ [super pushViewController:viewController
                                                   animated:NO]; }
                    completion:nil];
}

- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
    return [[self popToViewController:[self.viewControllers[self.viewControllers.count - 2]]
                             animated:animated] lastObject];
}

- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
{
    return [self popToViewController:[self.viewControllers firstObject]
                            animated:animated];
}

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    __block NSArray* viewControllers = nil;

    [UIView transitionWithView:self.view
                      duration:animated?FLIP_DURATION:0
                       options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromLeft
                    animations:^{ viewControllers = [super popToViewController:viewController animated:NO]; }
                    completion:nil];

    return viewControllers;
}

@end

Have a look at ADTransitionController , a drop in replacement for UINavigationController with custom transition animations (its API matches the API of UINavigationController) that we created at Applidium.

You can use different pre-defined animations for push and pop actions such as Swipe , Fade , Cube , Carrousel , Zoom and so on.


Here is how I have done the same in Swift:

For Push:

    UIView.animateWithDuration(0.75, animations: { () -> Void in
        UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
        self.navigationController!.pushViewController(nextView, animated: false)
        UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromRight, forView: self.navigationController!.view!, cache: false)
    })

For Pop:

I actually did this a little differently to some of the responses above - but as I am new to Swift development, it might not be right. I have overridden viewWillDisappear:animated: and added the pop code in there:

    UIView.animateWithDuration(0.75, animations: { () -> Void in
        UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
        UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false)
    })

    super.viewWillDisappear(animated)

I am not aware of any way you can change the transition animation publicly.

If the "back" button is not necessary you should use modal view controllers to have the "push from bottom" / "flip" / "fade" / (≥3.2)"page curl" transitions.

On the private side, the method -pushViewController:animated: calls the undocumented method -pushViewController:transition:forceImmediate: , so eg if you want a flip-from-left-to-right transition, you can use

[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];

You can't change the "pop" transition this way, however.


Based on answer updated for swift 4

For push UIViewController

let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "yourViewController") as! yourViewController
    UIView.animate(withDuration: 0.75, animations: {() -> Void in
    UIView.setAnimationCurve(.easeInOut)
    self.navigationController?.pushViewController(terms, animated: true)
    UIView.setAnimationTransition(.flipFromRight, for: (self.navigationController?.view)!, cache: false)
})

For Pop

UIView.animate(withDuration: 0.75, animations: {() -> Void in
    UIView.setAnimationCurve(.easeInOut)
    UIView.setAnimationTransition(.flipFromLeft, for: (self.navigationController?.view)!, cache: false)
})
navigationController?.popViewController(animated: false)

While all the answers here are great and most work very well, there is a slightly simpler method which achieves the same effect...

For Push:

  NextViewController *nextViewController = [[NextViewController alloc] init];

  // Shift the view to take the status bar into account 
  CGRect frame = nextViewController.view.frame;
  frame.origin.y -= 20;
  frame.size.height += 20;
  nextViewController.view.frame = frame;

  [UIView transitionFromView:self.navigationController.topViewController.view toView:nextViewController.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished) {
    [self.navigationController pushViewController:nextViewController animated:NO];
  }];

For Pop:

  int numViewControllers = self.navigationController.viewControllers.count;
  UIView *nextView = [[self.navigationController.viewControllers objectAtIndex:numViewControllers - 2] view];

  [UIView transitionFromView:self.navigationController.topViewController.view toView:nextView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
    [self.navigationController popViewControllerAnimated:NO];
  }];}

في برنامج التحويل البرمجي LLVM 3.0 في Xcode 4.2 يمكنك منع التحذير كما يلي:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

إذا كنت تحصل على الخطأ في عدة أماكن ، وترغب في استخدام نظام الماكرو C لإخفاء pragmas ، فيمكنك تعريف الماكرو لتسهيل منع التحذير:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

يمكنك استخدام الماكرو مثل هذا:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

إذا كنت بحاجة إلى نتيجة الرسالة التي تم تنفيذها ، فيمكنك القيام بذلك:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);




ios animation uinavigationcontroller