iosswiftuilabelswift-protocolsswift-optionals

Protocol conformance with implicitly unwrapped optionals


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


Solution

  • 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 }
        }
    }