如何在UI子类中隐藏Storyboard和Nib特定的初始化器

时间:2017-01-29 22:20:19

标签: ios swift

问题

如果我知道init?(coder:)并且不会调用其他storyboard / nib特定的初始值设定项,我是否可以避免在UI子类中实现或调用它们的要求?

背景

许多UIKit课程包括UIViewControllerUIViewUIControlUIButtonUITextField等的子类)采用NSCoding协议。 NSCoding init?(coder:)方法用于从故事板或笔尖实例化这些类。

NSCoding协议:

public protocol NSCoding {
   func encode(with aCoder: NSCoder)
   init?(coder aDecoder: NSCoder)
}

采用带初始化程序的协议的类必须将该初始化程序实现为required。必需的初始值设定项必须由所有子类实现。

我经常构建没有故事板或笔尖的iOS应用程序。我在代码中完全实现了UIViewController,UIView和UIControl子类。但是,如果我想提供自己的init?(coder:)方法(我经常这样做),我必须在子类中实现init来安抚编译器。以下示例说明了这一点。

以下内容无法编译

class CustomView: UIView {
    init() {
        super.init(frame: CGRect.zero)
    }
}

// Error:'required' initializer 'init(coder:)' must be provided by subclass of 'UIView'

以下编译,因为我提供了init?(coder:)的实现。对于仅代码的UI子类,我通常通过抛出一个致命错误来声明我不希望它被调用来实现'init(coder :)'。

class CustomView: UIView {
    init() {
        super.init(frame: CGRect.zero)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("NSCoding not supported")
    }
}

由于上述原因,CustomView的子类还需要实现'init(coder:)'

class SubClassOfCustomView: CustomView {
    let someProperty: Int

    init(someProperty: Int) {
        self.someProperty = someProperty
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("NSCoding not supported")
    }
}

1 个答案:

答案 0 :(得分:2)

UI子类& @available(*,不可用)

*以下代码是在Swift 3中编写和测试的

此解决方案的关键是创建自定义UI子类继承的基类。在下面的示例中,这些子类名为BaseViewControllerBaseViewBaseButton。这些子类包含一个初始化程序,它默认超类的指定初始化程序的参数,这些参数对于它们的子类是隐藏的。

init(coder:)必须在所有子类中实现,因为它是UI超类的必需初始化程序。您可以通过将属性@available(*, unavailable)置于该初始化程序的实现之上来解决此问题。

The available attribute用于“表示声明相对于某些平台和操作系统版本的生命周期”。将此属性与以下参数一起使用:@available(*, unavailable)使得在所有平台的所有版本上都不可用的代码块。

的UIViewController

class BaseViewController: UIViewController {

    // This initializer hides init(nibName:bundle:) from subclasses
    init() {
        super.init(nibName: nil, bundle: nil)
    }

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("NSCoding not supported")
    }
}

class CustomViewController: BaseViewController {
    let someProperty: Int

    init(someProperty: Int) {
        self.someProperty = someProperty
        super.init()
    }
}

let customViewController = CustomViewController(someProperty: 1)

的UIView

class BaseView: UIView {

    // This initializer hides init(frame:) from subclasses
    init() {
        super.init(frame: CGRect.zero)
    }

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("NSCoding not supported")
    }
}

class CustomView: BaseView {
    let someProperty: Int

    init(someProperty: Int) {
        self.someProperty = someProperty
        super.init()
    }
}

let customView = CustomView(someProperty: 1)

UIButton - UIControl Subclass

此UIButton示例说明了如何子类化UIControl子类。

internal class BaseButton: UIButton {

    // This initializer hides init(frame:) from subclasses
    init() {
        super.init(frame: CGRect.zero)
    }

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("NSCoding not supported")
    }
}

class CustomButton: BaseButton {
    let someProperty: Int

    init(someProperty: Int) {
        self.someProperty = someProperty
        super.init()
    }
}

let customButton = CustomButton(someProperty: 1)

考虑

我不建议定期使用@available(*, unavailable)来避免实现所需的初始化程序。它有利于减少在这种情况下不会被调用的冗余代码(因为您不打算使用storyboard / nib)。 @available(*, unavailable)的外观通过在基类中使用它(并且具有从基类继承的自定义子类)而不是在每个自定义子类中来减少。

我知道这在Swift 2和3中有效。未来版本的Swift可能不允许这样做。但是,我希望Swift团队提出一种更好的方法来避免自定义UI子类中的冗余代码。

出于好奇,我尝试从故事板中启动BaseViewController子类。我预计应用程序会因找不到选择器错误而崩溃,但它会调用init?(coder)方法,即使它已从所有平台隐藏。这可能是因为available属性没有隐藏Objective C中的init?(coder)初始值设定项,而且这是故事板实例化代码运行的地方。

UIKit经常使用类和继承,而Swift社区鼓励结构和面向协议的编程。我在基本UI类声明之上包含以下标题,以阻止基本UI类成为全局设置和功能的转储基础。

/**
 *  This base UIViewController subclass removes the requirement to override init(coder:) and hides init(nibName:bundle:) from subclasses.
 *  It is not intended to create global functionality inherited by all UIViewControllers.
 *  Alternatively, functionality can be added to UIViewController's via composition and/or protocol oriented programming.
 */

/**
 *  This base UIView subclass removes the requirement to override init(coder:) and hides init(frame:) from subclasses.
 *  It is not intended to create global functionality inherited by all UIViews.
 *  Alternatively, functionality can be added to UIView's via composition and/or protocol oriented programming.
 */

参考:我发现Swift语言指南的Initialization部分有助于理解初始化器的规则。