Unexpected Cannot assign to property 'self' is immutable compile time error IN CLASS

Viewed 334

Now this is a weird behaviour of Swift compiler. I get a Cannot assign to property 'self' is immutable compile time error in a class. Here is the minimal playground code:

import UIKit

open class TextInputTraitsWrapper: NSObject {
    private var wrapped: UITextInputTraits
    
    public init(wrapped: UITextDocumentProxy) {
        self.wrapped = wrapped
        super.init()
    }
    
    open var keyboardAppearance: UIKeyboardAppearance {
        get {
            return wrapped.keyboardAppearance ?? UIKeyboardAppearance.default
        }
        set {
            wrapped.keyboardAppearance = newValue // ERROR ON THIS LINE
        }
    }
}

And this is the error that I get (this is the complete program):

enter image description here

As a side note protocol UITextInputTraits defines keyboardAppearance like this: optional var keyboardType: UIKeyboardType { get set }.

Why do I get this kind of error in a class??

2 Answers

It's a bug, but not the bug you might think (and the error message is no help at all). It's a known bug caused by the fact that the keyboardAppearance property of the UITextInputTraits protocol is an optional property (meaning that it might not be implemented). This is an Objective C feature, not a Swift feature, and the bug is that such properties are not directly settable in Swift, even when they are marked {get set}.

To see this, let's emulate the same schema for ourselves:

@objc public protocol P {
    @objc optional var keyboardAppearance : NSString {get set}
}

open class C : NSObject {
    private var wrapped: P
    public init(wrapped: P) {
        self.wrapped = wrapped
        super.init()
    }
    open var keyboardAppearance : NSString {
        get {
            wrapped.keyboardAppearance ?? "" as NSString
        }
        set {
            wrapped.keyboardAppearance = newValue
        }
    }
}

We get the same error. But if you delete the keyword optional, the error goes away. This proves that the optional is what causes the issue. But you cannot delete optional in your situation, because that protocol doesn't belong to you.

The workaround is the same as for the original bug — use a key path:

let kp = \C.wrapped.keyboardAppearance
self[keyPath:kp] = newValue

The error is unrelated to the class you created. It is related to the underlying protocol of your variable "wrapped". You can't directly modify the keyboardAppearance property.

Instead you need to modify it at the source. That is your textfield/search bar where the user is typing.

To do that just get access to your UITextField /UISearchBar and change the keyboardAppearance property there.

textField.keyboardAppearance = UIKeyboardAppearance.dark
Related