Reputation: 19239
I want to create a common ancestor for all highlightable views in Swift. I wanted that already existing UIKit classes that implement highlighted
property worked out of the box, so after reading this answer and checking that the Objective-C getter is defined as isHighlighted
I changed the protocol definition to this:
@objc protocol Highlightable {
var highlighted: Bool { @objc(isHighlighted) get set }
}
So the protocol implementation for UILabel
and UIControl
is as simple as this:
extension UILabel: Highlightable {}
extension UIControl: Highlightable {}
This works great, I can access and set the highlighted
property from Swift as Highlightable
instances. However, when I try to implement the protocol on my Swift classes with even the simplest implementation like this:
class HighlightableView: UIView, Highlightable {
var highlighted: Bool = false
}
I get this compilation error:
Objective-C method 'highlighted' provided by getter for 'highlighted' does not match the requirement's selector ('isHighlighted')
The only way I could get it to work is using computed properties, but it's not what I want.
class HighlightableView: UIView, Highlightable {
var highlighted: Bool { @objc(isHighlighted) get { return true } set {} }
}
My environment is Xcode 8.0 and Swift 3. Updated to XCode 8.2 and the error persists.
My current workaround is to completely avoid any naming conflict between Objective-C and Swift. But this is far from ideal:
@objc protocol Highlightable {
var _highlighted: Bool { get set }
}
extension UILabel: Highlightable {
var _highlighted: Bool {
get { return isHighlighted }
set { isHighlighted = newValue }
}
}
extension UIControl: Highlightable {
var _highlighted: Bool {
get { return isHighlighted }
set { isHighlighted = newValue }
}
}
class HighlightableView: UIView, Highlightable {
var _highlighted: Bool = false
}
Upvotes: 4
Views: 2387
Reputation: 2593
Your UILabel
and UIControl
extensions satisfy the way you created your protocol because they already have a property called highlighted
whose getter accessor method is isHighlighted
.
Your HighlightableView
does not satisfy the adoption of your Highlightable
protocol because you have the @objc(isHighlighted)
requirement on your getter.
You have to use computed properties to satisfy this. However, this means that you also need a backing store for the highlighted
property. Something like private var _highlighted = false
.
In your case, since this is undesirable, you could remove the @objc
attribute on your protocol.
protocol Highlightable: class {
var highlighted: Bool { get set }
}
extension UILabel: Highlightable { }
extension UIControl: Highlightable { }
class HighlightableView: UIView, Highlightable {
var highlighted = false
}
let label = UILabel()
label.isHighlighted // Prior to iOS 10, this property is called "highlighted"
let view = HighlightableView()
view.highlighted
let highlightables: [Highlightable] = [ label, view ]
for highlightable in highlightables {
print(highlightable.highlighted)
}
// Prints:
// false
// false
However the property names would not be consistent across the concrete types.
Here is an alternative approach:
@objc protocol Highlightable: class {
var isHighlighted: Bool { @objc(isHighlighted)get @objc(setHighlighted:)set }
}
extension UILabel: Highlightable { }
extension UIControl: Highlightable { }
class HighlightableView: UIView, Highlightable {
private var _isHighlighted = false
var isHighlighted: Bool {
@objc(isHighlighted) get {
return _isHighlighted
}
@objc(setHighlighted:) set {
_isHighlighted = newValue
}
}
}
let label = UILabel()
label.isHighlighted = true
let view = HighlightableView()
view.isHighlighted
let highlightables: [Highlightable] = [ label, view ]
for highlightable in highlightables {
print(highlightable.isHighlighted)
}
// Prints:
// false
// false
This exposes a consistent isHighlighted
property across all of the concrete types while still conforming to Highlightable
. The drawback here is the @objc
attribute is more pervasive in a context where is should not be necessary. That is, the @objc
attribute is not being used to expose a Swift protocol to Objective-C code.
EDIT:
Looking at the iOS 10 API diffs for Swift (and doing some testing in Xcode 7.2), UILabel
and UIControl
's isHighlighted
property was previously named highlighted
. Using the code above while linking against iOS SDK 9.3 or lower will result in compile time errors.
In the first example, these errors can be fixed by renaming the label.isHighlighted
line to label.highlighted
.
In the second example, these errors can be fixed by renaming all instances of isHighlighted
to highlighted
(except for those within the brackets of @objc
attributes).
9.3 to iOS 10.0 API Differences: https://developer.apple.com/library/content/releasenotes/General/iOS10APIDiffs/index.html
Upvotes: 1
Reputation: 212
I have a solution, but it's bit bigger than you want, however i think it will help you.
You should do swift protocol and determine default implementation for classes what you need
protocol Highlightable: class {
var highlighted: Bool { get set }
}
extension Highlightable where Self: UILabel {
var highlighted: Bool {
get {
return isHighlighted
}
set {
isHighlighted = newValue
}
}
}
extension Highlightable where Self: UIControl {
var highlighted: Bool {
get {
return isHighlighted
}
set {
isHighlighted = newValue
}
}
}
So you could implement it
extension UILabel: Highlightable {}
extension UIControl: Highlightable {}
class CustomView: UIView, Highlightable {
var highlighted: Bool = false
}
I hope it helps you.
Upvotes: 0