一个MKAnnotationView子类在Swift中应该有哪些初始化方法?

11

我正在项目中创建MKAnnotationView的子类。它需要有两个属性来存储子视图,我需要在某个地方初始化这些属性。

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

    ...
}

但这会导致运行时异常:

致命错误:类“PulsatingDotMarker”使用未实现的初始化程序“init(frame:)”

好的,我想initWithAnnotation:reuseIdentifier:内部调用了initWithFrame:,所以我应该覆盖后者。让我们尝试一下:

class PulsatingDotMarker: MKAnnotationView {

    let innerCircle: UIView
    let outerCircle: UIView

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

        super.init(frame: frame)
    }

    ...
}

但是,这会在创建注释视图对象时引发编译错误:

调用中存在“reuseIdentifier”额外参数

如果我实现了(必需的)初始化程序initWithFrame:,那么默认的初始化程序initWithAnnotation:reuseIdentifier:就会丢失吗?

也许如果我添加一个重载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)
    }

    ...
}

不行,仍然不好——编译错误:

在 super.init 调用时未初始化属性 'self.innerCircle'

好的,如果我有一个 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)
    }

    ...
}

毫不意外,Swift通过告诉我以下信息来保护我:

在super.init调用时,属性“self.innerCircle”没有被初始化

(这次是在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) {
        innerCircle = ...
        outerCircle = ...

        super.init(frame: frame)
    }

    ...
}

我又错了,但实际上这个可以工作-尽管我在同一个对象中两次分配了一个常量属性(!)。

应该如何正确地完成这个任务?

(注意: 该类还包括一个必需的initWithCoder:初始化程序,在第一个示例中只是调用fatalError,但从 storyboard 中永远不会创建该对象。)


如果子视图被声明为var innerCircle: UIView!会怎样?这可能避免“required init(xxx)”错误。 - user467105
这正是我最终所做的,但听起来像是一个hack - 将属性标记为可空和可变,即使它应该是非空和常量,只是为了取悦编译器... - Kuba Suder
我花了一些时间研究类似的问题,并创建了一个测试项目,我认为我已经找出了原因(Objective C类在其公共“init”方法中调用[self initWith ...]方法),但不幸的是目前还没有解决方案 - https://dev59.com/sYzda4cB1Zd3GeqPn42a - Rupert
3个回答

8

很不幸,MKAnnotationView 强制要求您实现 init(frame: CGRect) 方法,这意味着您还必须在该方法中初始化所有实例变量。

本文 对此进行了更详细的解释。

对于只能使用传入值初始化的变量,您必须将这些变量设置为可选并在 init(frame: CGRect) 中将它们设置为 nil。

原因是我怀疑 MKAnnotationView 在其 Objective-C init 方法中调用了 self.initWithFrame: rect。 这样,如果子类覆盖了 initWithFrame:(CGRect) rect,它将被调用。 但是,在 Swift 中,这会导致问题,因为如果您声明了自定义指定初始化程序,则不会继承超类的初始化程序。 因此,您必须在子类中实现 init(frame: CGRect)

我在 UITableViewController 中也遇到了同样的问题。它的头文件似乎遵循相同的模式。 也就是两个 faliable 的指定初始化器。

这让我非常难过。 但是我们能做什么呢。


2

对于我的应用,我选择的解决方案是将子视图声明为可选项,并在initFrame中进行实例化...

var innerCircle: UIView?

下面是我的代码...

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

感觉不太像 hack :), 但需要更多的代码/工作,因为子视图是可选的。

希望这可以帮到你。


0

显然,在Swift 3中存在某些实际的问题,因为iOS在运行时实际上没有调用指定的初始化程序。

我发现其他答案中的建议无法编译(在XCode 8.1 GM / iOS 10.1上测试),但经过各种尝试,我发现这个组合是有效的:

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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接