Yesterday in a code review we saw someone overriding UIButton's func setTitle(String?, for: UIControl.State) and someone said, "Can't we do better? This API feels pretty Obj. C-y."
What if Swift could let the call site of such a function look like:
Would this require getter functions too? I find often times that the lack of getters sometimes faces this type of pattern. I presume that you are inferring that only a frozen enumeration would be allowed for this; which I would imagine that the authors of UIControl.State might want to have the option of one day adding an additional case if it so warranted it.
I mean, it is an Obj-C API, inherited from UIKit. If you want those properties, you could write them now (though I'd name them <state>Title). Otherwise what are you proposing?
It would be nice to have some kind of "parameterised computed properties" - basically something which looks like a function at the call site (with parameters), but works like a computed property with get/set pairs and _modify accessors.
It’s utterly boring, but worth pointing out that a nested struct grants the OP’s syntax wish:
struct ButtonStateProperties<PropType> {
var normal: PropType
var selected: PropType?
// ...etc...
}
struct Button {
var title: ButtonStateProperties<String>
var backgroundColor: ButtonStateProperties<Color>
// ...etc...
}
someButton.title.normal = "Engage!" // works
This has the advantage over using an enum that some of the state values can be optional (selected above) while normal is required.
For a little more language-evolution-y excitement, one could use property wrappers to make normal the default, and expose other states through the projected value:
struct ButtonStateProperties<PropType> {
var normal: PropType
var selected: PropType?
// ...etc...
}
@propertyWrapper struct ButtonProperty<PropType> {
var projectedValue: ButtonStateProperties<PropType>
var wrappedValue: PropType {
get { projectedValue.normal }
set { projectedValue.normal = newValue }
}
init(wrappedValue normalValue: PropType) {
projectedValue = ButtonStateProperties(normal: normalValue)
}
}
struct Button {
@ButtonProperty var title: String
@ButtonProperty var backgroundColor: Color
// ...etc...
}
someButton.title = "Engage!" // Sets the normal title
someButton.$title.selected = "Engagissimo!"
I can also imagine somewhat more unreasonable things one could do with dynamic keypath member lookup. In any case, it seems to me the language doesn’t need new features to improve on the legacy design from UIKit.
It's easiest to work with with classes, but still doable otherwise using UnsafeMutablePointer. It would be much better if it were a real language feature.
/// An emulation of the missing Swift feature of named subscripts.
/// - Note: Argument labels are not supported.
public struct ObjectSubscript<Object: AnyObject, Index, Value> {
public typealias Get = (Object) -> (Index) -> Value
public typealias Set = (Object) -> (Value, Index) -> Void
public unowned var object: Object
public var get: Get
public var set: Set
}
public extension ObjectSubscript {
init(
_ object: Object,
get: @escaping Get,
set: @escaping Set
) {
self.object = object
self.get = get
self.set = set
}
subscript(index: Index) -> Value {
get { get(object)(index) }
nonmutating set { set(object)(newValue, index) }
}
}
(On phone in bed so not long enough explaining and best links, but wanted to chip in with some UIKit and UIButton setup code examples)
I wrote ViewComposer to rid of verbose non Swifty customizarion of UIKit views. I’ve not updated it since 2018, but might give you some ideas. Allows you to write:
let button: UIButton = [.color(.red), .text("Red"), .textColor(.blue)]
Supports different texts for different button states too.
You might also find some simpler UIKit styling syntax in Zhip, I customise a button here like so:
Remember that UIControl.State is an OptionSet and there are cases where a combination of states must be used. I don't really see this as Obj-C style. The requirement is a setter that takes two parameters, which is often awkward.
All cases. The name of any OptionSet needs to be plural or end with the word Set in order to be accurate. It's UIControl.States in reality, even when it's a unit set.
This does not change that it's an Objective-C API that should be a subscript in Swift.