How to expose existing property on Obj-C class using an extension protocol in Swift
The compiler error message
note: Objective-C method 'isEnabled' provided by getter for 'enabled' does not match the requirement's selector ('enabled')
gives a hint about the problem. The enabled
property of UIButton
is inherited from UIControl
and in Objective-C declared as
@property(nonatomic, getter=isEnabled) BOOL enabled
Therefore the protocol method has to be
@objc protocol Enableable: class {
var enabled: Bool { @objc(isEnabled) get set }
}
and the implementation (similarly as in Swift 1.2 error on Objective-C protocol using getter):
extension UIImageView: Enableable {
var enabled: Bool {
@objc(isEnabled) get {
return alpha > DisabledAlpha
}
set(enabled) {
alpha = enabled ? EnabledAlpha : DisabledAlpha
}
}
}
Swift protocol extension in Objective-C class
Objective-C protocols cannot have default implementations.
Objective-C protocols can have real optional methods/properties, unlike Swift protocols, which only have required methods/properties. The workaround for this in Swift is the use of a default implementation, however, sadly those cannot be seen in Objective-C.
I would suggest creating a pure Swift protocol and for all Objective-C classes that want to extend this, write the conformance in Swift, then create @objc
wrapper functions in Swift that call the default protocol implementations - if it needs to be called, if it doesn't need to be called, simply ignore it.
Something along the lines of:
protocol SwiftProtocol {
func requiredFunc()
func optionalFunc()
}
extension SwiftProtocol {
func optionalFunc() {}
}
@objc extension ObjcClass: SwiftProtocol {
@objc func requiredFunc() {
print("do something")
}
// This will call the default implementation - can be omitted if you don't need to call the default implementation from Objective-C
@objc func objc_optionalFunc() {
optionalFunc()
}
}
Implement Swift protocol with Objective-C property getter
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
Accessing Obj-C properties in Swift extension file
I think that you can't access private properties from extension. Your scrollView property is in .m file, not .h - which means it's private and it's not visible from extension file.
Solution: move
@property (strong, nonatomic) UIScrollView *scrollView;
to your header file.
Swift extension storage for protocols
Any protocol object can be converted into a type-erased class. Build an AnySomeProtocol
and store that.
private var sourceKey: UInt8 = 0
final class AnySomeProtocol: SomeProtocol {
func getData() -> String { return _getData() }
init(_ someProtocol: SomeProtocol) { _getData = someProtocol.getData }
private let _getData: () -> String
}
extension UIViewController: SomeProtocolInjectable {
var source: SomeProtocol! {
get {
return objc_getAssociatedObject(self, &sourceKey) as? SomeProtocol
}
set(newValue) {
objc_setAssociatedObject(self, &sourceKey, AnySomeProtocol(newValue), .OBJC_ASSOCIATION_RETAIN)
}
}
}
class MyViewController : UIViewController {
override func viewDidLoad() {
self.title = source.getData()
}
}
The caller can only use this to access the protocol methods. You can't force it back into its original type with as
, but you should avoid that anyway.
As a side note, I'd really recommend making source
return SomeProtocol?
rather than SomeProtocol!
. There's nothing here that promises that source
will be set. You don't even set it until viewDidLoad
.
Related Topics
Swift: Gradient Along a Bezier Path (Using Calayers)
Pull Notification Locally on Jailbroken Device
Track Cellular Data Usage Using Swift
Removing Wkwebview Accesory Bar in Swift
iOS Color to Transparent in Uiimage
How to Use Multiple Segues with One Uitableviewdelegate
JSON Parsing Using Nsjsonserialization in iOS
How to Write Output of Augraph to a File
Uploads Using Backgroundsessionconfiguration and Nsurlsessionuploadtask Cause App to Crash
Swift Error: Editor Placeholder in Source File
Uibutton - Alloc Initwithframe: VS. Buttonwithtype:
How to Create/Extract an Array of Views Using @Viewbuilder in Swiftui
Swift 3 Nsnotificationcenter Keyboardwillshow/Hide
Perform Segue with Identifier Wont Work in Swift 2
How to Get Vcf Data with Contact Images Using Cncontactvcardserialization Datawithcontacts: Method