Thêm bộ điều khiển chế độ xem làm chế độ xem phụ trong bộ điều khiển chế độ xem khác


81

Tôi đã tìm thấy một số bài đăng cho vấn đề này nhưng không có bài viết nào giải quyết được vấn đề của tôi.

Nói như tôi đã ..

  1. ViewControllerA
  2. ViewControllerB

Tôi đã cố gắng thêm ViewControllerB làm chế độ xem phụ trong ViewControllerA nhưng, nó gặp lỗi như " fatal error: unexpectedly found nil while unwrapping an Optional value".

Dưới đây là mã ...

ViewControllerA

var testVC: ViewControllerB = ViewControllerB();

override func viewDidLoad()
{
    super.viewDidLoad()
    self.testVC.view.frame = CGRectMake(0, 0, 350, 450);
    self.view.addSubview(testVC.view);
    // Do any additional setup after loading the view.
}

ViewControllerB chỉ là một màn hình đơn giản với một nhãn trong đó.

ViewControllerB

 @IBOutlet weak var test: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()
    test.text = "Success" // Throws ERROR here "fatal error: unexpectedly found nil while unwrapping an Optional value"
}

BIÊN TẬP

Với giải pháp được đề xuất từ ​​câu trả lời của người dùng, ViewControllerB trong ViewControllerA sẽ tắt màn hình. Viền xám là khung tôi đã tạo cho chế độ xem phụ. nhập mô tả hình ảnh ở đây

Câu trả lời:


176

Một vài nhận xét:

  1. Khi bạn khởi tạo bộ điều khiển chế độ xem thứ hai, bạn đang gọi ViewControllerB(). Nếu bộ điều khiển chế độ xem đó lập trình tạo chế độ xem của nó (điều đó là bất thường) thì sẽ ổn. Nhưng sự hiện diện của cảnh IBOutletcho thấy rằng cảnh của bộ điều khiển chế độ xem thứ hai này đã được xác định trong Trình tạo giao diện, nhưng bằng cách gọi ViewControllerB(), bạn không cho bảng phân cảnh có cơ hội khởi tạo cảnh đó và kết nối tất cả các cửa hàng. Do đó, việc không UILabelđược bao bọc hoàn toàn là nil, dẫn đến thông báo lỗi của bạn.

    Thay vào đó, bạn muốn cung cấp cho bộ điều khiển chế độ xem đích của mình một "id bảng phân cảnh" trong Trình tạo giao diện và sau đó bạn có thể sử dụng instantiateViewController(withIdentifier:)để khởi tạo nó (và kết nối tất cả các cửa hàng IB). Trong Swift 3:

    let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
    

    Bây giờ bạn có thể truy cập này controller's view.

  2. Nhưng nếu bạn thực sự muốn làm addSubview(tức là bạn không chuyển sang cảnh tiếp theo), thì bạn đang tham gia vào một thực hành được gọi là "ngăn chặn bộ điều khiển chế độ xem". Bạn không chỉ muốn đơn giản addSubview. Bạn muốn thực hiện một số lệnh gọi bộ điều khiển chế độ xem vùng chứa bổ sung, ví dụ:

    let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
    addChild(controller)
    controller.view.frame = ...  // or, better, turn off `translatesAutoresizingMaskIntoConstraints` and then define constraints for this subview
    view.addSubview(controller.view)
    controller.didMove(toParent: self)
    

    Để biết thêm thông tin về lý do tại sao điều này addChild(được gọi trước đây addChildViewController) và didMove(toParent:)(được gọi trước đây didMove(toParentViewController:)) là cần thiết, hãy xem WWDC 2011 video # 102 - Triển khai UIViewController Containment . Tóm lại, bạn cần đảm bảo rằng hệ thống phân cấp bộ điều khiển chế độ xem của bạn luôn đồng bộ với hệ thống phân cấp chế độ xem của bạn và các lệnh gọi này đến addChilddidMove(toParent:)đảm bảo đúng như vậy.

    Cũng xem Tạo bộ điều khiển chế độ xem vùng chứa tùy chỉnh trong Hướng dẫn lập trình bộ điều khiển chế độ xem.


Nhân tiện, phần trên minh họa cách thực hiện việc này theo chương trình. Nó thực sự dễ dàng hơn nhiều nếu bạn sử dụng "chế độ xem vùng chứa" trong Trình tạo giao diện.

nhập mô tả hình ảnh ở đây

Sau đó, bạn không phải lo lắng về bất kỳ lệnh gọi nào liên quan đến ngăn chặn này và Trình tạo giao diện sẽ xử lý việc đó giúp bạn.

Để triển khai Swift 2, hãy xem bản sửa đổi trước của câu trả lời này .


1
Cảm ơn bạn đã giải thích chi tiết. Khi tôi thử thêm ViewControllerBvào ViewControllerA, ViewControllerBsẽ tắt màn hình. Tôi đã chỉnh sửa bài đăng của mình bằng ảnh chụp màn hình của trình mô phỏng.
Srujan Simha

Điều đó là khả thi. Đó là lý do tại sao, trong ví dụ của tôi, tôi đặt framethủ công. Hoặc nếu bạn tắt translatesFrameIntoConstraints(hoặc bất cứ điều gì được gọi là), và bạn cũng có thể thêm các ràng buộc theo chương trình. Nhưng nếu bạn đang thêm chế độ xem phụ, bạn có trách nhiệm thiết lập khung của nó, bằng cách này hay cách khác, giống như bạn đối với tất cả các chế độ xem phụ được thêm theo chương trình.
Rob

1
Đây là cách bạn có thể thêm giới hạncontroller.view.frame = UIScreen.mainScreen().bounds
Codetard

3
Khi thực hiện ngăn chặn bộ điều khiển chế độ xem, bạn thực sự nên tham chiếu chế độ xem siêu tốc, không phải màn hình. Thành thật mà nói, bây giờ chúng tôi đã chia đôi màn hình đa nhiệm, làm bất cứ điều gì liên quan đến màn hình nói chung là không thể nhìn thấy được.
Rob

1
@Honey - Tôi không chắc ý của bạn khi "giả sử rằng tất cả chế độ xem của viewController chỉ là một chế độ xem phụ của parentViewController". Theo định nghĩa, khi bạn làm như vậy addSubview, dạng xem gốc của bộ điều khiển con là dạng xem phụ của dạng xem mà bạn đã thêm nó vào. Tất cả những gì bạn làm là thêm các ràng buộc giữa chế độ xem gốc của bộ điều khiển con và chế độ xem mà bạn vừa thêm nó làm chế độ xem phụ.
Rob

48

Cảm ơn Rob. Thêm cú pháp chi tiết cho quan sát thứ hai của bạn:

let controller:MyView = self.storyboard!.instantiateViewControllerWithIdentifier("MyView") as! MyView
controller.ANYPROPERTY=THEVALUE // If you want to pass value
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChildViewController(controller)
controller.didMoveToParentViewController(self)

Và để loại bỏ bộ điều khiển chế độ xem:

self.willMoveToParentViewController(nil)
self.view.removeFromSuperview()
self.removeFromParentViewController() 

6
BTW khi gọi addChildViewControllerthì không gọi willMoveToParentViewController. Gọi điện addChildViewControllersẽ gọi cho bạn. Xem Thêm và xóa con trong Hướng dẫn lập trình bộ điều khiển chế độ xem dành cho iOS. Vì lý do này, cá nhân tôi luôn gọi addChildViewControllerngay sau khi khởi tạo nó, nhưng trước khi định cấu hình nó.
Rob

1
Khi bạn thực hiện controller.ANYPROPERTY = THEVALUE..Tôi đoán rằng AnyProperty được định nghĩa trong childViewController. Tôi đã thử nó và nó báo lỗi cho tôi. Bất kỳ ý tưởng làm thế nào để khắc phục điều đó.
Anuj Arora

@Anuj Arora đoán của bạn là đúng. ANYPROPERTY được định nghĩa trong child viewController. Bạn có thể kiểm tra ANYPROPERTY trong child viewcontroller nhưng trong ViewDidAppear thì không trong ViewDidLoad.
Sunita

7
This code will work for Swift 4.2.

let controller:SecondViewController = 
self.storyboard!.instantiateViewController(withIdentifier: "secondViewController") as! 
SecondViewController
controller.view.frame = self.view.bounds;
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)

Đừng không gọi willMove. addChildlàm điều đó cho bạn. Xem willMove tài liệu . Vậy dãy số là (1) addChild; (2) cấu hình khung nhìn của vc con và thêm nó vào hệ thống phân cấp chế độ xem; và (3) gọididMove(toParent:)
Rob

4

Để thêm và xóa ViewController

 var secondViewController :SecondViewController?

  // Adding 
 func add_ViewController() {
    let controller  = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController")as! SecondViewController
    controller.view.frame = self.view.bounds
    self.view.addSubview(controller.view)
    self.addChild(controller)
    controller.didMove(toParent: self)
    self.secondViewController = controller
}

// Removing
func remove_ViewController(secondViewController:SecondViewController?) {
    if secondViewController != nil {
        if self.view.subviews.contains(secondViewController!.view) {
             secondViewController!.view.removeFromSuperview()
        }
        
    }
}

Đừng gọi willMove. Như willMove tài liệu cho biết, addChildđiều đó có lợi cho bạn.
Rob

3

func callForMenuView () {

    if(!isOpen)

    {
        isOpen = true

        let menuVC : MenuViewController = self.storyboard!.instantiateViewController(withIdentifier: "menu") as! MenuViewController
        self.view.addSubview(menuVC.view)
        self.addChildViewController(menuVC)
        menuVC.view.layoutIfNeeded()

        menuVC.view.frame=CGRect(x: 0 - UIScreen.main.bounds.size.width, y: 0, width: UIScreen.main.bounds.size.width-90, height: UIScreen.main.bounds.size.height);

        UIView.animate(withDuration: 0.3, animations: { () -> Void in
            menuVC.view.frame=CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width-90, height: UIScreen.main.bounds.size.height);
    }, completion:nil)

    }else if(isOpen)
    {
        isOpen = false
      let viewMenuBack : UIView = view.subviews.last!

        UIView.animate(withDuration: 0.3, animations: { () -> Void in
            var frameMenu : CGRect = viewMenuBack.frame
            frameMenu.origin.x = -1 * UIScreen.main.bounds.size.width
            viewMenuBack.frame = frameMenu
            viewMenuBack.layoutIfNeeded()
            viewMenuBack.backgroundColor = UIColor.clear
        }, completion: { (finished) -> Void in
            viewMenuBack.removeFromSuperview()

        })
    }

1

Cảm ơn Rob, đã cập nhật cú pháp Swift 4.2

let controller:WalletView = self.storyboard!.instantiateViewController(withIdentifier: "MyView") as! WalletView
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)

sử dụng "controller.view.frame = self.view.bounds" thay vì "controller.view.frame = self.view.frame" phù hợp với tôi!
Mehdico

Đừng gọi willMove. Như willMove tài liệu cho biết, addChildđiều đó có lợi cho bạn. Không có gì tốt khi gọi nó hai lần.
Rob

0

Vui lòng kiểm tra tài liệu chính thức về triển khai trình điều khiển chế độ xem vùng chứa tùy chỉnh:

https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplectingaContainerViewController.html#//apple_ref/doc/uid/TP40007457-CH11-SW1

Tài liệu này có nhiều thông tin chi tiết hơn cho mọi hướng dẫn và cũng mô tả cách thêm chuyển đổi.

Đã dịch sang Swift 3:

func cycleFromViewController(oldVC: UIViewController,
               newVC: UIViewController) {
   // Prepare the two view controllers for the change.
   oldVC.willMove(toParentViewController: nil)
   addChildViewController(newVC)

   // Get the start frame of the new view controller and the end frame
   // for the old view controller. Both rectangles are offscreen.r
   newVC.view.frame = view.frame.offsetBy(dx: view.frame.width, dy: 0)
   let endFrame = view.frame.offsetBy(dx: -view.frame.width, dy: 0)

   // Queue up the transition animation.
   self.transition(from: oldVC, to: newVC, duration: 0.25, animations: { 
        newVC.view.frame = oldVC.view.frame
        oldVC.view.frame = endFrame
    }) { (_: Bool) in
        oldVC.removeFromParentViewController()
        newVC.didMove(toParentViewController: self)
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.