Як зробити власний ініціалізатор для підкласу 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підкласу?


Ініціалізатори добре підходять для програмно створених viewControllers, але для viewcontroller, створених за допомогою розкадрування, вам не пощастило, і вам доведеться це обійти
Мед,

Відповіді:


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)
    }
}

чи може imageURL замість цього бути константою? ("дозвольте imageURL: NSURL?)
Ван Ду Тран

2
не працює xCode 7, необхідний конструктор не вдається ініціалізувати властивість рівня класу, в моєму випадку це Int, а не NSURL?
Кріс Хейс,

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

Для тих, хто пише інтерфейс у коді

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(). Зрештою, ви могли б уникнути, зробивши власність необов’язковою, але це насправді не відповідає на питання ОП.

// 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")
    }
}

Вам в основному потрібно ABANDON, завантажуючи його з розкадрування. Чому?

Тому що, коли ви викликаєте viewController, storyboard.instantiateViewController(withIdentifier: "viewController")тоді UIKit зробить своє і подзвонить

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

Ви ніколи не можете перенаправити цей виклик на інший метод init.

Документи на instantiateViewController(withIdentifier:):

Використовуйте цей метод для створення об’єкта контролера подання, який буде представлений програмно. Кожного разу, коли ви викликаєте цей метод, він створює новий екземпляр контролера перегляду за допомогою init(coder:)методу.

Однак для програмно створеного viewController або створеного пером viewControllers ви можете перенаправити цей дзвінок, як показано вище.


5

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

Вони задокументовані тут .


0

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

Створіть власний init, який використовує суперініт з nibName та bundle і після цього отримує доступ до властивості view, щоб змусити завантажувати ієрархію подання.

Потім у функції 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)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.