【问题标题】:UIBezierPath Subclass InitializerUIBezierPath 子类初始化器
【发布时间】:2015-07-04 06:08:16
【问题描述】:

我正在尝试创建 UIBezierPath 的子类来添加一些对我有用的属性。

class MyUIBezierPath : UIBezierPath {
   var selectedForLazo : Bool! = false

   override init(){
       super.init()
   }

   /* Compile Error: Must call a designated initializer of the superclass 'UIBezierPath' */
   init(rect: CGRect){
       super.init(rect: rect)
   }

   /* Compile Error: Must call a designated initializer of the superclass 'UIBezierPath' */
   init(roundedRect: CGRect, cornerRadius: CGFloat) {
       super.init(roundedRect: roundedRect, cornerRadius: cornerRadius)
   }

   required init(coder aDecoder: NSCoder) {
       fatalError("init(coder:) has not been implemented")
   }
}

编辑: 我需要这个,因为在我的代码中我写了

var path = MyUIBezierPath(roundedRect: rect, cornerRadius: 7)

它会导致编译错误:

"必须调用超类 'UIBezierPath' 的指定初始化程序"

我尝试在子类中添加该初始化程序,但似乎不起作用。

你能帮帮我吗?

【问题讨论】:

  • “这不起作用”是什么意思?去掉 intWithCoder 中的 fatalError 行,将其替换为对 super 的调用。
  • 我想你只需要实现initWithCoder:。在那个中添加对 super 的调用,然后删除其他的。
  • 好的,我再问一次。 “不起作用”是什么意思?这对试图帮助你的人来说是完全没用的。你需要说出发生了什么。它不编译吗?跑的时候会不会死机?什么?具体一点。

标签: ios swift inheritance subclass uibezierpath


【解决方案1】:

注意:这个问题在 iOS 9 中得到了解决,其中的 API 已经被重写,所以 init(rect:) 存在,所有其他的也一样,作为便利初始化器,它们应该是。


问题

简而言之,您遇到的问题是以下代码无法编译:

    class MyBezierPath : UIBezierPath {}
    let b = MyBezierPath(rect:CGRectZero)

从 Swift 的角度来看,这似乎是错误的。文档似乎说 UIBezierPath 有一个初始化程序init(rect:)。但是为什么 UIBezierPath 的init(rect:) 没有在我们的子类 MyBezierPath 中被继承呢?按照初始化器继承的正常规则,应该是这样的。

解释

UIBezierPath 不适用于子类化。因此,它没有任何初始化器——除了init(),它继承自NSObject。在 Swift 中,UIBezierPath 看起来 就好像它有初始化器一样;但这是错误的表示。 UIBezierPath 实际上有什么,如果我们看一下 Objective-C 头文件,我们可以看到,是便利构造函数,它们是类方法,例如:

+ (UIBezierPath *)bezierPathWithRect:(CGRect)rect;

现在,这个方法(连同它的兄弟)展示了一些 Swift 不能很好处理的不寻常的特性:

  • 它不仅仅是初始化器的变体;它是一个 pure 便利构造函数。 Objective-C 向我们展示了 UIBezierPath 没有对应的真正初始化器initWithRect:。这在 Cocoa 中是一种非常不寻常的情况。

  • 它返回UIBezierPath*,而不是instancetype。这意味着它不能被继承,因为它返回了错误类型的实例。在 MyBezierPath 子类中,调用 bezierPathWithRect: 会产生 UIBezierPath,不是 MyBezierPath。

Swift 无法很好地应对这种情况。一方面,它按照其通常的策略将类方法bezierPathWithRect: 转换为明显的初始化程序init(rect:)。但另一方面,这不是一个“真正的”初始化器,不能被子类继承。

因此,您被明显的初始化程序 init(rect:) 误导了,然后当您无法在子类上调用它时感到惊讶和难住,因为它不是继承的。

注意:我并不是说 Swift 在这里的行为不是错误。我认为它一个错误(虽然我有点模糊是否将错误归咎于 Swift 或 UIBezierPath API)。要么 Swift 不应该将 bezierPathWithRect: 变成一个初始化器,或者,如果它确实 使它成为一个初始化器,它应该使那个初始化器可继承。无论哪种方式,它应该是可继承的。但事实并非如此,所以现在我们必须寻找解决方法。

解决方案

那你该怎么办?我有两个解决方案:

  • 不要子类化。子类化 UIBezierPath 是一个坏主意。它不是为这种事情而设计的。而不是一个子类,而是一个 wrapper - 一个类或结构,而不是具有 UIBezierPath 的特性,具有它 具有的特性 em> 一个 UIBezierPath。我们称之为 MyBezierPathWrapper:

    struct MyBezierPathWrapper {
        var selectedForLazo : Bool = false
        var bezierPath : UIBezierPath!
    }
    

    这只是将您的自定义属性和方法与普通的 UIBezierPath 结合起来。然后,您可以分两步创建它,如下所示:

    var b = MyBezierPathWrapper()
    b.bezierPath = UIBezierPath(rect:CGRectZero)
    

    如果感觉不满意,您可以通过添加一个采用 UIBezierPath 的初始化程序来一步创建:

    struct MyBezierPathWrapper {
        var selectedForLazo : Bool = false
        var bezierPath : UIBezierPath
        init(_ bezierPath:UIBezierPath) {
            self.bezierPath = bezierPath
        }
    }
    

    现在你可以像这样创建它:

    var b = MyBezierPathWrapper(UIBezierPath(rect:CGRectZero))
    
  • 带有便利构造函数的子类。如果你坚持子类化,即使 UIBezierPath 不适合这种事情,你也可以通过提供便利构造函数来实现。这是因为 UIBezierPath 唯一重要的是它的 CGPath,因此您可以将这个方便的构造函数设置为 copy 构造函数,只需从真实的 UIBezierPath 传输路径即可:

    class MyBezierPath : UIBezierPath {
        var selectedForLazo : Bool! = false
        convenience init(path:UIBezierPath) {
            self.init()
            self.CGPath = path.CGPath
        }
    }
    

    现在我们可以创建一个与之前的方法非常相似的方法:

    let b = MyBezierPath(path:UIBezierPath(rect:CGRectZero))
    

    这不是很好,但我认为这比必须像您的解决方案那样重新定义 all 初始化程序更令人满意。最后,我真的在做和你做的一模一样的事情,以一种更压缩的方式。但总的来说,我更喜欢第一个解决方案:首先不要子类化。

【讨论】:

  • 感谢 matt 的回答,我发现很奇怪 init(rect:) 不是指定的初始化程序而是方便的初始化程序,我得出了相同的结论。我会听从你的建议,我会报告错误。很抱歉之前没有更好地解释问题,并感谢您优雅的解决方案。
  • 它与指定初始化器与便利初始化器无关。它与继承有关。我已经扩展了我的答案以更全面地解释,并提供更好的解决方案。
  • 注意在iOS 9这个问题已经解决了; API 已经过调整,所以 init(rect:) 存在,所有其他的也一样,作为便利初始化器,它们应该是。
【解决方案2】:

我发现这个简单的解决方法似乎可以发挥作用。

class MyUIBezierPath : UIBezierPath {
   var selectedForLazo : Bool! = false

   override init() {
       super.init()
   }

   init(rect: CGRect){
       super.init()
       self.appendPath(UIBezierPath(rect: rect))
   }

   init(roundedRect: CGRect, cornerRadius: CGFloat) {
       super.init()
       self.appendPath(UIBezierPath(roundedRect: roundedRect, cornerRadius: cornerRadius))
   }

   required init(coder aDecoder: NSCoder) {
       super.init()
   }
}

请发布其他解决方案,因为它们肯定会比我的更好。

【讨论】:

    猜你喜欢
    • 2014-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多