Пользовательский запуск UIViewController из раскадровки

Я попытался найти некоторые актуальные вопросы, но ничего не мог получить, надеюсь, что кто-то может помочь.

Я установил некоторый UIViewController на раскадровке. Затем я хочу загрузить один из контроллеров представления в код и вставить его в стек навигации. Я считаю, что правильный способ сделать это - использовать

instantiateViewControllerWithIdentifier

Это вызывает init(coder: NSCoder), и все хорошо, моя программа работает, но я хочу иметь собственный инициализатор, который устанавливает некоторые переменные для моего контроллера представления. Рассмотрим следующее:

class A : UIViewController {   

  let i : Int

  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    // property self.i not initialized at super.init call   
  }

}

Я, очевидно, получаю ошибку, так как i необходимо указать во время создания объекта. Любые решения? Мне не интересно объявлять i как var и настраивать его позже, поскольку это побеждает точку, и у меня больше нет гарантии компилятора, что i является неизменяемым.

Редактирование разъяснений

Предположим, что у меня есть загруженный в настоящее время ViewController, который имеет некоторую переменную i. Это значение является переменной и может меняться. Теперь предположим, что из этого ViewController я хочу представить еще один и инициализировать его с помощью i.

class ViewController: UIViewController {

   var i : Int

   // ... other things

  // in response to some button tap...
  @IBAction func tappedButton(sender: AnyObject) {
    let st = UIStoryboard(name: "Main", bundle: nil)
    let vc = st.instantiateViewControllerWithIdentifier("AControllerID") as! A
    // How do I initialize A with i ?
    self.presentViewController(vc, animated: true, completion: nil)
  }

}

Кажется, я не могу этого сделать и сохраняю i неизменяемым, используя let вместо var.

Ответ 1

Упрощение моего предыдущего ответа, которое быстро и позволяет избежать альтернативных хакерских исправлений:

Вот контроллер подробного представления, который вы, возможно, захотите создать из раскадровки с набором objectID:

import UIKit

class DetailViewController: UIViewController {

    var objectID : Int!

    internal static func instantiate(with objectID: Int) -> DetailViewController {

        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailViewController") as DetailViewController
        vc.objectID = objectID
        return vc
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        if((objectID) != nil){
            print("Here is my objectID: \(objectID)")
        }
    }
}

Вот как вы могли бы использовать его, чтобы надавить на контроллер навигации с идентификатором объекта, установленным в 1:

self.navigationController.pushViewController(DetailViewController.instantiate(1), animated: true)

Добавлено сообщение в блоге: https://theswiftcook.wordpress.com/2017/02/17/how-to-initialize-a-storyboard-viewcontroller-with-data-without-segues-swift-3-0git/

Ссылка на пример на GitHub: https://github.com/hammadzz/Instantiate-ViewController-From-Storyboard-With-Data

Ответ 2

Просто запустите свою собственность, прежде чем вызвать super.init. Это то, что вы должны сделать в Swift

class A : UIViewController {

let i : Int

required init(coder aDecoder: NSCoder) {
    i = 10;
    //Property should be init before you call super
    super.init(coder: aDecoder)
}
 }

Насколько я знаю, свойство let можно инициализировать только из метода init.

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

Ответ 3

(уродливый) способ решить эту проблему:

Вы можете установить let i из внешнего буфера в своем коде (переменная AppDelegate в этом примере)

required init?(coder aDecoder: NSCoder) {
    self.i = UIApplication.shared().delegate.bufferForI

    super.init(coder: aDecoder)
}

И когда вы инициируете свой UIViewController с помощью раскадровки:

UIApplication.shared().delegate.bufferForI = myIValue
self.navigationController!.pushViewControllerFading(self.storyboard!.instantiateViewController(withIdentifier: "myViewControllerID") as UIViewController)

EDIT: Вам не нужно передавать значение через AppDelegate. Лучший ответ здесь.

Ответ 4

Ниже приведены два помощника, один - перечисление раскадровки, добавьте каждую раскладку в свой проект в качестве примера в этом перечислении. Имя должно соответствовать файлу {storyboard_name}.storyboard. Каждый контроллер представления в вашем раскадровке должен иметь свой идентификатор раскадровки, установленный на имя класса. Это довольно стандартная практика.

import UIKit

public enum Storyboard: String {
    case Main
    case AnotherStoryboard
    //case {storyboard_name}

    public func instantiate<VC: UIViewController>(_ viewController: VC.Type) -> VC {
        guard
            let vc = UIStoryboard(name: self.rawValue, bundle: nil)
                .instantiateViewController(withIdentifier: VC.storyboardIdentifier) as? VC
            else { fatalError("Couldn't instantiate \(VC.storyboardIdentifier) from \(self.rawValue)") }

        return vc
    }

    public func instantiateInitialVC() -> UIViewController {

        guard let vc = UIStoryboard(name: self.rawValue, bundle: nil).instantiateInitialViewController() else {
            fatalError("Couldn't instantiate initial viewcontroller from \(self.rawValue)")
        }

        return vc
    }
}

extension UIViewController {
    public static var defaultNib: String {
        return self.description().components(separatedBy: ".").dropFirst().joined(separator: ".")
    }

    public static var storyboardIdentifier: String {
        return self.description().components(separatedBy: ".").dropFirst().joined(separator: ".")
    }
}

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

import UIKit

class DetailViewController: UIViewController {

    var objectID : Int!
    var objectDetails: ObjectDetails = ObjectDetails()        

    internal static func instantiate(with objectID: Int) -> DetailViewController {

        let vc = Storyboard.Main.instantiate(DetailViewController.self)
        vc.objectID = objectID
        return vc
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        if((objectID) != nil){
            // In this method I use to make a web request to pull details from an API
            loadObjectDetails()
        }
    }
}

(Архитектура повлияла/копирует проект iOS с открытым исходным кодом Kickstarter)