How to hide storyboards and hangs of individual initiators in user interface subclasses

Question

In cases where I know init?(coder:) , and other scripts for the / nib storyboard will not be called, can I avoid the requirement to implement or call them in subclasses of the user interface?

Background

Many UIKit classes, including UIViewController , UIView and UIView subclasses ( UIButton , UITextField , etc.) accept the NSCoding protocol. The NSCoding init?(coder:) method is used to instantiate these classes from a storyboard or nib.

NSCoding protocol:

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

Classes that accept a protocol with an initializer must implement this initializer as required . Mandatory initializers must be implemented by all subclasses.

I often create iOS apps without storyboards or tips. I fully implement subclasses of UIViewController, UIView, and UIControl in code. However, I have to implement init?(coder:) in subclasses to calm the compiler if I want to provide my own init methods (which I often do). The following examples illustrate this.

Below does not compile

 class CustomView: UIView { init() { super.init(frame: CGRect.zero) } } // Error:'required' initializer 'init(coder:)' must be provided by subclass of 'UIView' 

The compilation is done below , since I introduced the implementation of init?(coder:) . For code-only UI subclasses, I usually implement "init (coder :)", throwing a fatal error to assert that I do not expect it to be called.

 class CustomView: UIView { init() { super.init(frame: CGRect.zero) } required init?(coder aDecoder: NSCoder) { fatalError("NSCoding not supported") } } 

CustomView subclasses should also implement "init (coder :)" for the reasons stated above.

 class SubClassOfCustomView: CustomView { let someProperty: Int init(someProperty: Int) { self.someProperty = someProperty super.init() } required init?(coder aDecoder: NSCoder) { fatalError("NSCoding not supported") } } 
0
ios swift
Jan 29 '17 at 22:20
source share
1 answer

UI subclasses and @ available (*, not available)

* The code below has been written and tested in Swift 3

The essence of this solution is to create basic subclasses that inherit your custom UI subclasses. In the examples below, these subclasses are called BaseViewController , BaseView and BaseButton . These subclasses include an initializer that, by default, arguments to the initializer assigned by the superclass, which is hidden from its subclasses.

init(coder:) must be implemented in all subclasses, since it is a required initializer of interface superclasses. You can get around this by putting the @available(*, unavailable) above the implementation of this initializer.

The available attribute is used to indicate the life cycle of declarations regarding specific platforms and operating system versions. Using this attribute with the following arguments: @available(*, unavailable) makes a block of code that follows the absence of all versions of all platforms.

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 - Subclass of UIControl

This UIButton example illustrates how subclasses of UIControl subclasses.

 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) 

Questions

I do not recommend regularly using @available(*, unavailable) to avoid executing the required initializers. This is useful for reducing redundant code that won't be called in this case (since you don't plan on using storyboard / nibs). The appearance of @available(*, unavailable) reduced by using it in base classes (and inheriting custom subclasses from base classes), and not in every custom subclass.

I know this works in Swift 2 and 3. It is possible that future versions of Swift will not allow this. However, I hope that the Swift team will offer a better way to avoid this redundant code in user subclasses of the user interface.

Out of curiosity, I tried to initiate a subclass of BaseViewController from the storyboard. I expected the application to crash with an error not found in the selector, but it called the init?(coder) method, although it was hidden from all platforms. This may be due to the fact that the available attribute does not hide the init?(coder) Initializer init?(coder) from Objective-C, and the scripting code is run in it.

UIKit often uses classes and inheritance, while the Swift community encourages structuring and protocol programming. I include the following headers over the base UI declarations to prevent the base UI classes from becoming a medium for global settings and functionality.

 /** * 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 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 via composition and/or protocol oriented programming. */ 

Link: I found the Initialization Swift Language Guide section useful for understanding the rules for initializers.

+1
Jan 29 '17 at 22:20
source share



All Articles