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")
}
}
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 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.
I have a simple UIView subclass that looks like this:
import UIKit
class AngleViewManager: UIView {
class AngleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.red
}
init(first: CGPoint, second: CGPoint, third: CGPoint) {
// setup
super.init(frame: CGRect(dimensions))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
When I initialize an AngleView instance within AngleViewManager like
AngleView(firstPoint, secondPoint, thirdPoint),
the background color is not red, and setting a breakpoint in the overridden init shows that it is never called even though I am explicitly calling it in my custom initializer which does get called successfully.
Am I missing something obvious?
You're calling the super class's (UIView) implementation of init(frame: CGRect).
Just change it to self.init(frame: frame)
I'm trying to create a custom init method for a UIView like so:
convenience init(frame: CGRect, tutorProfileImageURL: String?) {
self.tutorProfileImageURL = tutorProfileImageURL
super.init(frame: frame)
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
_ = Bundle.main.loadNibNamed("TutorArrivedAlertView", owner: self, options: nil)?[0] as! UIView
self.addSubview(customView)
customView.frame = self.bounds
tutorImageView.layer.cornerRadius = tutorImageView.frame.size.height/2
tutorImageView.clipsToBounds = true
}
However I am getting the error:
Convenience initializer must delegate with self.init rather than chaining to a superclass initializer with super.init
This error indicates, we shouldn't chain convenience initializer with its super class initializer.
We need to call following method
super.init(frame: frame)
inside this method
override init(frame: CGRect)
This is what it looks like:
convenience init(frame: CGRect, tutorProfileImageURL: String?) {
self.init(frame: frame)
self.tutorProfileImageURL = tutorProfileImageURL
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
Check difference between Convenience and Designation initializers
The convenience initializer uses his init for it. So just change the last line, to self.init
convenience init(frame: CGRect, tutorProfileImageURL: String?) {
self.init(frame: frame)
self.tutorProfileImageURL = tutorProfileImageURL
}
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