Добавление контроллера представления в виде подсмотра в другом контроллере представления
Я нашел несколько сообщений для этой проблемы, но ни одна из них не решила мою проблему.
Скажи, что я..
- ViewControllerA
- 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 отключается от экрана. Серая рамка - это рамка, которую я создал для подсмотра.
![enter image description here]()
Ответы
Ответ 1
Пара наблюдений:
-
Когда вы создаете экземпляр второго контроллера представления, вы вызываете ViewControllerB()
. Если этот контроллер представления программно создает свое представление (что необычно), это было бы хорошо. Но наличие IBOutlet
предполагает, что эта вторая сцена контроллера представления была определена в Интерфейсном ViewControllerB()
, но, вызывая ViewControllerB()
, вы не даете раскадровке возможность создать экземпляр этой сцены и подключить все выходы. Таким образом, неявно развернутый UILabel
равен nil
, что приводит к вашему сообщению об ошибке.
Вместо этого вы хотите присвоить контроллеру представления назначения "идентификатор раскадровки" в Интерфейсном instantiateViewController(withIdentifier:)
а затем вы можете использовать instantiateViewController(withIdentifier:)
чтобы создать его экземпляр (и подключить все выходы IB). В Swift 3:
let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
Теперь Вы можете получить доступ controller
view
.
-
Но если вы действительно хотите сделать 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:)
), см. Видео # 102 WWDC 2011 - Реализация содержания UIViewController. Короче говоря, вам нужно убедиться, что ваша иерархия контроллера представления синхронизируется с вашей иерархией представления, и эти вызовы addChild
и didMove(toParent:)
гарантируют, что это так.
Также см. Создание пользовательских контроллеров представления контейнера в Руководстве по программированию контроллера представления.
Кстати, вышеизложенное иллюстрирует, как это сделать программно. На самом деле это намного проще, если вы используете "представление контейнера" в Интерфейсном Разработчике.
![enter image description here]()
Тогда вам не нужно беспокоиться о вызовах, связанных с защитой, и Interface Builder позаботится об этом за вас.
Для реализации Swift 2 см. Предыдущую редакцию этого ответа.
Ответ 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;
controller.willMoveToParentViewController(self)
self.view.addSubview(controller.view)
self.addChildViewController(controller)
controller.didMoveToParentViewController(self)
И для удаления диспетчера view:
self.willMoveToParentViewController(nil)
self.view.removeFromSuperview()
self.removeFromParentViewController()
Ответ 3
This code will work for Swift 4.2.
let controller:SecondViewController =
self.storyboard!.instantiateViewController(withIdentifier: "secondViewController") as!
SecondViewController
controller.view.frame = self.view.bounds;
controller.willMove(toParent: self)
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)
Ответ 4
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()
})
}
Ответ 5
Благодаря Робу, обновлен синтаксис Swift 4.2
let controller:WalletView = self.storyboard!.instantiateViewController(withIdentifier: "MyView") as! WalletView
controller.view.frame = self.view.bounds;
controller.willMove(toParent: self)
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)
Ответ 6
Также, пожалуйста, ознакомьтесь с официальной документацией по внедрению пользовательского контроллера контейнера:
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)
}
}
Ответ 7
Для добавления и удаления ViewController
var secondViewController :SecondViewController?
// Adding
func add_ViewController() {
let controller = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController")as! SecondViewController
controller.view.frame = self.view.bounds;
controller.willMove(toParent: self)
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()
}
}
}