Делегирование среди контроллеров представлений контейнеров

У меня есть родительский контроллер просмотра, на котором размещены 3 контроллера просмотра контейнеров.

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

Кажется, я не могу определить, что потенциально не так с тем, как я его настроил. Если есть рекомендуемый способ передачи данных между контейнерами, я тоже внимателен!

Ниже приведена сводка кода по настройке:

class ParentViewController: UIViewController { 

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let firstContainerVC = segue.destination as? FirstContainerVC {
            //....
        }

        if let secondContainerVC = segue.destination as? SecondContainerVC {
           //....
        }        
    }
        protocol Delegate {
           func passX(a: String?)
           func passY(b: String?)
        }
}    

class FirstContainerVC: UIViewController {

    var delegate: Delegate?

    if isTrue {
        delegate.passX(a: "TestOne")
    } else {
        delegate.passY(b: "TestTwo")
    }
}

class SecondContainerVC: UIViewController, Delegate {

    override func viewDidLoad() {
        let firstVC = self.storyboard?.instantiateViewController(withIdentifier: "firstContainer") as! FirstContainerVC
    firstVC.delegate = self
    }

    func passX(a: String?) {
        //if let a = a....
    }

    func passY(b: String?) {
        //if let b = b....
    }
}

person Chris    schedule 18.05.2018    source источник
comment
Почему второй контроллер представления контейнера создает экземпляр первого контроллера представления контейнера, если все они содержатся в одном и том же родительском контроллере представления?   -  person liquid    schedule 19.05.2018
comment
@slickdaddy я думал, что мне придется создать его экземпляр, чтобы получить свойство делегата, извините, я новичок в этом, не могли бы вы уточнить предложенный способ?   -  person Chris    schedule 19.05.2018
comment
Опубликуйте свой родительский контроллер представления, и я объясню.   -  person liquid    schedule 19.05.2018
comment
@slickdaddy опубликовал повторно, не уверен, что он предоставляет детали, поскольку я встроил VC через раскадровку, но, увидев комментарий smartcats ниже, я думаю, что мне нужно реорганизовать унаследованный делегат в другом месте ... к родителю?   -  person Chris    schedule 19.05.2018
comment
Когда вы говорите, что родительский контроллер представления содержит 3 других контроллера представления, вы имеете в виду, что вы явно установили между ними отношения родитель-потомок? Или вы просто имеете в виду, что все 3 контроллера представления перемещаются из родительского контроллера представления?   -  person liquid    schedule 19.05.2018
comment
я должен был бы сказать последнее (?)... где я перетащил представление контейнера в мой родительский контроллер представления, создав встроенный переход   -  person Chris    schedule 19.05.2018


Ответы (2)


К сожалению, я не знаю, как работает перетаскивание в Xcode, я все делаю в коде. Однако, когда ваш родительский контроллер представления создает экземпляр другого контроллера представления, просто установите родителя в качестве делегата контейнера.

Создайте протокол:

protocol SomeProtocol: AnyObject {
    func passX(a: String?)
    func passY(b: String?)
}

И в контейнерах будут делегаты типа этого протокола:

class FirstContainerVC: UIViewController {
    weak var delegate: SomeProtocol?
}

class SecondContainerVC: UIViewController {
    weak var delegate: SomeProtocol?
}

Родитель должен соответствовать протоколу, чтобы он мог стать делегатом. Затем, когда вы создаете экземпляры контейнеров (что вы должны сделать только один раз в этом сценарии), установите self в качестве их делегатов:

class ParentViewController: UIViewController, SomeProtocol {

    // make the containers instance properties so that you
    // can access them from the protocol methods
    weak var firstContainerVC = FirstContainerVC()
    weak var secondContainerVC = SecondContainerVC()

    // set the delegates at some point
    func setDelegates() {
        firstContainerVC?.delegate = self
        secondContainerVC?.delegate = self
    }

    func passX(a: String?) {
        guard let a = a else {
            return
        }
        secondContainerVC?.getFromFirst(a: a)
    }

    func passY(b: String?) {
        //
    }

}

Затем, когда вы хотите перейти от первого ко второму, перейдите через делегат от первого контейнера к родителю и от родителя ко второму контейнеру.

class FirstContainerVC: UIViewController {

    weak var delegate: SomeProtocol?

    func sendToSecond() {
        delegate?.passX(a: "slick")
    }

}

class SecondContainerVC: UIViewController {

    weak var delegate: SomeProtocol?

    func getFromFirst(a: String) {
        print(a)
    }

}

Это несколько грубый пример. Вы должны кодировать свою реализацию так, как вам удобнее (т. е. изящно разворачивать, как/где вы создаете экземпляр и т. д.). Кроме того, если все контроллеры представления являются постоянными контроллерами представления (это означает, что они никогда не освобождаются), нет необходимости делать их weak var. Как бы вы это ни делали, концепции все те же. Родитель является делегатом для контейнеров, а контейнеры взаимодействуют друг с другом через родителя.

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

person liquid    schedule 18.05.2018
comment
оцените четкое пошаговое руководство, но, изменив мой код следующим образом, я, к сожалению, все еще не вижу функции протокола, запускаемой родительским элементом, а затем и функцией принимающего/завершающего контейнерного контроллера.. какие-либо другие советы или, может быть, я могу показать? - person Chris; 19.05.2018
comment
Если FirstContainerVC хочет отправить что-то SecondContainerVC, первый контейнер отправляет это родителю с помощью делегата delegate?.passX(a: "slick"). Этот метод вызывается в родительском. Затем родитель передает его второму контроллеру представления с помощью secondContainerVC?.getFromFirst(a: a). - person liquid; 19.05.2018
comment
я нашел свою проблему... нужно было назначить делегата firstContainerVC родителю в родительском методе prepareForSegue на stackoverflow.com/questions/27235071/ ценю то, что меня направили в правильном направлении! - person Chris; 20.05.2018

Я согласен с @slickdaddy в том, что ваш второй контроллер представления контейнера не имеет права создавать экземпляр вашего первого контроллера представления контейнера. Я подозреваю, что у вас уже был первый контейнерный VC, а теперь у вас их 2.

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

Держите знания о вашей иерархии текущими «вниз». Другими словами, содержащиеся или находящиеся в собственности VC не должны ничего знать о своих владельцах или родителях. Это поможет с повторным использованием, организацией и т. д.

Также рассмотрите другой подход: передачу одного и того же объекта модели в каждый содержащийся контроллер представления.

И, наконец, мой предпочтительный подход — позволить каждому контроллеру представления (или фактически его модели представления, если выполняется MVVM) обращаться к синглтону DependencyManager, в котором живут такие объекты модели. Если все сделано правильно, модульные тесты все еще могут иметь полный контроль, внедряя фиктивные модели в DependencyManager.

person Smartcat    schedule 18.05.2018