What initializers should a MKAnnotationView subclass have in Swift?
Asked Answered
C

3

11

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 has one initializer listed in its documentation, initWithAnnotation:reuseIdentifier:, so I figured I'd simply override that:

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:

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:?

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

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

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

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

Costin answered 9/4, 2015 at 23:43 Comment(3)
What if the subviews are declared as var innerCircle: UIView!? That may avoid the "required init(xxx)" errors.Manual
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...Costin
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 - #31161643Earthenware
O
8

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.

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

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.

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

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

Oney answered 1/7, 2015 at 9:27 Comment(0)
F
2

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.

Fachini answered 10/6, 2015 at 8:7 Comment(0)
G
0

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);
}
Gauche answered 26/10, 2016 at 17:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.