отправить событие родительскому ViewController в Swift

Я пришел из AS3, поэтому мне будет проще показать вам, что я пытаюсь сделать с AS3. У меня есть UIViewController (root), а внутри него есть ContainerView. У меня сложилось впечатление, что UIViewController представления контейнера является дочерним элементом UIViewController (root). Я хотел бы, чтобы кнопка была нажата на контроллере дочернего представления (представление контейнера) и подняла это событие до родительского (корневого UIViewController). В AS3 у меня было бы что-то вроде этого

Корневой класс создает дочерний класс

var childClass = new ChildClass()

childClass.addEventListener("buttonWasPressed", callThisFunction);

private function callThisFunciton(e:Event):void
{
// move the child view
TweenLite.to(childClass,1,{x:100});

}

И в дочернем классе у меня есть функция кнопки, которая развертывает это событие, которое будет всплывать к родителю.

dispatchEvent(new Event("buttonWasPressed", true));

Что я не уверен, как сделать, так это заставить корневой VC прослушивать это событие. Поскольку я использую containerView, я не уверен, как настроить выход для этого дочернего VC и слушать то, что делает ребенок. Я могу управлять перетаскиванием из IB в VC, но это просто создало выход для UIView, который представляет представление контейнера. Когда я печатаю некоторый текст, я вижу, что дочерний контроллер представления создается первым перед родительским VC.

Я нашел этот пост, который, я думаю, указывает в правильном направлении. https://craiggrummitt.wordpress.com/2014/07/14/связь-между-объектами-в-объективе-c-and-swift-compared-with-actionscript-part-5/

Но я получаю сообщение об ошибке, скорее всего, потому, что я не уверен, как установить соединение из родительского VC в дочерний VC, который находится внутри представления контейнера. Я посмотрел вокруг, и я не могу найти много информации по теме.

Спасибо за помощь!


person icekomo    schedule 27.03.2015    source источник


Ответы (2)


Есть два способа:

1) Используйте делегированный протокол (рекомендуется)

a) В своем дочернем элементе создайте протокол делегата и необязательное свойство в дочернем виртуальном канале, которое будет содержать делегата.

protocol ChildViewControllerDelegate {

}

class ChildViewController: UIViewController {

    var delegate:ChildViewControllerDelegate?
}

б) В протоколе делегата создайте метод, который будет вызываться при нажатии кнопки, и реализуйте метод buttonWasPressed() в дочернем элементе, который вызывает этот метод для делегата. (Вы захотите связать этот метод с кнопкой в ​​раскадровке)

protocol ChildViewControllerDelegate {
    func childViewControllerDidPressButton(childViewController:ChildViewController)
}

class ChildViewController: UIViewController {

    var delegate:ChildViewControllerDelegate?

    @IBOutlet weak var button: UIButton!

    @IBAction func buttonWasPressed(sender: AnyObject) {
        self.delegate?.childViewControllerDidPressButton(self)
    }
}

c) Сделайте так, чтобы ваш родительский контроллер представления соответствовал дочернему протоколу

class ParentViewController: UIViewController, ChildViewControllerDelegate {

    func childViewControllerDidPressButton(childViewController: ChildViewController) {
        // Do fun handling of child button presses!
    }
}

c) Когда дочерний элемент встраивается в родительский, запускается особый вид перехода, называемый переходом внедрения. Вы можете увидеть это в раскадровке — это линия, которая соединяет дочерний элемент с родителем:

показывает выбранный этап внедрения

Добавьте идентификатор к этому переходу в раскадровке:

показывает настройки перехода к встраиванию с установленным пользовательским идентификатором

И константа для него в родительском контроллере представления:

struct Constants {
    static let embedSegue = "embedSegue"
}

class ParentViewController: UIViewController, ChildViewControllerDelegate {

    func childViewControllerDidPressButton(childViewController: ChildViewController) {
        // Do fun handling of child button presses!
    }
}

d) Затем в контроллере родительского представления переопределите метод prepareForSegue() и проверьте, соответствует ли segue.identifier тому, что вы установили в качестве идентификатора. Если это так, то вы можете получить ссылку на дочерний контроллер представления через segue.destinationViewController. Включите это как ваш дочерний контроллер представления, затем вы можете установить родителя в качестве его делегата:

struct Constants {
    static let embedSegue = "embedSegue"
}

class ParentViewController: UIViewController, ChildViewControllerDelegate {

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == Constants.embedSegue {
            let childViewController = segue.destinationViewController as ChildViewController
            childViewController.delegate = self
        }
    }

    func childViewControllerDidPressButton(childViewController: ChildViewController) {
        // Do fun handling of child button presses!
    }
}

д) Победа!

2) Используйте NSNotification и NSNotificationCenter

Вы можете думать о них как о событиях ActionScript, однако они не всплывают автоматически через иерархию представлений, как AS. Вместо этого уведомления отправляются глобально через NSNotificationCenter. Я предпочитаю использовать это только тогда, когда есть несколько объектов, которым нужно прослушивать одно конкретное событие.

Дополнительную информацию о NSNotification и NSNotificationCenter можно найти здесь: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/

person BTSmith    schedule 27.03.2015
comment
Спасибо за очень обстоятельный ответ! - person icekomo; 27.03.2015
comment
что, если дочернее представление реализовано программно?? как работает переход? - person Jenita _Alice4Real; 01.11.2016
comment
Для этого решения мой func childViewControllerDidPressButton(childViewController: ChildViewController) { // Увлекательно обрабатывайте нажатия дочерних кнопок! } никогда ничего не получает. Что-нибудь изменилось? - person bradford gray; 10.08.2017

Ну или вы можете использовать статические переменные, что, наверное, самый простой способ.

class ParentViewController: UIViewController {

    static var instance:ParentViewController?

    override func awakeFromNib() {
        self.dynamicType.instance = self
    }

    func parentFunction() {
        print("called \(#function) in \(self.dynamicType)")
    }
}

class ChildViewController: UIViewController {
    func childFunction() {
        ParentViewController.instance?.parentFunction()
    }

    override func viewDidAppear(_ animated: Bool) {
        childFunction()
    }
}
person pravdomil    schedule 27.06.2016
comment
это хорошее решение?? программно какие проблемы могут возникнуть из этого (если есть?) - person George Asda; 08.10.2016
comment
Как правило, это плохая идея по нескольким причинам. Сначала вы подключаете ChildViewController к ParentViewController, никто другой не может использовать ChildViewController. Во-вторых, если у вас есть два экземпляра ParentViewController, это больше не будет работать, потому что есть только один статический экземпляр. - person Christopher Camps; 12.02.2019