What initializers should a MKAnnotationView subclass have in Swift?

What initializers should a MKAnnotationView subclass have in Swift?



I'm creating a subclass of MKAnnotationView in my project. It needs to have two properties for storing subviews which I need to initialize somewhere at the beginning.


MKAnnotationView



MKAnnotationView has one initializer listed in its documentation, initWithAnnotation:reuseIdentifier:, so I figured I'd simply override that:


MKAnnotationView


initWithAnnotation:reuseIdentifier:


class PulsatingDotMarker: MKAnnotationView

let innerCircle: UIView
let outerCircle: UIView

override init!(annotation: MKAnnotation!, reuseIdentifier: String!)
innerCircle = ...
outerCircle = ...

super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)


...



But this causes a runtime exception:



fatal error: use of unimplemented initializer 'init(frame:)' for class 'PulsatingDotMarker'



Ok, so I guess initWithAnnotation:reuseIdentifier: internally calls initWithFrame:, so it's probably that one that I should override instead. Let's try that:


initWithAnnotation:reuseIdentifier:


initWithFrame:


class PulsatingDotMarker: MKAnnotationView

let innerCircle: UIView
let outerCircle: UIView

override init(frame: CGRect)
innerCircle = ...
outerCircle = ...

super.init(frame: frame)


...



This however causes a compile error when creating the annotation view object:



Extra argument 'reuseIdentifier' in call



Hmm, so if I implement the (required) initializer initWithFrame:, it now loses the default initializer initWithAnnotation:reuseIdentifier:?


initWithFrame:


initWithAnnotation:reuseIdentifier:



Maybe if I added an override of initWithAnnotation:reuseIdentifier: that just calls super it will be available again, will that work?


initWithAnnotation:reuseIdentifier:


super


class PulsatingDotMarker: MKAnnotationView

let innerCircle: UIView
let outerCircle: UIView

init!(annotation: MKAnnotation!, reuseIdentifier: String!)
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)


override init(frame: CGRect)
innerCircle = ...
outerCircle = ...

super.init(frame: frame)


...



Nope, still not good - compile error:



Property 'self.innerCircle' not initialized at super.init call



Ok, what if I had an initWithFrame:, but initialized the subviews in initWithAnnotation:reuseIdentifier:? (But then what if someone just calls initWithFrame: directly?...)


initWithFrame:


initWithAnnotation:reuseIdentifier:


initWithFrame:


class PulsatingDotMarker: MKAnnotationView

let innerCircle: UIView
let outerCircle: UIView

init!(annotation: MKAnnotation!, reuseIdentifier: String!)
innerCircle = ...
outerCircle = ...

super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)


override init(frame: CGRect)
super.init(frame: frame)


...



Not surprisingly, Swift protects me from that by telling me:



Property 'self.innerCircle' not initialized at super.init call



(this time in initWithFrame:).


initWithFrame:



So what am I supposed to do? I can't create the subviews both here and there, right?


class PulsatingDotMarker: MKAnnotationView

let innerCircle: UIView
let outerCircle: UIView

init!(annotation: MKAnnotation!, reuseIdentifier: String!)
innerCircle = ...
outerCircle = ...

super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)


override init(frame: CGRect)
innerCircle = ...
outerCircle = ...

super.init(frame: frame)


...



Wrong again, this actually works - even though I'm assigning a constant property twice in the same object (!).



How should this be done properly?



(Note: the class also included a required initWithCoder: initializer that just calls fatalError from the first example, but the object is never created from a storyboard.)


initWithCoder:


fatalError





What if the subviews are declared as var innerCircle: UIView!? That may avoid the "required init(xxx)" errors.
– user467105
Apr 10 '15 at 11:40


var innerCircle: UIView!





That's exactly what I ended up doing, but it sounds like a hack - marking a property as nullable and mutable even though it should be non-null and constant just to please the compiler...
– Kuba Suder
Apr 10 '15 at 12:12





I spent some time on similar problems, and made a test project where I think I managed to work out the cause (Objective C class calling a [self initWith...] method in its public init method), but unfortunately no solutions as yet - stackoverflow.com/questions/31161143/…
– Rupert
Jul 1 '15 at 12:24


[self initWith...]


init




3 Answers
3



Unfortunately for MKAnnotationView forces you to implement init(frame: CGRect) which means you have to initialise all your instance variables in that method as well.


MKAnnotationView


init(frame: CGRect)



This article explains it a bit more



For variables that can only be initialised with passed in values you have to make those variables optional and set them to nil in the init(frame: CGRect).


init(frame: CGRect)



The reason for this is that I suspect that MKAnnotationView is calling self.initWithFrame: rect in its objective-C init method. This is so that if a subclass overrides the initWithFrame:(CGRect) rect it will be called. However, this causes a problem in swift because if you declare a custom designated initialiser you do not inherit initialisers of the superclass. Therefore you have to implement the init(frame: CGRect) in your subclass.


self.initWithFrame: rect


initWithFrame:(CGRect) rect


init(frame: CGRect)



I have had the same problem with UITableVeiwController. Its header looks to follow the same pattern. i.e two faliable designated initialisers.


UITableVeiwController



It makes me very sad. But what can you do.



For my app, the solution I chose is to declare the subview as an optional and instantiate it in initFrame...



var innerCircle: UIView?



Here is my code...


class EventAnnotationView: MKPinAnnotationView

static var REUSE_ID = "EventAnnotationView"

var imageView: UIImageView?

override init(frame: CGRect)

super.init(frame: frame)

// Create subview for custom images
imageView = UIImageView(frame: CGRectMake(0, 0, 22, 22))

...



override init(annotation: MKAnnotation!, reuseIdentifier: String!)

super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)


required init(coder aDecoder: NSCoder)

super.init(coder: aDecoder)




Feels like less of a hack :), but requires more code/work since the subview is an optional.



Hope this helps.



There's clearly something actually broken in Swift 3 given the designated initializer isn't actually being called by iOS at run time.



I found the suggestion in the other answers don't compile (Tested on XCode 8.1 GM / iOS 10.1), but after various hacking around I found this combination works:


override init(annotation: MKAnnotation?, reuseIdentifier: String?)
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier);

/* Your actual init code */


convenience init(frame: CGRect)
self.init(annotation: nil, reuseIdentifier: nil);






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

ャフサォクコ ケウ,コ,ワ メ,ロスョノ゙,クネ,フムカヤヲニ,エコ゚ツ ウイオン゙ケワサネォキモュキォウイノンコチ゚メヌナイゥフュ,カヒウネェ ネ,ホノケ,ムュキ ッボーミュハ,チ ツス ィ メウイマヤ,゙ウチ ヅ ロ,ォジヌェ ャヌット ェ,マャ,チナエヒネソキツテ トホヲヲミーァ

How do I collapse sections of code in Visual Studio Code for Windows?