Jak utworzyć niestandardowy inicjator dla podklasy UIViewController w języku Swift?

95

Przepraszam, jeśli pytano o to wcześniej, dużo szukałem i wiele odpowiedzi pochodzi z wcześniejszych wersji beta Swift, kiedy sprawy wyglądały inaczej. Nie mogę znaleźć ostatecznej odpowiedzi.

Chcę UIViewControllerutworzyć podklasę i mieć niestandardowy inicjator, który pozwoli mi łatwo skonfigurować go w kodzie. Mam problem z zrobieniem tego w Swift.

Potrzebuję init()funkcji, której mogę użyć do przekazania określonej funkcji, NSURLktórej będę używać z kontrolerem widoku. W mojej głowie wygląda to tak init(withImageURL: NSURL). Jeśli dodam tę funkcję, prosi mnie o dodanie init(coder: NSCoder)funkcji.

Wydaje mi się, że dzieje się tak, ponieważ jest oznaczony w superklasie requiredsłowem kluczowym? Więc muszę to zrobić w podklasie? Dodaję to:

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

Co teraz? Czy mój specjalny inicjator jest uważany za conveniencejeden? Wyznaczony? Czy nazywam super inicjalizator? Inicjator z tej samej klasy?

Jak dodać mój specjalny inicjator do UIViewControllerpodklasy?

Doug Smith
źródło
Inicjatory są dobre dla programowo tworzonych kontrolerów widoku, ale w przypadku kontrolerów widoku utworzonych za pomocą scenorysu masz pecha i musisz sobie z tym
poradzić

Odpowiedzi:

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
źródło
czy imageURL może być zamiast tego stałą? („niech imageURL: NSURL?)
Van Du Tran,
2
nie działa xCode 7, wymagany konstruktor nie może zainicjować właściwości poziomu klasy, w moim przypadku Int, a nie NSURL?
Chris Hayes
1
@NikolaLukic - Możemy tworzy nową instancję klasy dzwoniąc ViewController(), ViewController(imageURL: url)lub ładowanie go z ujęć.
ylin0x81
3
Musiałem napisać swój, ponieważ w required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }przeciwnym razie super.init(coder: aDecoder)otrzymywałem błąd Property self.somePropertynot initialized na super.initwezwanie
Honey
1
Nie wiem, jak to jest powiązane. Jeśli robisz czysty kod, nie musisz wypełniać swoich właściwości wewnątrz init(coder: aDecoder). fatalErrorWystarczą
Miód
26

Dla tych, którzy piszą UI w kodzie

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")
    }
}
Sasan Angel
źródło
12

Jest to bardzo podobne do innych odpowiedzi, ale z pewnym wyjaśnieniem. Przyjęta odpowiedź jest myląca, ponieważ jej właściwość jest opcjonalna i nie ujawnia faktu, że init?(coder: NSCoder)MUSISZ zainicjować każdą właściwość, a jedynym rozwiązaniem jest posiadanie fatalError(). Ostatecznie możesz uciec, tworząc opcje dla swoich właściwości, ale to tak naprawdę nie odpowiada na pytanie 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")
    }
}

Zasadniczo musisz PRZERWAĆ ładowanie go ze scenorysu. Czemu?

Bo kiedy wezwać viewController storyboard.instantiateViewController(withIdentifier: "viewController")następnie UIKit zrobi swoje rzeczy i nazywają

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

Nigdy nie możesz przekierować tego wywołania do innej metody init.

Dokumenty w instantiateViewController(withIdentifier:):

Użyj tej metody, aby utworzyć obiekt kontrolera widoku, który będzie prezentowany programowo. Za każdym razem, gdy wywołujesz tę metodę, tworzy nowe wystąpienie kontrolera widoku przy użyciu init(coder:)metody.

Jednak dla programowo utworzonych viewController lub nib utworzonych viewControllers możesz przekierować to wywołanie, jak pokazano powyżej.

kochanie
źródło
5

Wygodne inicjatory są pomocnicze i obsługują inicjatory dla klasy. Można zdefiniować wygodny inicjator, aby wywołać wyznaczony inicjator z tej samej klasy, co inicjator wygody, z niektórymi wyznaczonymi parametrami inicjatora ustawionymi na wartości domyślne. Można również zdefiniować wygodny inicjator, aby utworzyć wystąpienie tej klasy dla określonego przypadku użycia lub typu wartości wejściowej.

Są udokumentowane tutaj .

Vatsal Manot
źródło
0

Jeśli potrzebujesz niestandardowego inicjatora dla popover, na przykład, możesz użyć następującego podejścia:

Utwórz niestandardowy init, który używa superinit z nibName i bundle, a następnie uzyskaj dostęp do właściwości widoku, aby wymusić ładowanie hierarchii widoków.

Następnie w funkcji viewDidLoad możesz skonfigurować widoki za pomocą parametrów przekazanych podczas inicjalizacji.

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
źródło