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
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.
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