ios - UIBezierPath子类初始化器

标签 ios swift inheritance subclass uibezierpath

我正在尝试创建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'的指定初始化程序”

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

你能帮我吗?

最佳答案

注意:在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看起来好像具有初始化程序。但这是一个错误的表示。正如我们看到的Objective-C标头所示,UIBezierPath实际上具有的是便捷构造函数,它们是类方法,例如:
+ (UIBezierPath *)bezierPathWithRect:(CGRect)rect;

现在,此方法(及其 sibling )展示了一些Swift无法很好处理的异常功能:
  • 它不仅仅是初始化程序的变体;它是一个纯粹的便利构造函数。 Objective-C向我们展示UIBezierPath没有相应的真实初始化器initWithRect:。在可可中,这是非常不寻常的情况。
  • 它返回UIBezierPath*,而不是instancetype。这意味着它不能被继承,因为它返回错误类型的实例。在MyBezierPath子类中,调用bezierPathWithRect:会生成UIBezierPath,而不是MyBezierPath。

  • 斯威夫特严重地应付这种情况。一方面,它根据其通常的策略将类方法bezierPathWithRect:转换为表面上的初始化器init(rect:)。但是,另一方面,这不是“真正的”初始化程序,并且不能由子类继承。

    因此,您已经被表面上的初始化程序init(rect:)所迷惑,然后由于无法继承子类而无法对其进行调用时,感到惊讶和沮丧。

    注意:我并不是说Swift的行为不是错误;我认为这是一个错误(尽管我对将错误归咎于Swift还是UIBezierPath API还是有些困惑)。 Swift不应将bezierPathWithRect:转换为初始化器,或者如果确实使它成为初始化器,则应使该初始化器可继承。无论哪种方式,它都应该是可继承的。但这不是,所以现在我们必须寻找一种解决方法。

    解决方案

    那你该怎么办?我有两种解决方案:
  • 不要子类化。 继承UIBezierPath是一个糟糕的主意。它不是为这种事情而做的。而不是子类,而是构造一个包装器-一个类或结构,而不是具有UIBezierPath的功能,而是具有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,因此您可以使此便捷构造函数成为一个复制构造函数,仅从真实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))
    

    这不是很好,但是我认为比在解决方案中必须重新定义所有初始化程序要令人满意的多。最后,我实际上以一种压缩的方式做着与您正在做的事情完全相同的事情。但是总的来说,我更喜欢第一个解决方案:首先不要继承子类。
  • 关于ios - UIBezierPath子类初始化器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29867782/

    相关文章:

    java - 在子类中实现 clone()

    c++ - 继承编译时还是运行时?

    ios - 最快的方法 - 字符串组件之间的逗号

    ios - 如何将纬度和经度传递给另一个函数 - swift location

    ios - 通知中心 - 当他们在事件应用程序上收到通知时,通知不会出现在通知中心

    ios - Swift IAP 稍后从 json 响应中获取latest_receipt_info 值

    ios - interactivePopGestureRecognizer 弹出到根而不是 1 个顶部 Controller

    c++ - 如何访问 `polymorphic` 的任何子级的 `std::variant` 基类?

    ios - FMDBBlockSQLiteCallBackFunction 在未使用 makeFunctionNamed 的应用程序中崩溃

    ios - 如何将UIActivityIndi​​cator添加到应用程序的启动画面?