简体   繁体   中英

Why is UIControlState a Struct and not an Enum?

I'm a bit confused by how UIControlState operates. Specifically, if I look at the following example:

sender.setTitle("NewTitle", for: UIControlState.normal) 

I understand that this sets the title for the Button's (Sender's) normal state. I would have expected .normal to be an Enum value of the type UIControlState, but instead learned that it was a struct with constants. First question:

  1. If the purpose is to just pass a state, why wasn't this defined as an enum with .normal as one of the cases?

Secondly, when I look at the documentation for UIStateControl, all I see are definitions of "constants," eg:

static var normal: UIControlState

Second/Third question:

  1. Why are the "constants" for UIStateControl defined with "var" and not "let?"

  2. How is UIControlState's static property defined as type "UIControlState?" Isn't that recursive?

Why is UIControlState a Struct and not an Enum?

UIControlState is fundamentally not an enumeration. An enumeration is an "OR" type.

enum Foo {
    case a, b, c
}

var f = Foo.a
f = .b
f = .c

So, in the above example, f can hold .a or .b or .c .

But that's not the case with UIControlState ; it's an option set . An option set can hold a set of one or more cases. It is therefore an "AND" type, and we usually implement them with a struct that conforms to the OptionSet protocol.

struct Bar : OptionSet {

    let rawValue: Int

    // Note that the raw values are unique powers of two.
    // Each bit represents a flag determining if a given case is present.
    static let a = Bar(rawValue: 1) // 001
    static let b = Bar(rawValue: 2) // 010
    static let c = Bar(rawValue: 4) // 100
}

var b = Bar.a    // .a but not .b or .c
b = [.a, .b]     // .a and .b, but not .c
b = [.c, .a, .b] // .a and .b and .c

So, you can see in the above example, b can hold any set of .a , .b and .c .

And that's the same with UIControlState ; we can for example talk about a control state of being focused and highlighted:

let controlState: UIControlState = [.highlighted, .focused]

If it were an enum , we could only talk about whether a control was in one specific state, such as only highlighted or only focussed. But that's not a correct model, because controls can be in multiple different states at once.

Also it's worth noting that with UIControlState , the .normal case is equivalent to an empty set of options [] ; it means "not highlighted or focussed or selected or disabled or ...".

Secondly, when I look at the documentation for UIStateControl, all I see are definitions of "constants," eg:

 static var normal: UIControlState 

That's not quite accurate. The UIControlState declaration in the auto-generated Swift header looks like this:

public struct UIControlState : OptionSet {

    public init(rawValue: UInt)


    public static var normal: UIControlState { get }

    public static var highlighted: UIControlState { get } // used when UIControl isHighlighted is set

    public static var disabled: UIControlState { get }

    public static var selected: UIControlState { get } // flag usable by app (see below)

    @available(iOS 9.0, *)
    public static var focused: UIControlState { get } // Applicable only when the screen supports focus

    public static var application: UIControlState { get } // additional flags available for application use

    public static var reserved: UIControlState { get } // flags reserved for internal framework use
}

You'll note the { get } at the end. This means that they are just read only properties. How they are actually implemented (as let constants, var read-only computed properties etc.) is a pure implementation detail.

In this case, UIControlState is defined in UIKit with the NS_OPTIONS macro, which Swift imports in as an OptionSet conforming structure with each option value being a static read-only property.

How is UIControlState's static property defined as type "UIControlState?" Isn't that recursive?

No, it's not recursive at all. Remember, they're static properties, and as such are nothing more than global variables namespaced to UIControlState (and they don't even need to have storage; they could be computed, although, again that's an implementation detail).

If they were instance stored properties, then it would indeed be recursive. But they aren't.

Why is UIControlState a Struct and not an Enum?

Because UIControlEvents is related to the UIKit framework which has been built using Objective-C , means that it has nothing to do with the Swift enums.


If the purpose is to just pass a state, why wasn't this defined as an enum with .normal as one of the cases?

Nevertheless, UIControlEvents struct conforms to OptionSet protocol, which is part of the Swift programming language; The purpose of conforming to the OptionSet is to representing bit mask types . If you tried to create a structure which conforms to OptionSet , you would notice that implementing init(rawValue:) initializer and rawValue property are required, example:

struct CustomOptions: OptionSet {
    let rawValue: Int

    static let easy = CustomOptions(rawValue: 0b00000001) // 1
    static let medium = CustomOptions(rawValue: 0b00000010) // 2
    static let hard = CustomOptions(rawValue: 0b00000011) // 3
    static let unfair = CustomOptions(rawValue: 0b00000100) // 4
}

let myOption = CustomOptions.medium
print(myOption) // CustomOptions(rawValue: 2)

Note that the rawValues usually are unique powers of two (1, 2, 4, 8, 16, and so forth), but I just made them 1, 2, 3 and 4 for the purpose of the simplicity.

Because the options have raw values, you will be able to call the option without even the need of mentioning what's the struct, consider the following:

func doSomething(param: CustomOptions) {
    // ...
}

// you don't have to: doSomething(param: CustomOptions.medium)
// instead, you could call it like this:
doSomething(param: CustomOptions.medium)

Thus:

sender.setTitle("title", for: .normal)

Enum VS Structure conforming to OptionSet

But wait! why to use a structure that conforming to a OptionSet protocol instead of an enum?

In addition to what mentioned related to the difference between the used programming languages, enum is a representation for a single value one at a time , but OptionSet structure could be a representation for combined values in a single value , sound confusing? consider the following:

let combinedOption: CustomOptions = [.easy, .medium]
print(combinedOption) // CustomOptions(rawValue: 3)

At the first look, [.easy, .medium] might be tricky which looks like an array, but it is not! actually it is a single CustomOptions instance which is a combination (summation) of easy and medium options.

Thus:

sender.setTitle("title", for: [.normal, .disabled])


Why are the "constants" for UIStateControl defined with "var" and not "let?"

It could be related to how the representation of the bridged Objective-C enumeration cases should be, however, if you would try to:

UIControlState.normal = 3

You should obviously get the error of:

在此输入图像描述

which means that it has been declared (Objective-C) as a readonly property.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM