10

In Objective-C category, you can bring in the extended capability introduced by the category methods by including the header of the category in your class.

It seems like all Swift extensions are automatically introduced without import. How do you achieve the same thing in Swift?

For example:

extension UIView {
  // only want certain UIView to have this, not all
  // similar to Objective-C, where imported category header
  // will grant the capability to the class
  func extraCapability() {

  }
}
Boon
  • 40,656
  • 60
  • 209
  • 315

3 Answers3

13

Define a protocol that will serve as a selection, wether the extensions should be available or not:

protocol UIViewExtensions { }

then define an extension for the protocol, but only for subclasses of UIView (the other way around won't work):

extension UIViewExtensions where Self: UIView {
    func testFunc() -> String { return String(tag) }
}

A class that is defined to have the protocol will also have the extension:

class A: UIView, UIViewExtensions { }    
A().testFunc() //has the extension

And if it is not defined to have the protocol, it will also not have the extension:

class B: UIView {}    
B().testFunc() //execution failed: MyPlayground.playground:17:1: error: value of type 'B' has no member 'testFunc'

UPDATE

Since protocol extensions don't do class polymorphism, if you need to override functions, the only thing I can think of is to subclass:

class UIViewWithExtensions: UIView {
    override func canBecomeFocused() -> Bool { return true }
}
UIViewWithExtensions().canBecomeFocused() // returns true

this could also be combined with the extension, but I don't think it would still make much sense anymore.

Community
  • 1
  • 1
Daniel
  • 20,420
  • 10
  • 92
  • 149
  • 3
    +1 Note that in ObjC, all subclasses actually get the extended methods. It's just that the compiler doesn't see them at compile time and will generate warnings if you try to use them. This matters because it's quite easy to break this kind of protection in ObjC, and there are many use cases ObjC can't achieve. This answer not only is the right way to do this, but actually does what the OP means. *Only* types assigned this extension will get it, and attempts to use it in the wrong places will generate errors, not warnings. I wouldn't even call this a "workaround protocol." It's just a protocol. – Rob Napier Jun 07 '16 at 15:43
  • There is an issue with this approach. If I testFunc() is an existing method in UIView (say pointInside:withEvent:), UIView's version is called even though it's defined in the extension. Protocol extension doesn't seem to allow override, how can we get the replaced method be called instead? – Boon Jun 07 '16 at 16:56
4

You can make extensions private for a particular class by adding private before the extension like so

private extension UIView {

  func extraCapability() {

  }
}

This will mean it can only be used in that particular class. But you will need to add this to each class that requires this extension. As far as I know there is no way to import the extension like you can in Obj-c

AdamM
  • 4,400
  • 5
  • 49
  • 95
  • 1
    I want the extension to add capability to some UIView, but not all. With ObjC category, I can do that. – Boon Jun 07 '16 at 15:18
  • 1
    Unfortunately the best you can do in Swift, is add that to the top of a class where you views require it. That way it won't be available to all views in your app, only in the classes where that is added at the top. It is not a great solution as it means repeating yourself, but I don't believe there is any other way in Swift. If there is a better solution, I would love to hear it – AdamM Jun 07 '16 at 15:21
0

NOTE Private access in Swift differs from private access in most other languages, as it’s scoped to the enclosing source file rather than to the enclosing declaration. This means that a type can access any private entities that are defined in the same source file as itself, but an extension cannot access that type’s private members if it’s defined in a separate source file.

According to Apple, here, it does not appear you can make extensions private in separate files.

You can create a private extension in the same source file.

Siriss
  • 3,737
  • 4
  • 32
  • 65