Swift has the OptionSet type, which basically adds set operations to C-Style bit flags. Apple is using them pretty extensively in their frameworks. Examples include the options parameter in animate(withDuration:delay:options:animations:completion:)
.
On the plus side, it lets you use clean code like:
options: [.allowAnimatedContent, .curveEaseIn]
However, there is a downside as well.
If I want to display the specified values of an OptionSet
, there doesn't seem to be a clean way to do it:
let options: UIViewAnimationOptions = [.allowAnimatedContent, .curveEaseIn]
print("options = " + String(describing: options))
Displays the very unhelpful message:
options = UIViewAnimationOptions(rawValue: 65664)
The docs for some of these bit fields expresses the constant as a power-of-two value:
flag0 = Flags(rawValue: 1 << 0)
But the docs for my example OptionSet, UIViewAnimationOptions
, doesn't tell you anything about the numeric value of these flags and figuring out bits from decimal numbers is not straightforward.
Is there some clean way to map an OptionSet to the selected values?
My desired output would be something like:
options = UIViewAnimationOptions([.allowAnimatedContent, .curveEaseIn])
But I can't think of a way to do this without adding messy code that would require me to maintain a table of display names for each flag.
(I'm interested in doing this for both system frameworks and custom OptionSets I create in my own code.)
Enums let you have both a name and a raw value for the enum, but those don't support the set functions you get with OptionSets.
Here is one approach I've taken, using a dictionary and iterating over the keys. Not great, but it works.
struct MyOptionSet: OptionSet, Hashable, CustomStringConvertible {
let rawValue: Int
static let zero = MyOptionSet(rawValue: 1 << 0)
static let one = MyOptionSet(rawValue: 1 << 1)
static let two = MyOptionSet(rawValue: 1 << 2)
static let three = MyOptionSet(rawValue: 1 << 3)
var hashValue: Int {
return self.rawValue
}
static var debugDescriptions: [MyOptionSet:String] = {
var descriptions = [MyOptionSet:String]()
descriptions[.zero] = "zero"
descriptions[.one] = "one"
descriptions[.two] = "two"
descriptions[.three] = "three"
return descriptions
}()
public var description: String {
var result = [String]()
for key in MyOptionSet.debugDescriptions.keys {
guard self.contains(key),
let description = MyOptionSet.debugDescriptions[key]
else { continue }
result.append(description)
}
return "MyOptionSet(rawValue: \(self.rawValue)) \(result)"
}
}
let myOptionSet = MyOptionSet([.zero, .one, .two])
// prints MyOptionSet(rawValue: 7) ["two", "one", "zero"]
StrOptionSet Protocol:
StrOptionSet Extension:
Here is the snippet:
protocol StrOptionSet : OptionSet, CustomStringConvertible {
typealias Label = (Self, String)
static var labels: [Label] { get }
}
extension StrOptionSet {
var strs: [String] { return Self.labels
.filter{ (label: Label) in self.intersection(label.0).isEmpty == false }
.map{ (label: Label) in label.1 }
}
public var description: String { return strs.joined(separator: ",") }
}
Add the label set for target option set VTDecodeInfoFlags.
extension VTDecodeInfoFlags : StrOptionSet {
static var labels: [Label] { return [
(.asynchronous, "asynchronous"),
(.frameDropped, "frameDropped"),
(.imageBufferModifiable, "imageBufferModifiable")
]}
}
Use it
let flags: VTDecodeInfoFlags = [.asynchronous, .frameDropped]
print("flags:", flags) // output: flags: .asynchronous,frameDropped
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With