RootViewController Switch Transition Animation


126

มีวิธีใดบ้างที่จะมีเอฟเฟกต์การเปลี่ยน / ภาพเคลื่อนไหวในขณะที่แทนที่วิวคอนโทรลเลอร์ที่มีอยู่เป็น rootviewcontroller ด้วยอันใหม่ใน appDelegate

คำตอบ:


273

คุณสามารถตัดการสลับของrootViewControllerบล็อกภาพเคลื่อนไหวในการเปลี่ยนภาพ:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newViewController; }
                completion:nil];

5
เฮ้ Ole ฉันลองใช้วิธีนี้แล้วมันใช้งานได้บางส่วนสิ่งนี้คือแอพของฉันจะอยู่ในโหมดแนวนอนเท่านั้น แต่ด้วยการเปลี่ยน rootviewcontroller ตัวควบคุมมุมมองที่นำเสนอใหม่จะถูกโหลดในแนวตั้งในตอนเริ่มต้นและหมุนไปที่โหมดแนวนอนอย่างรวดเร็ว , วิธีแก้ปัญหานั้น?
Chris Chen

4
ฉันตอบคำถามของ Chris Chen (หวังว่าอาจจะ?) ในคำถามแยกต่างหากของเขาที่นี่: stackoverflow.com/questions/8053832/…
Kalle

1
เฮ้ฉันต้องการการเปลี่ยนแปลงแบบพุชในแอนิเมชั่นเดียวกันฉันจะทำได้หรือไม่
ผู้ใช้ 1531343

14
ฉันสังเกตเห็นปัญหาบางอย่างเกี่ยวกับสิ่งนี้คือองค์ประกอบที่วางผิดตำแหน่ง / องค์ประกอบที่โหลดอย่างเกียจคร้าน ตัวอย่างเช่นหากคุณไม่มีแถบนำทางบน root vc ที่มีอยู่ให้เคลื่อนไหวไปยัง vc ใหม่ที่มีอยู่การเคลื่อนไหวจะเสร็จสมบูรณ์และจากนั้นจึงเพิ่มแถบนำทาง มันดูโง่ - มีความคิดว่าทำไมถึงเป็นเช่นนี้และจะทำอะไรได้บ้าง?
anon_dev1234

1
ฉันพบว่าการโทรnewViewController.view.layoutIfNeeded()ก่อนบล็อกภาพเคลื่อนไหวช่วยแก้ปัญหาเกี่ยวกับองค์ประกอบที่โหลดได้อย่างเกียจคร้าน
โอ้

66

ฉันพบสิ่งนี้และทำงานได้อย่างสมบูรณ์:

ในแอปของคุณ

- (void)changeRootViewController:(UIViewController*)viewController {

    if (!self.window.rootViewController) {
        self.window.rootViewController = viewController;
        return;
    }

    UIView *snapShot = [self.window snapshotViewAfterScreenUpdates:YES];

    [viewController.view addSubview:snapShot];

    self.window.rootViewController = viewController;

    [UIView animateWithDuration:0.5 animations:^{
        snapShot.layer.opacity = 0;
        snapShot.layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.5);
    } completion:^(BOOL finished) {
        [snapShot removeFromSuperview];
    }];
}

ในแอปของคุณ

 if (!app) { app = (AppDelegate *)[[UIApplication sharedApplication] delegate]; }
        [app changeRootViewController:newViewController];

เครดิต:

https://gist.github.com/gimenete/53704124583b5df3b407


สิ่งนี้รองรับการหมุนหน้าจออัตโนมัติหรือไม่
Wingzero

1
วิธีนี้ใช้ได้ผลดีกว่าในกรณีของฉัน เมื่อใช้ transitionWithView ตัวควบคุมมุมมองรูทใหม่ได้รับการจัดวางอย่างเหมาะสมจนกระทั่งหลังจากการเปลี่ยนแปลงเสร็จสิ้น วิธีนี้ทำให้สามารถเพิ่มตัวควบคุมมุมมองรูทใหม่ลงในหน้าต่างจัดวางแล้วเปลี่ยนได้
Fostah

@ Wingzero ช้าไปหน่อย แต่สิ่งนี้จะอนุญาตให้มีการเปลี่ยนแปลงใด ๆ ไม่ว่าจะผ่าน UIView.animations (เช่น CGAffineTransform พร้อมการหมุน) หรือ CAAnimation ที่กำหนดเอง
สามารถ

41

ฉันกำลังโพสต์คำตอบของพระเยซูที่ดำเนินการอย่างรวดเร็ว ใช้ตัวระบุของ viewcontroller เป็นอาร์กิวเมนต์โหลดจาก storyboard ที่ต้องการViewControllerและเปลี่ยน rootViewController ด้วยภาพเคลื่อนไหว

อัปเดต Swift 3.0:

  func changeRootViewController(with identifier:String!) {
    let storyboard = self.window?.rootViewController?.storyboard
    let desiredViewController = storyboard?.instantiateViewController(withIdentifier: identifier);

    let snapshot:UIView = (self.window?.snapshotView(afterScreenUpdates: true))!
    desiredViewController?.view.addSubview(snapshot);

    self.window?.rootViewController = desiredViewController;

    UIView.animate(withDuration: 0.3, animations: {() in
      snapshot.layer.opacity = 0;
      snapshot.layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.5);
      }, completion: {
        (value: Bool) in
        snapshot.removeFromSuperview();
    });
  }

อัปเดต Swift 2.2:

  func changeRootViewControllerWithIdentifier(identifier:String!) {
    let storyboard = self.window?.rootViewController?.storyboard
    let desiredViewController = storyboard?.instantiateViewControllerWithIdentifier(identifier);

    let snapshot:UIView = (self.window?.snapshotViewAfterScreenUpdates(true))!
    desiredViewController?.view.addSubview(snapshot);

    self.window?.rootViewController = desiredViewController;

    UIView.animateWithDuration(0.3, animations: {() in
      snapshot.layer.opacity = 0;
      snapshot.layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.5);
      }, completion: {
        (value: Bool) in
        snapshot.removeFromSuperview();
    });
  }

  class func sharedAppDelegate() -> AppDelegate? {
    return UIApplication.sharedApplication().delegate as? AppDelegate;
  }

หลังจากนั้นคุณสามารถใช้งานได้ง่ายมากจากทุกที่:

let appDelegate = AppDelegate.sharedAppDelegate()
appDelegate?.changeRootViewControllerWithIdentifier("YourViewControllerID")

อัปเดต Swift 3.0

let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.changeRootViewController(with: "listenViewController")

26

สวิฟต์ 2

UIView.transitionWithView(self.window!, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromLeft, animations: {
  self.window?.rootViewController = anyViewController
}, completion: nil)

สวิฟต์ 3, 4, 5

UIView.transition(with: self.window!, duration: 0.5, options: UIView.AnimationOptions.transitionFlipFromLeft, animations: {
  self.window?.rootViewController = anyViewController
}, completion: nil)

XCode แก้ไขรหัสของฉันเป็นดังนี้: `` UIView.transition (with: self.view.window!, ระยะเวลา: 0.5, ตัวเลือก: UIViewAnimationOptions.transitionFlipFromTop, ภาพเคลื่อนไหว: {appDelegate.window? .rootViewController = myViewController}, complete: nil) ``
Scaryguy

10

ลองดูสิ ใช้ได้ดีสำหรับฉัน

BOOL oldState = [UIView areAnimationsEnabled];
[UIView setAnimationsEnabled:NO];
self.window.rootViewController = viewController;
[UIView transitionWithView:self.window duration:0.5 options:transition animations:^{
    //
} completion:^(BOOL finished) {
    [UIView setAnimationsEnabled:oldState];
}];

แก้ไข:

อันนี้ดีกว่าครับ

- (void)setRootViewController:(UIViewController *)viewController
               withTransition:(UIViewAnimationOptions)transition
                   completion:(void (^)(BOOL finished))completion {
    UIViewController *oldViewController = self.window.rootViewController;
    [UIView transitionFromView:oldViewController.view 
                        toView:viewController.view
                      duration:0.5f
                       options:(UIViewAnimationOptions)(transition|UIViewAnimationOptionAllowAnimatedContent|UIViewAnimationOptionLayoutSubviews)
                    completion:^(BOOL finished) {
        self.window.rootViewController = viewController;
        if (completion) {
            completion(finished);
        }
    }];
}

ฉันมีแอนิเมชั่นเริ่มต้นแปลก ๆ เมื่อเพียงแค่เปลี่ยน root VC รุ่นแรกกำจัดสิ่งนั้นสำหรับฉัน
juhan_h

เวอร์ชันที่สองจะทำให้เค้าโครงมุมมองย่อยเป็นภาพเคลื่อนไหวดังที่คุณ Juhan_h กล่าวถึง หากไม่จำเป็นให้ทดลองลบUIViewAnimationOptionAllowAnimatedContent|UIViewAnimationOptionLayoutSubviewsหรือใช้เวอร์ชันแรกหรือวิธีอื่น
ftvs

3

เพื่อไม่ให้มีปัญหากับการพลิกการเปลี่ยนแปลงในภายหลังในแอปเป็นการดีที่จะล้างมุมมองเก่าออกจากสแต็กด้วย

UIViewController *oldController=self.window.rootViewController;

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionCrossDissolve
                animations:^{ self.window.rootViewController = nav; }
                completion:^(BOOL finished) {
                    if(oldController!=nil)
                        [oldController.view removeFromSuperview];
                }];

2

คำตอบที่ถูกต้องคือคุณไม่จำเป็นต้องแทนที่rootViewControllerบนหน้าต่างของคุณ ให้สร้างแบบกำหนดเองUIViewControllerกำหนดหนึ่งครั้งและปล่อยให้แสดงคอนโทรลเลอร์ย่อยทีละตัวและแทนที่ด้วยแอนิเมชั่นหากจำเป็น คุณสามารถใช้โค้ดต่อไปนี้เป็นจุดเริ่มต้น:

Swift 3.0

import Foundation
import UIKit

/// Displays a single child controller at a time.
/// Replaces the current child controller optionally with animation.
class FrameViewController: UIViewController {

    private(set) var displayedViewController: UIViewController?

    func display(_ viewController: UIViewController, animated: Bool = false) {

        addChildViewController(viewController)

        let oldViewController = displayedViewController

        view.addSubview(viewController.view)
        viewController.view.layoutIfNeeded()

        let finishDisplay: (Bool) -> Void = {
            [weak self] finished in
            if !finished { return }
            oldViewController?.view.removeFromSuperview()
            oldViewController?.removeFromParentViewController()
            viewController.didMove(toParentViewController: self)
        }

        if (animated) {
            viewController.view.alpha = 0
            UIView.animate(
                withDuration: 0.5,
                animations: { viewController.view.alpha = 1; oldViewController?.view.alpha = 0 },
                completion: finishDisplay
            )
        }
        else {
            finishDisplay(true)
        }

        displayedViewController = viewController
    }

    override var preferredStatusBarStyle: UIStatusBarStyle {
        return displayedViewController?.preferredStatusBarStyle ?? .default
    }
}

และวิธีที่คุณใช้คือ:

...
let rootController = FrameViewController()
rootController.display(UINavigationController(rootViewController: MyController()))
window.rootViewController = rootController
window.makeKeyAndVisible()
...

ตัวอย่างด้านบนแสดงให้เห็นว่าคุณสามารถซ้อนUINavigationControllerอยู่ภายในได้FrameViewControllerและใช้งานได้ดี แนวทางนี้ช่วยให้คุณปรับแต่งและควบคุมได้ในระดับสูง เพียงโทรหาเมื่อFrameViewController.display(_)ใดก็ได้ที่คุณต้องการแทนที่ตัวควบคุมรูทบนหน้าต่างของคุณและมันจะทำงานนั้นให้คุณ


2

นี่คือการอัปเดตสำหรับ swift 3 วิธีนี้ควรอยู่ในตัวแทนแอปของคุณและคุณเรียกใช้จากตัวควบคุมมุมมองใด ๆ ผ่านทางอินสแตนซ์ที่ใช้ร่วมกันของผู้มอบสิทธิ์แอป

func logOutAnimation() {
    let storyBoard = UIStoryboard.init(name: "SignIn", bundle: nil)
    let viewController = storyBoard.instantiateViewController(withIdentifier: "signInVC")
    UIView.transition(with: self.window!, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromLeft, animations: {
        self.window?.rootViewController = viewController
        self.window?.makeKeyAndVisible()
    }, completion: nil)
}

ส่วนที่หายไปจากคำถามต่างๆข้างต้นคือ

    self.window?.makeKeyAndVisible()

หวังว่านี่จะช่วยใครบางคนได้


1

ใน AppDelegate.h:

#define ApplicationDelegate ((AppDelegate *)[UIApplication sharedApplication].delegate)]

ในคอนโทรลเลอร์ของคุณ:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
    ApplicationDelegate.window.rootViewController = newViewController;
    }
                completion:nil];

6
สิ่งนี้เหมือนกับคำตอบที่ยอมรับยกเว้นการจัดรูปแบบไม่ถูกต้อง รำคาญทำไม?
jrturton

1
อันนี้ไม่ได้ขึ้นอยู่กับว่าคุณอยู่ใน View หรือ ViewController ความแตกต่างที่ยิ่งใหญ่ที่สุดคือปรัชญาในแง่ของความหนาหรือบางที่คุณต้องการให้ Views และ ViewControllers ของคุณเป็นอย่างไร
สูงสุด

0

ฉันเสนอวิธีที่มันใช้งานได้ดีในโปรเจ็กต์ของฉันและมันก็ให้ภาพเคลื่อนไหวที่ดี ฉันได้ทดสอบข้อเสนออื่น ๆ ที่พบในโพสต์นี้ แต่บางข้อไม่ได้ผลตามที่คาดไว้

- (void)transitionToViewController:(UIViewController *)viewController withTransition:(UIViewAnimationOptions)transition completion:(void (^)(BOOL finished))completion {
// Reset new RootViewController to be sure that it have not presented any controllers
[viewController dismissViewControllerAnimated:NO completion:nil];

[UIView transitionWithView:self.window
                  duration:0.5f
                   options:transition
                animations:^{
                    for (UIView *view in self.window.subviews) {
                        [view removeFromSuperview];
                    }
                    [self.window addSubview:viewController.view];

                    self.window.rootViewController = viewController;
                } completion:completion];
}

0

แอนิเมชั่นแสนหวาน (ทดสอบด้วย Swift 4.x):

extension AppDelegate {
   public func present(viewController: UIViewController) {
        guard let window = window else { return }
        UIView.transition(with: window, duration: 0.5, options: .transitionFlipFromLeft, animations: {
            window.rootViewController = viewController
        }, completion: nil)
    }
}

โทร

guard let delegate = UIApplication.shared.delegate as? AppDelegate else { return }
delegate.present(viewController: UIViewController())
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.