UIBezierPath subclass initializer

I am trying to subclass UIBezierPath to add some properties that I find useful.

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

      

EDIT: I need this because in my code I write

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

      

and this results in a compilation error:

"Should call the designated superclass initializer 'UIBezierPath'"

I tried adding these initializers to the subclass but doesn't seem to work.

Can you help me?

+3


source to share


2 answers


NOTE . This issue was fixed in iOS 9 where the API was rewritten to exist init(rect:)

, as well as all others as convenience initializers, as they should be.


Problem

In a nutshell, the problem you are facing is that the following code won't compile:

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

      

From Swift's point of view, this doesn't seem right. The documentation seems to say that UIBezierPath has an initializer init(rect:)

. But then why is init(rect:)

n't UIBezierPath inherited in our subclass MyBezierPath? According to the normal rules of initializer inheritance, this should be.

Explanation

UIBezierPath is not intended to be subclassed. Accordingly, it has no initializers - other than init()

which it inherits from NSObject. In Swift, UIBezierPath looks like it has initializers; but this is a false idea. In fact, UIBezierPath, as we can see that we are looking at Objective-C headers, are convenience constructors which are class methods such as:

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

      



Now this method (along with its siblings) showcases some unusual features that Swift doesn't do very well with:

  • It's not just a variant of an initializer; it is a pure convenience constructor. Objective-C shows us that UIBezierPath does not have a corresponding true initializer initWithRect:

    . This is a very unusual situation in Cocoa.

  • Returns UIBezierPath*

    , not instancetype

    . This means that it cannot be inherited because it returns an instance of the wrong type. In a subclass of MyBezierPath, the call bezierPathWithRect:

    gives UIBezierPath, not MyBezierPath.

Swift handles this situation poorly. On the one hand, it translates a class method bezierPathWithRect:

into an apparent initializer init(rect:)

according to its usual policy. But on the other hand, it is not a "real" initializer and cannot be inherited by a subclass.

So you were fooled with a seeming initializer init(rect:)

and then surprised and stopped when you can't call it on your subclass because it doesn't inherit.

NOTE: I am not saying that Swift's behavior here is not a bug; I think this is a bug (although I'm a little vague about whether the bug is to blame on Swift or 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. In any case, it must be inheritable. But this is not the case, so now we must look for a workaround.

Decision

So what should you do? I have two solutions:

  • Not subclasses. Subclassing UIBezierPath was a bad idea to start with. It's not done for this kind of thing. Instead of subclassing, create a wrapper - a class or structure that, instead of a function that is UIBezierPath, has a function that has a UIBezierPath. Let's call it MyBezierPathWrapper:

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

    This just binds your custom properties and methods to a regular UIBezierPath. Then you can create it in two steps, for example:

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

    If that's not satisfactory, you can make it a one-step creation by adding an initializer that accepts a 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))
    
          

  • Subclass with a convenience constructor. If you insist on subclassing, although UIBezierPath is not designed for this kind of thing, you can do so by providing a convenience constructor. This works because the only important thing about the UIBezierPath is it CGPath

    , so you can make this convenience constructor a copy constructor just passing the path from the real UIBezierPath:

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

    We can now create it very similar to the previous approach:

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

    It's not great, but I think it's a little more satisfying than overriding all initializers like your solution does. In the end, I am doing the same thing as you in a more concise way. But in balance, I prefer the first solution: don't subclass in the first place.

+8


source


I found this simple solution that seems to do the job.

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

      



Please post other solutions, because they will certainly be better than mine.

-1


source







All Articles