別のViewControllerのサブビューとしてViewControllerを追加する


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. 2番目のViewControllerをインスタンス化すると、を呼び出しViewControllerB()ます。そのビューコントローラがプログラムでそのビューを作成する場合(これは珍しいことです)、それは問題ありません。しかし、IBOutletこの2番目のビューコントローラーのシーンがInterface Builderで定義されていることを示唆していViewControllerB()ますが、を呼び出すことによって、ストーリーボードにそのシーンをインスタンス化してすべてのアウトレットを接続する機会を与えていません。したがって、暗黙的にアンラップされたUILabelnilであり、エラーメッセージが表示されます。

    代わりに、Interface Builderで宛先ViewControllerに「ストーリーボード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:))が必要な理由の詳細については、WWDC 2011ビデオ#102-UIViewControllerContainmentの実装を参照してください。つまり、View Controller階層がView階層と同期していることを確認する必要があります。これらの呼び出しによりaddChilddidMove(toParent:)これが当てはまります。

    参照してくださいカスタムコンテナビューコントローラの作成ビューコントローラプログラミングガイド。


ちなみに、上記はプログラムでこれを行う方法を示しています。Interface Builderで「コンテナビュー」を使用すると、実際にははるかに簡単になります。

ここに画像の説明を入力してください

そうすれば、これらの封じ込め関連の呼び出しについて心配する必要はなく、InterfaceBuilderが自動的に処理します。

Swift 2の実装については、この回答の以前のリビジョンを参照してください。


1
詳細な説明ありがとうございます。に追加しようとするViewControllerBViewControllerAViewControllerB画面から消えてしまいます。シミュレーターのスクリーンショットで投稿を編集しました。
Srujan Simha 2014

それは可能です。そのため、私の例では、frame手動で設定しました。または、オフにした場合translatesFrameIntoConstraints(またはそれが何と呼ばれる場合でも)、おそらくプログラムで制約を追加することもできます。ただし、サブビューを追加する場合は、プログラムで追加されたすべてのサブビューの場合と同様に、フレームを何らかの方法で設定する必要があります。
ロブ

1
これが境界を追加する方法ですcontroller.view.frame = UIScreen.mainScreen().bounds
Codetard 2016

3
ビューコントローラの封じ込めを行うときは、実際には画面ではなくスーパービューを参照する必要があります。率直に言って、分割画面マルチタスクができたので、画面を参照することを行うことは一般的にお勧めできません。
ロブ

1
@ Honey-「viewControllerのビューがすべてparentViewControllerの1つのサブビューであると仮定する」とはどういう意味かわかりません。定義上、これを行うaddSubviewと、子コントローラーのルートビューは、それを追加したビューのサブビューになります。子コントローラーのルートビューと、サブビューとして追加したばかりのビューの間に制約を追加するだけです。
ロブ

50

ロブに感謝します。2番目の観察のための詳細な構文の追加:

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)

そして、ビューコントローラを削除するには:

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

6
呼び出すときにところで、addChildViewController、んではない呼び出しますwillMoveToParentViewController。呼び出すaddChildViewControllerと、あなたに代わって呼び出されます。参照子の追加と削除iOS用プログラミングガイドビューコントローラを。 このため、個人的にはaddChildViewController、インスタンス化した直後、構成する前に必ず電話をかけます。
ロブ

1
controller.ANYPROPERTY = THEVALUE ..を実行すると、AnyPropertyがchildViewControllerで定義されていると思います。私はそれを試しました、そしてそれは私にエラーを与えていました。それを修正する方法についてのアイデア。
Anuj Arora

@AnujAroraあなたの推測は正しいです。ANYPROPERTYは子viewControllerで定義されます。子viewcontrollerでANYPROPERTYを確認できますが、ViewDidLoadではなくViewDidAppearで確認できます。
スニタ2016年

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)

を呼び出さないでくださいwillMoveaddChildあなたのためにそれをします。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()
        }
        
    }
}

を呼び出さないでくださいwillMovewillMove ドキュメントは言う、addChildあなたのためにそれを行います。
ロブ

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のおかげで、Swift4.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」を使用すると、うまくいきます。
サブリナ

を呼び出さないでくださいwillMovewillMove ドキュメントは言う、addChildあなたのためにそれを行います。それを二度呼ぶことからは何の役にも立たない。
ロブ

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.