I am creating a UIView subclass with the intention to force users to my required init method than the default one.
So for that, I have created a convenience method for this.
#available(*, unavailable, message: "init is unavailable.")
public override init(frame: CGRect) {
super.init(frame: frame)
}
required
convenience public init(withSomeParameters myParam:Type) {
self.init(frame: CGRect.zero)
//Doing something nice!
}
This works! However, when I try to init it's showing me two ways to initialize it. How to force the user to make use of custom init method?
You can make it private , so user must need to init Test class with withSomeParameters
class Test:UIView {
private override init(frame: CGRect) {
super.init(frame: frame)
}
convenience public init(withSomeParameters myParam:Type) {
self.init(frame: CGRect.zero)
//Doing something nice!
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Maybe you need to mark the coder initialiser as unavailable as well:
#available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Plus: remove the convenience from your initialiser, and call supers init(frame:)
public init(withSomeParameters myParam:Type) {
super.init(frame: .zero)
//Doing something nice!
}
As another example, here is some base UIView subclass I use in a lot of my projects that don't utilise storyboards:
class MXView: UIView {
init() {
super.init(frame: .zero)
}
// Storyboards are incompatible with truth and beauty.
#available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Subclass:
class CustomView: MXView {
init(someParameters params: Type) {
// Phase 1: store ivars.
super.init()
// Phase 2: Do something nice.
}
If you do it like that, users of CustomView will be forced to use init(someParamters:). init(frame:) is shadowed because init(someParameters:) is a non-convenience init.
Related
I have many custom View in my project ( UIView's subclass). And I need to override init method.
I just want to override init(frame: CGRect) method. And I don't want to write the same code init?(coder in many UIView subclasses again and again.
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
And I add an extension to UIView, then OK.
extension UIView{
convenience init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The problem occurs, when I custom UITableView class.
class Table: UITableView {
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
}
Xcode tips firstly,
'required' initializer 'init(coder:)' must be provided by subclass of 'UITableView'
class Table: UITableView {
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Xcode tips secondly,
Declaration 'init(coder:)' cannot override more than one superclass declaration
How to fix it?
You can inherit your CustomTableView class from a BaseTableView Class which is a subclass of UITableView. BaseTableView class will contain both initialiser method of UITableView. Eg:
class BaseTableView: UITableView {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
}
}
Then your custom classes are inherited from BaseTableView class with a convenience override method of init(frame:...
class Table1: BaseTableView {
convenience override init(frame: CGRect, style: UITableView.Style) {
self.init(frame: frame, style: style)
}
}
class Table2: BaseTableView {
convenience override init(frame: CGRect, style: UITableView.Style) {
self.init(frame: frame, style: style)
}
}
we use convenience override init to convey that this is a convenience init that has the same signature as the designated initializer in the superclass.
Because convenience init and required init conflicted. You can not have more than one implementation for an initializer.
I know must call a designated initializer of the superclass, I think init(type: UIButtonType) had called a designated initializer, so I used it in subclass convenience initializer, but failed
class TSContourButton: UIButton {
enum ContourButtonSizeType {
case large
case small
}
convenience init(type:ContourButtonSizeType) {
self.init(type: .custom)
}
then, I tried this. It compiles okay. but, It doesn't look professional
class TSClass: UIButton {
convenience init(frame: CGRect, myString: String) {
self.init(frame: frame)
self.init(type: .custom)
}
so, I doubt that I may think wrong. So, I did some test. It successfully called super convenience initializer. Why I can't use self.init(type: .custom) in convenience initializer at my subclass of UIButton?
class person: UIButton {
var name: String = "test"
override init(frame: CGRect) {
super.init(frame: .zero)
self.name = "one"
}
convenience init(myName: String) {
self.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class man: person {
convenience init(mySex: Int) { // it successfully call superclass convenience initializer
self.init(myName: "info")
}
If, for say, name is your mandatory field, you implement all your initial set up in a function. And you should handle if name is not available. I'll keep small as the default option if no type was provided.
// MARK:- Designated Initializers
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialSetup(type: .small)
}
override init(frame: CGRect) {
super.init(frame: frame)
initialSetup(type: .small)
}
// MARK:- Convenience Initializers
convenience init(type: ContourButtonSizeType) {
self.init(frame: .zero)
initialSetup(type: type)
}
func initialSetup(type: ContourButtonSizeType) {
// handle all initial setup
}
init(type: UIButtonType) is not UIButton's designated initializer, init(frame: CGRect) is the right designated initializer of UIButton
you just need to overwrite init(frame: CGRect)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button1 = MyButton(type: .custom)
}
}
class MyButton: UIButton {
// 初始化父类的指定构造器,然后你就可以获得父类的便利构造器
// overwrite the designated initializer of the super class, then you can automatically inherit the convenience initializer of the super class
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
if you want to customize your own button, you can add convenience init(myType: UIButton.ButtonType)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button1 = MyButton(type: .custom)
let button2 = MyButton(myType: .custom)
}
}
class MyButton: UIButton {
// 初始化父类的指定构造器,然后你就可以获得父类的便利构造器
// overwrite the designated initializer of the super class, then you can automatically inherit the convenience initializer of the super class
override init(frame: CGRect) {
super.init(frame: frame)
}
// 创建自己的遍历构造器
// this is what you want
convenience init(myType: UIButton.ButtonType) {
self.init(type: myType) // called the convenience initializer which you automatically inherit from super class
// customize your own button
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If this is useful to you, can you give me a like, (づ ̄ 3 ̄)づ
The following code returns a couple of compiler errors after converting to swift3:
override init(frame: CGRect) { //Initializer does not override a designated initializer from its superclass
super.init(frame: frame) //Must call a designated initializer of the superclass 'MKAnnotationView'
}
How do I go about fixing this?
I am guessing (from the comment in your code) that you are trying to create a subclass of MKAnnotationView. If thats true, try this.
class myAnnot : MKAnnotationView{
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Utilizing constants in subclasses with lot of initializers to override is tedious. Look at the class below, I need to duplicate the code in both initializers.
class Test : UIView {
let subview: UIView
override init(frame: CGRect) {
subview = UIView() // once
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
subview = UIView() // twice
super.init(coder: aDecoder)
}
}
If I try to make use of a common initializer then I get the following errors (see the comments)
override init(frame: CGRect) {
commonInit() // 1: Use of 'self' in method call 'commonInit' before super.init initializes self
super.init(frame: frame) // 2: Property 'self.subview' is not in initialized at super.init call
}
private func commonInit() {
subview = UIView() // 3: Cannot assign to 'subview' in 'self'
}
It works fine if I do not use a constant and define the subview like:
var subview: UIView?
And then of course switch order in init like this:
super.init(frame: frame)
commonInit()
So my question: is there no way to use a common initializer for constants in Swift as of now?
EDIT: I totally forgot to mention that the struggle here is that I can't initiate the subview before I'm in the init, it's initiated based on data that is not known when declaring the constant.
Try this:
class Test : UIView {
let subview = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Another option:
class Test : UIView {
let subview:UIView
init(frame: CGRect?, coder: NSCoder?) {
// The first phase initialization here
subview = UIView()
if let frame = frame {
super.init(frame: frame)
}
else if let coder = coder {
super.init(coder: coder)
}
else {
super.init()
}
// the Second phase initialization here
self.addSubview(subview)
}
convenience init() {
self.init(frame: nil, coder: nil)
}
override convenience init(frame: CGRect) {
self.init(frame: frame, coder: nil)
}
required convenience init(coder aDecoder: NSCoder) {
self.init(frame: nil, coder: aDecoder)
}
}
A little bit cleaner alternative:
class Test : UIView {
let subview:UIView
private enum SuperInitArg {
case Frame(CGRect), Coder(NSCoder), None
}
private init(_ arg: SuperInitArg) {
subview = UIView()
switch arg {
case .Frame(let frame): super.init(frame:frame)
case .Coder(let coder): super.init(coder:coder)
case .None: super.init()
}
addSubview(subview)
}
convenience init() {
self.init(.None)
}
override convenience init(frame: CGRect) {
self.init(.Frame(frame))
}
required convenience init(coder aDecoder: NSCoder) {
self.init(.Coder(aDecoder))
}
}
Do the following:
class Test : UIView {
let subview = UIView()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//edit subview properties as needed
}
}
This works well for me:
// Declare this somewhere (it can be used by multiple classes)
class FrameCoder: NSCoder {
let frame: CGRect
init(_ frame: CGRect) {
self.frame = frame
super.init()
}
}
Then, when you want a common initializer pattern, use this:
class MyView: UIView {
let something: SomeType
required init(coder aDecoder: NSCoder) {
if (aDecoder is FrameCoder) {
super.init(frame: (aDecoder as! FrameCoder).frame)
}
else {
super.init(coder: aDecoder)
}
// Common initializer code goes here...
something = // some value
}
override init(frame: CGRect) {
self.init(coder: FrameCoder(frame))
}
}
The advantage of using this method is you don't need to create default values for let definitions -- you can set them to the correct values in context, just like you would if there were only a single initializer.
Note that you can use this technique for initializers taking arbitrary values (not just for init(frame: CGRect)) -- you can create a specific NSCoder subclass to wrap any value and type you need to pass to an initializer, and then chain it into your init(coder:) method.
(also, there's probably some way to do this with a generic... haven't quite figured that out yet! Anyone...?)
One option is following the Xcode pattern:
class Test : UIView {
var subview: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
subview = UIView()
}
}
Notice your subview is a var
Another option is:
class Test : UIView {
let subview: UIView = {
let sv = UIView()
// some config, (i.e.: bgColor etc., frame is not yet _real_
// can't yet access instance's frame and other properties
return sv
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
// frame might be valid, preferably use layout constraints
addSubview(subview)
}
}
Hope this helps
I have the following class representing a button in my iOS 8 custom keyboard:
internal class KeyButton: UIButton {
required init(char: Character) {
super.init()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Since KeyButton is not initialised via storyboard the constructor (coder: NSCoder) would never be called.
The problem is that I am required to implement (coder: NSCoder) constructor, when I run the app I receive the exception plugin interrupted when instantiating KeyButton.
Why am I required to implement (coder: NSCoder) constructor although I instantiate everything programatically
It has nothing to do with the coder. UIButton's init() calls init(frame: CGRect), which you haven't implemented. Add the following, and you should be good to go...
override init(frame: CGRect ) {
super.init(frame: frame)
println("Button frame allocated")
}