I am trying to make a Swift protocol that I can use on UILabel
, UITextField
, and UITextView
that incorporate their text
, attributedText
, and font
properties.
However, unfortunately these three classes are inconsistent with whether they use optional types for these properties or implicitly unwrapped optionals.
For example, if I create this protocol:
protocol MyProtocol: class {
var font: UIFont? { get set }
}
And I apply it:
extension UILabel: MyProtocol { }
extension UITextField: MyProtocol { }
extension UITextView: MyProtocol { }
It works fine for UITextField
and UITextView
but UILabel
's font
property is UIFont!
and so the compiler says UILabel
doesn't conform to MyProtocol
.
Additionally text
and attributedText
are optional (String?
) for UILabel
and UITextField
but implicitly unwrapped for UITextView
(String!
). So it's not even consistent which ones use optionals and which ones use implicitly unwrapped optionals for all three properties.
So then I've had to rename font
in the protocol to eg. uiFont
as essentially an alias for font
with the following implementation in each of the extensions above:
extension UILabel: MyProtocol {
var uiFont: UIFont? {
get { font }
set { font = newValue }
}
}
// … and similarly for UITextField and UITextView
This is a bit annoying as it takes away from the simplicity of the protocol.
I found this post on the Swift forum that seems to be the same issue and the discussion seems to say this is not how it's supposed to behave in Swift 4.2, but I am using Swift 5 and still getting this. There was even a proposal to abolish IUOs that got merged.
Note I am using Xcode 11.7 with iOS 13.7 on macOS Catalina 10.15.6 (19G2021).
Is there some way to avoid this problem altogether, or perhaps to make the code a bit cleaner so I don't need to have as much redundancy?
Thanks
Although it seems like a bug in the Swift, you can extend the protocol itself to make it work:
extension UILabel: MyProtocol { }
extension MyProtocol where Self: UILabel {
var font: UIFont? {
get { self.font ?? nil }
set { self.font = newValue }
}
}