Додавання контролера перегляду як підпрогляду в інший контролер перегляду


81

Я знайшов кілька публікацій щодо цієї проблеми, але жоден з них не вирішив мою проблему.

Скажіть, як я ..

  1. ViewControllerA
  2. ViewControllerB

Я спробував додати ViewControllerB як підзапис у ViewControllerA, але він видає помилку типу " 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"
}

РЕДАГУВАТИ

З запропонованим рішенням відповідей користувачів ViewControllerB у ViewControllerA виходить з екрану. Сіра рамка - це рамка, яку я створив для підпрогляду. введіть тут опис зображення

Відповіді:


178

Пара спостережень:

  1. Коли ви створюєте екземпляр другого контролера перегляду, ви телефонуєте ViewControllerB(). Якщо цей контролер подання програмно створює свій вигляд (що незвично), це було б добре. Але наявність IBOutletприпущень говорить про те, що сцена цього другого контролера перегляду була визначена у Interface Builder, але, зателефонувавши ViewControllerB(), ви не даєте раскадровці можливості створити цю сцену та підключити всі розетки. Таким чином, неявно розгорнутий UILabelє nil, в результаті чого в повідомленні про помилку.

    Натомість ви хочете дати своєму контролеру перегляду призначення "ідентифікатор розкадровки" в Interface Builder, а потім ви можете використовувати його instantiateViewController(withIdentifier:)для створення екземплярів (і підключити всі відділення IB). У Swift 3:

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

    Тепер ви можете отримати доступ до цієї controller«S 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 - Впровадження обмеження UIViewController . Коротше кажучи, вам потрібно забезпечити, щоб ваша ієрархія контролера перегляду залишалася синхронізованою з вашою ієрархією подання, і ці виклики addChildта didMove(toParent:)переконатися, що це так.

    Також див. Створення користувацьких контролерів подання контейнерів у Посібнику з програмування контролера перегляду.


До речі, вищесказане ілюструє, як це зробити програмно. Насправді набагато простіше, якщо ви використовуєте "перегляд контейнера" ​​в Interface Builder.

введіть тут опис зображення

Тоді вам не доведеться турбуватися про будь-який із цих викликів, пов’язаних із стримуванням, і Interface Builder подбає про вас.

Щодо реалізації Swift 2, див. Попередній перегляд цієї відповіді .


1
Дякую за детальне пояснення. Коли я намагався додати ViewControllerBдо ViewControllerA, ViewControllerBвиходить з екрану. Я відредагував свою публікацію зі скріншотом симулятора.
Srujan Simha

Це можливо. Ось чому в своєму прикладі я встановлюю frameвручну. Або якщо ви вимкнете translatesFrameIntoConstraints(або як би це ще не називалося), і, можливо, ви також можете додати обмеження програмно. Але якщо ви додаєте підпогляд, ви відповідаєте за встановлення його кадру, так чи інакше, як і для всіх програмно доданих підпоглядів.
Роб

1
Ось як можна додати межіcontroller.view.frame = UIScreen.mainScreen().bounds
Codetard

3
Виконуючи обмеження контролера перегляду, ви дійсно повинні посилатися на суперперегляд, а не на екран. Чесно кажучи, тепер, коли ми розділили багатозадачність на екрані, робити все, що посилається на екран, як правило, недоцільно.
Роб

1
@Honey - Я не впевнений, що ви маєте на увазі під "припустити, що всі перегляди viewController - це лише один з підпрозорів parentViewController". За визначенням, коли ви це робите addSubview, кореневий вигляд дочірнього контролера - це підпрогляд подання, до якого ви його додали. Все, що вам потрібно зробити, це додати обмеження між кореневим поданням дочірнього контролера та видом, до якого ви щойно додали його як підпрогляд.
Роб

50

Завдяки Робу. Додаємо детальний синтаксис для другого спостереження:

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, ваші припущення правильні. ANYPROPERTY визначено в дочірньому viewController.Ви можете перевірити ANYPROPERTY у дочірньому контролері перегляду, але в ViewDidAppear не в ViewDidLoad.
Суніта

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:)
Роб

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. Як сказано в willMove документації , це 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

Завдяки Робу оновлений синтаксис 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.bounds" замість "controller.view.frame = self.view.frame" працює для мене!
Сабріна

Не дзвоніть willMove. Як сказано в willMove документації , це 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)
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.