다른 뷰 컨트롤러에서 뷰 컨트롤러를 서브 뷰로 추가하기


81

이 문제에 대한 게시물을 거의 찾았지만 아무도 내 문제를 해결하지 못했습니다.

내가 ..

  1. ViewControllerA
  2. ViewControllerB

ViewControllerA에서 ViewControllerB를 하위보기로 추가하려고했지만 " fatal error: unexpectedly found nil while unwrapping an Optional value" 와 같은 오류가 발생 합니다.

아래는 코드입니다 ...

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는 레이블이있는 단순한 화면입니다.

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"
}

편집하다

사용자 답변의 제안 된 솔루션으로 ViewControllerA의 ViewControllerB가 화면에서 사라집니다. 회색 테두리는 하위보기 용으로 만든 프레임입니다. 여기에 이미지 설명 입력

답변:


178

몇 가지 관찰 :

  1. 두 번째 뷰 컨트롤러를 인스턴스화 할 때 ViewControllerB(). 해당 뷰 컨트롤러가 프로그래밍 방식으로 뷰를 생성하면 (비정상적 임) 괜찮을 것입니다. 그러나의 존재는 IBOutlet이 두 번째 뷰 컨트롤러의 장면이 Interface Builder에 정의되어 있음을 시사하지만를 호출 ViewControllerB()하면 스토리 보드에 해당 장면을 인스턴스화하고 모든 콘센트를 연결할 수있는 기회를주지 않습니다. 따라서 암시 적으로 래핑되지 않은 UILabel것은 nil이므로 오류 메시지가 나타납니다.

    대신 인터페이스 빌더에서 대상 뷰 컨트롤러에 "스토리 보드 ID"를 제공하고 instantiateViewController(withIdentifier:)이를 인스턴스화하는 데 사용할 수 있습니다 (그리고 모든 IB 아웃렛을 연결). Swift 3 :

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

    이제이 액세스 할 수있는 controller'들 view.

  2. 그러나 정말로하고 싶다면 addSubview(즉, 다음 장면으로 전환하지 않는 경우) "뷰 컨트롤러 격리"라는 연습에 참여하는 것입니다. 당신은 단순히 원하는 것이 아닙니다 addSubview. 추가 컨테이너 뷰 컨트롤러 호출을 수행하고 싶습니다. 예 :

    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)
    

    더이는 이유에 대한 자세한 내용은 addChild(이전에 호출 addChildViewController) 및 didMove(toParent:)(이전이라고 didMove(toParentViewController:)) 필요, 참조 구현의 UIViewController 봉쇄 - WWDC 2011 비디오 # 102 . 요컨대, 뷰 컨트롤러 계층이 뷰 계층과 동기화되어 있는지 확인해야 하며 이러한 호출이 addChild이에 해당 didMove(toParent:)하는지 확인해야합니다.

    View Controller Programming GuideCreating Custom Container View Controllers 도 참조하십시오 .


그런데 위의 내용은 프로그래밍 방식으로 수행하는 방법을 보여줍니다. Interface Builder에서 "컨테이너 뷰"를 사용하면 실제로 훨씬 더 쉽습니다.

여기에 이미지 설명 입력

그러면 이러한 포함 관련 호출에 대해 걱정할 필요가 없으며 Interface Builder가 처리해줍니다.

Swift 2 구현 의 경우이 답변의 이전 개정을 참조하십시오 .


1
자세한 설명 감사합니다. 나는 추가 시도하는 경우 ViewControllerBViewControllerA, ViewControllerB화면을 것입니다. 시뮬레이터의 스크린 샷으로 게시물을 편집했습니다.
Srujan Simha 2014

가능합니다. 이것이 제 예에서 frame수동으로 설정 한 이유 입니다. 또는 끄면 translatesFrameIntoConstraints(또는 그 이름이 무엇이든) 프로그래밍 방식으로 제약 조건을 추가 할 수도 있습니다. 그러나 하위 뷰를 추가하는 경우 프로그래밍 방식으로 추가 된 모든 하위 뷰와 마찬가지로 프레임을 어느 방향 으로든 설정해야합니다.
Rob

1
이것은 당신이 경계를 추가하는 방법입니다controller.view.frame = UIScreen.mainScreen().bounds
Codetard

3
뷰 컨트롤러 포함을 수행 할 때 실제로 화면이 아닌 수퍼 뷰를 참조해야합니다. 솔직히, 이제 분할 화면 멀티 태스킹이 있으므로 화면을 참조하는 모든 작업을 수행하는 것은 일반적으로 바람직하지 않습니다.
Rob

1
@Honey- "모든 viewController의 뷰가 parentViewController의 하나의 하위 뷰라고 가정"한다는 것이 무슨 뜻인지 잘 모르겠습니다. 정의에 따라를 수행 addSubview하면 자식 컨트롤러의 루트보기는 추가 한보기의 하위보기입니다. 당신이 할 일은 자식 컨트롤러의 루트 뷰와 방금 그것을 서브 뷰로 추가 한 뷰 사이에 제약을 추가하는 것입니다.
Rob

50

Rob에게 감사합니다. 두 번째 관찰에 대한 자세한 구문 추가 :

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)

그리고 viewcontroller를 제거하려면 :

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

6
호출 할 때 BTW, addChildViewController수행 하지 호출 willMoveToParentViewController. 부름 addChildViewController은 당신을 위해 그것을 부를 것입니다. iOS 용 View Controller 프로그래밍 가이드 에서 자식 추가 및 제거를 참조하세요 . 이러한 이유로 저는 개인적으로 addChildViewController인스턴스화 한 직후에 구성하기 전에 항상 호출 했습니다.
Rob

1
controller.ANYPROPERTY = THEVALUE .. 할 때 AnyProperty가 childViewController에 정의되어 있다고 생각합니다. 나는 그것을 시도했고 그것은 나에게 오류를주고 있었다. 그것을 수정하는 방법에 대한 아이디어.
Anuj Arora 2015 년

@Anuj Arora 당신의 추측이 맞습니다. ANYPROPERTY는 자식 viewController에 정의되어 있습니다. ANYPROPERTY는 자식 viewcontroller에서 확인할 수 있지만 ViewDidLoad가 아닌 ViewDidAppear에서 확인할 수 있습니다.
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)

마십시오 하지 호출 willMove. addChild당신을 위해 그렇게합니다. willMove 설명서를 참조하십시오 . 따라서 시퀀스는 (1)입니다 addChild. (2) 자식 vc의 뷰를 구성하고 뷰 계층 구조에 추가합니다. 그리고 (3) 전화didMove(toParent:)
Rob

4

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()
        }
        
    }
}

전화하지 마십시오 willMove. 는 AS willMove 설명서를 말한다는이 addChild당신을 위해 작업을 수행합니다.
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()

        })
    }

2

Rob 덕분에 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)

1
"controller.view.frame = self.view.frame"대신 "controller.view.frame = self.view.bounds"를 사용하십시오!
Sabrina

전화하지 마십시오 willMove. 는 AS willMove 설명서를 말한다는이 addChild당신을 위해 작업을 수행합니다. 그것을 두 번 부르는 것은 소용이 없습니다.
Rob

0

커스텀 컨테이너 뷰 컨트롤러 구현에 대한 공식 문서도 확인하세요.

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

이 문서에는 모든 지침에 대한 훨씬 더 자세한 정보가 있으며 전환을 추가하는 방법도 설명합니다.

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)
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.