Как создать собственный инициализатор для подкласса UIViewController в Swift?

95

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

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

Мне нужна init()функция, которую я могу использовать для передачи конкретной функции, которую NSURLя затем использую с контроллером представления. На мой взгляд, это выглядит примерно так init(withImageURL: NSURL). Если я добавлю эту функцию, он попросит меня добавить эту init(coder: NSCoder)функцию.

Я считаю, что это потому, что он отмечен в суперклассе requiredключевым словом? Значит, я должен сделать это в подклассе? Добавляю:

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

Что теперь? Считается ли мой специальный инициализатор единичным convenience? Назначенный? Я вызываю суперинициализатор? Инициализатор из того же класса?

Как мне добавить свой специальный инициализатор в UIViewControllerподкласс?

Дуг Смит
источник
Инициализаторы хороши для программно созданных контроллеров просмотра, но для контроллеров просмотра, созданных с помощью раскадровки, вам не повезло, и вам придется работать над этим,
Дорогая,

Ответы:

161
class ViewController: UIViewController {

    var imageURL: NSURL?

    // this is a convenient way to create this view controller without a imageURL
    convenience init() {
        self.init(imageURL: nil)
    }

    init(imageURL: NSURL?) {
        self.imageURL = imageURL
        super.init(nibName: nil, bundle: nil)
    }

    // if this view controller is loaded from a storyboard, imageURL will be nil

    /* Xcode 6
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    */

    // Xcode 7 & 8
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
ylin0x81
источник
может вместо этого imageURL быть константой? ("let imageURL: NSURL?)
Ван Дю Тран
2
не работает xCode 7, требуемый конструктор не может инициализировать свойство уровня класса, в моем случае Int, а не NSURL?
Chris Hayes,
1
@NikolaLukic - Мы можем создаем новый экземпляр класса с помощью вызова ViewController(), ViewController(imageURL: url)или загрузить его из раскадровки.
ylin0x81
3
Мне пришлось написать свой, required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }иначе super.init(coder: aDecoder)я получал ошибку « Свойство self.somePropertyне инициализировано при super.initвызове»
Дорогая,
1
Не знаю, как это связано. Если вы пишете чистый код, вам не нужно заполнять свои свойства внутри init(coder: aDecoder). fatalErrorХВАТИТ
Honey
26

Для тех, кто пишет UI в коде

class Your_ViewController : UIViewController {

    let your_property : String

    init(your_property: String) {
        self.your_property = your_property
        super.init(nibName: nil, bundle: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) is not supported")
    }
}
Сасанский ангел
источник
12

Это очень похоже на другие ответы, но с некоторыми пояснениями. Принятый ответ вводит в заблуждение, потому что его свойство является необязательным и не раскрывает тот факт, что вы init?(coder: NSCoder)ДОЛЖНЫ инициализировать каждое свойство, и единственное решение для этого - наличие fatalError(). В конечном итоге вы могли бы уйти, сделав свои свойства необязательными, но это не полностью отвечает на вопрос OP.

// Think more of a OnlyNibOrProgrammatic_NOTStoryboardViewController
class ViewController: UIViewController {

    let name: String

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // I don't have a nib. It's all through my code.
    init(name: String) {
        self.name = name

        super.init(nibName: nil, bundle: nil)
    }

    // I have a nib. I'd like to use my nib and also initialze the `name` property
    init(name: String, nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle? ) {
        self.name = name
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    // when you do storyboard.instantiateViewController(withIdentifier: "ViewController")
    // The SYSTEM will never call this!
    // it wants to call the required initializer!

    init?(name: String, coder aDecoder: NSCoder) {
        self.name = "name"
        super.init(coder: aDecoder)
    }

    // when you do storyboard.instantiateViewController(withIdentifier: "ViewController")
    // The SYSTEM WILL call this!
    // because this is its required initializer!
    // but what are you going to do for your `name` property?!
    // are you just going to do `self.name = "default Name" just to make it compile?!
    // Since you can't do anything then it's just best to leave it as `fatalError()`
    required init?(coder aDecoder: NSCoder) {
        fatalError("I WILL NEVER instantiate through storyboard! It's impossible to initialize super.init?(coder aDecoder: NSCoder) with any other parameter")
    }
}

Вы в основном должны отказаться от загрузки из раскадровки. Зачем?

Потому что , когда вы вызываете ViewController storyboard.instantiateViewController(withIdentifier: "viewController")тогда UIKit будет делать свое дело и вызов

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

Вы никогда не сможете перенаправить этот вызов другому методу инициализации.

Документы по instantiateViewController(withIdentifier:):

Используйте этот метод для создания объекта контроллера представления для программного представления. Каждый раз, когда вы вызываете этот метод, он создает новый экземпляр контроллера представления, используя этот init(coder:)метод.

Тем не менее, для программно созданных viewController или созданных пером viewController вы можете перенаправить этот вызов, как показано выше.

Мед
источник
5

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

Они задокументированы здесь .

Ватсал Манот
источник
0

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

Создайте пользовательский init, который использует super init с nibName и bundle, и после этого обратитесь к свойству представления, чтобы принудительно загрузить иерархию представления.

Затем в функции viewDidLoad вы можете настроить представления с параметрами, переданными при инициализации.

import UIKit

struct Player {
    let name: String
    let age: Int
}

class VC: UIViewController {


@IBOutlet weak var playerName: UILabel!

let player: Player

init(player: Player) {
    self.player = player
    super.init(nibName: "VC", bundle: Bundle.main)
    if let view = view, view.isHidden {}
}

override func viewDidLoad() {
    super.viewDidLoad()
    configure()
}

func configure() {
    playerName.text = player.name + "\(player.age)"
}
}

func showPlayerVC() {
    let foo = Player(name: "bar", age: 666)
    let vc = VC(player: foo)
    present(vc, animated: true, completion: nil)
}
Rockdaswift
источник