问题描述
我正在尝试创建一个UIBezierPath的子类来添加一些对我有用的属性。
I'm trying to create a subclass of UIBezierPath to add some properties that are useful to me.
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")
}
}
编辑:
我需要这个,因为在我的代码中我写了
I need this because in my code i write
var path = MyUIBezierPath(roundedRect: rect, cornerRadius: 7)
并且它导致编译错误:
必须调用超类'UIBezierPath'的指定初始值设定项
"Must call a designated initializer of the superclass 'UIBezierPath'"
我三ed在子类中添加初始值设定项但似乎没有用。
I tried to add that initializers in the subclass but it seems not to work.
你能帮我吗?
推荐答案
注意:此问题已在iOS 9中解决,其中API已被重写,以便 init(rect:)
存在,所有其他的,作为便利初始化器,应该如此。
NOTE: This problem is solved in iOS 9, where the API has been rewritten so that init(rect:)
exists, and so do all the others, as convenience initializers, as they should be.
简而言之,您遇到的问题是以下代码无法编译:
In a nutshell, the problem you're experiencing is that the following code does not compile:
class MyBezierPath : UIBezierPath {}
let b = MyBezierPath(rect:CGRectZero)
从Swift的角度来看,这似乎是错误的。文档似乎说UIBezierPath有一个初始化器 init(rect:)
。但是为什么UIBezierPath的 init(rect:)
不在我们的子类MyBezierPath中继承?根据初始化程序继承的正常规则,它应该是。
From the Swift point of view, that seems just wrong. The documentation appears to say that UIBezierPath has an initializer init(rect:)
. But then why isn't UIBezierPath's init(rect:)
being inherited in our subclass MyBezierPath? According to the normal rules of initializer inheritance, it should be.
UIBezierPath不适用于子类。因此,它没有任何初始化器 - 除了 init()
,它继承自NSObject。在Swift中,UIBezierPath 看起来像,好像它有初始化器一样;但这是一种虚假的表现形式。 UIBezierPath实际上有什么,我们可以看到,如果我们看看Objective-C头,是方便构造函数,它们是类方法,例如:
UIBezierPath is not intended for subclassing. Accordingly, it doesn't have any initializers - except for init()
, which it inherits from NSObject. In Swift, UIBezierPath looks as if it has initializers; but this is a false representation. What UIBezierPath actually has, as we can see if we look at the Objective-C headers, are convenience constructors, which are class methods, such as this:
+ (UIBezierPath *)bezierPathWithRect:(CGRect)rect;
现在,这个方法(以及它的兄弟姐妹)展示了Swift没有处理的一些不寻常的功能好吧:
Now, this method (along with its siblings) demonstrates some unusual features that Swift does not deal with very well:
-
它不仅仅是初始化程序的变体;它是纯便利构造函数。 Objective-C向我们展示了UIBezierPath没有相应的真实初始化器
initWithRect:
。这在Cocoa中是一种非常不寻常的情况。
It is not merely a variant of an initializer; it is a pure convenience constructor. Objective-C shows us that UIBezierPath has no corresponding true initializer
initWithRect:
. That's a very unusual situation in Cocoa.
它返回 UIBezierPath *
,而不是 instancetype
。这意味着它不能被继承,因为它返回错误类型的实例。在子类MyBezierPath中,调用 bezierPathWithRect:
产生一个UIBezierPath,不一个MyBezierPath。
It returns UIBezierPath*
, not instancetype
. This means that it cannot be inherited, because it returns an instance of the wrong type. In a subclass MyBezierPath, calling bezierPathWithRect:
yields a UIBezierPath, not a MyBezierPath.
斯威夫特应对这种情况严重妥协。一方面,它将类方法 bezierPathWithRect:
转换为明显的初始化器 init(rect:)
,这与以其通常的政策。但另一方面,这不是一个真正的初始化器,并且不能被子类继承。
Swift copes badly with this situation. On the one hand, it translates the class method bezierPathWithRect:
into an apparent initializer init(rect:)
, in accordance with its usual policy. But on the other hand, this is not a "real" initializer, and cannot be inherited by a subclass.
因此你被明显的初始化器误导了 init(rect:)
然后当你无法在你的子类上调用它时会感到惊讶和难倒,因为它没有被继承。
You have thus been misled by the apparent initializer init(rect:)
and then surprised and stumped when you could not call it on your subclass because it isn't inherited.
注意:我不是说Swift在这里的行为不是一个bug;我认为它是一个错误(虽然我对是否要责怪Swift或UIBezierPath API上的错误有点朦胧)。 Swift不应该将 bezierPathWithRect:
转换为初始化程序,或者,如果 使其成为初始化程序,它应该使该初始化程序可以继承。无论哪种方式,都应该可以继承。但事实并非如此,所以现在我们必须寻找一种解决方法。
NOTE: I'm not saying that Swift's behavior here is not a bug; I think it is a bug (though I'm a little hazy on whether to blame the bug on Swift or on the UIBezierPath API). Either Swift should not turn bezierPathWithRect:
into an initializer, or, if it does make it an initializer, it should make that initializer inheritable. Either way, it should be inheritable. But it isn't, so now we have to look for a workaround.
那么应该怎样你做?我有两个解决方案:
So what should you do? I have two solutions:
-
不要继承。对UIBezierPath进行子类化是一个坏主意用。它不是为了这种事情而制造的。创建包装器 - 而不是具有 UIBezierPath的功能的类或结构,而不是子类,具有具有的功能一个UIBezierPath。我们称之为MyBezierPathWrapper:
Don't subclass. Subclassing UIBezierPath was a bad idea to start with. It is not made for this sort of thing. Instead of a subclass, make a wrapper - a class or struct that, rather than having the feature that is a UIBezierPath, has the feature that it has a UIBezierPath. Let's call it MyBezierPathWrapper:
struct MyBezierPathWrapper {
var selectedForLazo : Bool = false
var bezierPath : UIBezierPath!
}
这只是将您的自定义属性和方法与普通的UIBezierPath相结合。然后你可以分两步创建它,如下所示:
This simply couples your custom properties and methods with a normal UIBezierPath. You could then create it in two steps, like this:
var b = MyBezierPathWrapper()
b.bezierPath = UIBezierPath(rect:CGRectZero)
如果感觉不满意,可以通过以下方式创建一个添加一个带UIBezierPath的初始值设定项:
If that feels unsatisfactory, you can make this a one-step creation by adding an initializer that takes the UIBezierPath:
struct MyBezierPathWrapper {
var selectedForLazo : Bool = false
var bezierPath : UIBezierPath
init(_ bezierPath:UIBezierPath) {
self.bezierPath = bezierPath
}
}
现在您可以像这样创建它:
And now you can create it like this:
var b = MyBezierPathWrapper(UIBezierPath(rect:CGRectZero))
带有便利构造函数的子类。如果你坚持继承子类,即使UIBezierPath不适用于那种事情,你也可以通过提供一个方便的构造函数来实现。这是有效的,因为关于UIBezierPath唯一重要的是它的 CGPath
,所以你可以使这个方便构造函数成为一个 copy 构造函数,只是从一个传递路径真正的UIBezierPath:
Subclass with a convenience constructor. If you insist on subclassing, even though UIBezierPath is not intended for that sort of thing, you can do it by supplying a convenience constructor. This works because the only important thing about a UIBezierPath is its CGPath
, so you can make this convenience constructor a copy constructor merely transferring the path from a real UIBezierPath:
class MyBezierPath : UIBezierPath {
var selectedForLazo : Bool! = false
convenience init(path:UIBezierPath) {
self.init()
self.CGPath = path.CGPath
}
}
现在我们可以创建一个与上一个方法非常相似的方法:
Now we can create one very similarly to the previous approach:
let b = MyBezierPath(path:UIBezierPath(rect:CGRectZero))
这不是很好,但我认为它比你的解决方案重新定义所有初始化器更令人满意。最后,我真的以更加压缩的方式做着你正在做的事情。但总的来说,我更喜欢第一种解决方案:首先不要进行子类化。
It isn't great, but I think it's marginally more satisfying than having to redefine all the initializers as your solution does. In the end I'm really doing exactly the same thing you're doing, in a more compressed way. But on balance I prefer the first solution: don't subclass in the first place.
这篇关于UIBezierPath子类初始化程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!