Подтвердить что ты не робот

Пользовательский init для UIViewController в Swift с настройкой интерфейса в раскадровке

У меня возникла проблема с написанием пользовательского init для подкласса UIViewController, в основном я хочу передать зависимость через метод init для viewController, а не устанавливать свойство непосредственно как viewControllerB.property = value

Итак, я создал пользовательский init для моего диспетчера viewController и вызвал супер назначенный init

init(meme: Meme?) {
        self.meme = meme
        super.init(nibName: nil, bundle: nil)
    }

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

required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

Проблема в том, что когда я пытаюсь вызвать свой пользовательский init с помощью MyViewController(meme: meme), он не запускает свойства в моем viewController вообще...

Я пытался отлаживать, я нашел в своем viewController, init(coder aDecoder: NSCoder) сначала вызывается, затем мой пользовательский init получает вызов позже. Однако эти два метода init возвращают разные адреса памяти self.

Я подозреваю что-то не так с init для моего viewController, и он всегда будет возвращать self с помощью init?(coder aDecoder: NSCoder), который не имеет реализации.

Кто-нибудь знает, как правильно настроить пользовательский init для вашего контроля над представлением? Примечание: мой интерфейс viewController настроен в раскадровке

вот мой код viewController:

class MemeDetailVC : UIViewController {

    var meme : Meme!

    @IBOutlet weak var editedImage: UIImageView!

    // TODO: incorrect init
    init(meme: Meme?) {
        self.meme = meme
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func viewDidLoad() {
        /// setup nav title
        title = "Detail Meme"

        super.viewDidLoad()
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        editedImage = UIImageView(image: meme.editedImage)
    }

}
4b9b3361

Ответ 1

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

class MemeDetailVC : UIViewController {

    var meme : Meme!

    static func makeMemeDetailVC(meme: Meme) -> MemeDetailVC {
        let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("IdentifierOfYouViewController") as! MemeDetailVC

        newViewController.meme = meme

        return newViewController
    }
}

Не забудьте указать IdentifierOfYouViewController в качестве идентификатора контроллера представления в вашей раскадровке. Вам также может понадобиться изменить название раскадровки в приведенном выше коде.

Ответ 2

Вы не можете использовать пользовательский инициализатор при инициализации из раскадровки, используя init?(coder aDecoder: NSCoder), как Apple разработала раскадровку для инициализации контроллера. Однако есть способы отправить данные в UIViewController.

В вашем контроллере имен есть detail, поэтому я предполагаю, что вы попали туда с другого контроллера. В этом случае вы можете использовать метод prepareForSegue для отправки данных в детали (это Swift 3):

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "identifier" {
        if let controller = segue.destinationViewController as? MemeDetailVC {
            controller.meme = "Meme"
        }
    }
}

Я использовал свойство типа String вместо Meme для целей тестирования. Кроме того, убедитесь, что вы передали правильный идентификатор segue ("identifier" был просто заполнителем).

Ответ 3

Один из способов, которым я это сделал, - это инициализатор удобства.

class MemeDetailVC : UIViewController {

    convenience init(meme: Meme) {
        self.init()
        self.meme = meme
    }
}

Затем вы инициализируете свой MemeDetailVC с помощью let memeDetailVC = MemeDetailVC(theMeme)

Документация Apple на инициализаторы довольно хороша, но моим личным фаворитом является Ray Wenderlich: Инициализация в глубине серии, которая должна дать вам много объяснений/примеров по вашим различным параметрам init и "правильному" способу делать что-то.


EDIT. Хотя вы можете использовать инициализатор удобства в пользовательских контроллерах представлений, все правильно утверждают, что вы не можете использовать пользовательские инициализаторы при инициализации из раскадровки или в режиме раскадровки.

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

Если вы получаете свой контроллер просмотра через раскадровку, тогда вам нужно будет следовать @Caleb Kleveter answer и затем направить контроллер вида в ваш желаемый подкласс установите свойство вручную.

Ответ 4

Как отметил @Caleb Kleveter, мы не можем использовать пользовательский инициализатор при инициализации из раскадровки.

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

Примечание. Это не точный ответ на вопрос, а обходной путь для решения проблемы.

Создайте метод класса в классе MemeDetailVC следующим образом:

// Considering your view controller resides in Main.storyboard and it identifier is set to "MemeDetailVC"
class func 'init'(meme: Meme) -> MemeDetailVC {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let vc = storyboard.instantiateViewController(withIdentifier: "MemeDetailVC") as? MemeDetailVC
    vc.meme = meme
    return vc
}

Использование:

let memeDetailVC = MemeDetailVC.init(meme: Meme())

Ответ 5

Первоначально было несколько ответов, которые были проголосованы и уничтожены коровами, хотя они были в основном правильными. Ответ в том, что вы не можете.

При работе с определением раскадровки все экземпляры контроллера просмотра архивируются. Таким образом, для их запуска потребовалось использовать init?(coder.... coder - это информация о всех настройках/представлениях.

Таким образом, в этом случае нельзя также вызвать некоторую другую функцию init с помощью настраиваемого параметра. Он должен либо быть установлен как свойство при подготовке segue, либо вы можете вырезать segues и загружать экземпляры непосредственно из раскадровки и настраивать их (в основном шаблон factory с помощью раскадровки).

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

Ответ 6

UIViewController соответствует протоколу NSCoding, который определяется как:

public protocol NSCoding {

   public func encode(with aCoder: NSCoder)

   public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}    

Итак, UIViewController имеет два назначенных инициализатора init?(coder aDecoder: NSCoder) и init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?).

Storyborad вызывает init?(coder aDecoder: NSCoder) непосредственно в init UIViewController и UIView. Вам не место для передачи параметров.

Одно громоздкое обходное решение заключается в использовании временного кеша:

class TempCache{
   static let sharedInstance = TempCache()

   var meme: Meme?
}

TempCache.sharedInstance.meme = meme // call this before init your ViewController    

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder);
    self.meme = TempCache.sharedInstance.meme
}

Ответ 7

Swift 5

Вы можете написать собственный инициализатор только для свойств типа Optional.

class MyFooClass: UIViewController {
    var foo: Foo?

    init(with foo: Foo) {
        self.foo = foo
        super.init(nibName: nil, bundle: nil)
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.foo = nil
    }
}

Ответ 8

//Контроллер вида находится в Main.storyboard и имеет установленный идентификатор

Класс B

class func customInit(carType:String) -> BViewController 

{

let storyboard = UIStoryboard(name: "Main", bundle: nil)

let objClassB = storyboard.instantiateViewController(withIdentifier: "BViewController") as? BViewController

    print(carType)
    return objClassB!
}

Класс А

let objB = customInit(carType:"Any String")

 navigationController?.pushViewController(objB,animated: true)

Ответ 9

Swift 5 - Инициализация сохраненных свойств в подклассе UIViewController

class SomeClass: UIViewController {
    var storedProperty: String

     required init?(coder aDecoder: NSCoder) {
        self.storedProperty = "Stored Prop"
        super.init(coder: aDecoder);
     }

}