如何在Swift中为UIViewController子类创建自定义初始化函数?

122

如果此前已经问过这个问题,我很抱歉。我搜索了很多,但许多答案都是在早期的 Swift beta 版本中,当时情况有所不同。我似乎找不到一个明确的答案。

我想要子类化 UIViewController 并拥有一个自定义初始化函数,以便我可以轻松地在代码中设置它。我在 Swift 中遇到了一些问题。

我想要一个init()函数,可以用来传递一个特定的NSURL,然后我将与视图控制器一起使用它。在我的脑海中,它看起来像init(withImageURL: NSURL)。如果我添加那个函数,它会提示我添加init(coder: NSCoder)函数。

我认为这是因为它在父类中用required关键字标记了?所以我必须在子类中这样做?我添加了它:

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

现在怎么办?我的特殊初始化器被认为是“convenience”初始化器还是“designated”初始化器?我要调用超类初始化器吗?还是同一类中的初始化器?

我该如何将我的特殊初始化器添加到UIViewController子类中?


2
初始化程序对于通过编程方式创建的视图控制器非常有用,但是对于通过storyboard创建的视图控制器来说,则需要绕开这个问题。详情请参见此处链接:https://dev59.com/uFsW5IYBdhLWcg3wKkgz#39400793 - mfaani
5个回答

214
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
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

2
xCode 7不起作用,所需的构造函数无法初始化类级属性,在我的情况下是一个Int,而不是NSURL? - Chris Hayes
1
@NikolaLukic - 我们可以通过调用 ViewController()ViewController(imageURL: url) 或从故事板中加载来创建该类的新实例。 - linimin
3
为了避免出现错误:在调用super.init时,属性self.someProperty未被初始化,我必须将我的代码写成以下形式:required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") },而不是使用super.init(coder: aDecoder) - mfaani
@Honey - 在调用 super.init 之前,所有存储属性都必须被赋予一个初始值。在上面的示例代码中,imageURL 是一个可选变量,因此默认为 nil。 - linimin
2
不确定这与什么有关。如果你只是在写代码,那么你不需要在 init(coder: aDecoder) 中填充属性。fatalError 就足够了。 - mfaani
显示剩余9条评论

50

对于那些用代码编写用户界面的人

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

30

这与其他答案非常相似,但有一些解释。接受的答案是误导性的,因为它的属性是可选的,并且没有暴露你的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")
    }
}

你基本上必须放弃从storyboard中加载它。为什么?

因为当你调用一个viewController storyboard.instantiateViewController(withIdentifier: "viewController"),然后UIKit会做它的事情并调用

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

你永远无法将该调用重定向到另一个init方法。

instantiateViewController(withIdentifier:)的文档:

使用此方法以编程方式创建视图控制器对象。每次调用此方法时,它都会使用init(coder:)方法创建视图控制器的新实例。

但对于以编程方式创建的viewController或使用nib创建的viewControllers,您可以像上面显示的那样重定向该调用。


5
现在这种说法并不严格正确,你可以使用 iOS 13+ 中提供的 instantiateViewController(identifier:creator:) 方法将调用“重定向”到另一个初始化方法。 - Peter Kovacs
如何使用:https://www.hackingwithswift.com/example-code/uikit/how-to-use-dependency-injection-with-storyboards - alegelos

5
方便初始化器是类的次要支持初始化器。您可以定义一个方便的初始化器,以使用指定初始化器的某些参数设置为默认值来调用与方便初始化器相同类中的指定的初始化器。您还可以定义方便初始化器来创建该类的实例以用于特定用例或输入值类型。相关文档可以在这里找到。

0
如果您需要为弹出窗口创建自定义init,例如,可以使用以下方法:
创建一个自定义init,该init使用nibName和bundle的super init,然后访问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)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接