How to get next case of enum(i.e. write a circulating method) in Swift 4.2
Some problems with your approach are:
- The
Collection
protocol does not define alast
property. - In order to compare the elements with
==
they have to beEquatable
. - Collection indices are not necessarily integers, they must be incremented
withindex(after:)
.
This seems to be a working solution (tested with Xcode 10.0 beta 2):
extension CaseIterable where Self: Equatable {
func next() -> Self {
let all = Self.allCases
let idx = all.firstIndex(of: self)!
let next = all.index(after: idx)
return all[next == all.endIndex ? all.startIndex : next]
}
}
Example:
enum Direction: CaseIterable {
case east, south, west, north
}
print(Direction.east.next()) // south
print(Direction.north.next()) // east
Remarks:
- Only enumerations without associated values are
CaseIterable
, and
those are alsoEquatable
(but the compiler does not figure out that
by itself). ThereforeSelf: Equatable
is not a
real restriction. Self.allCases
can be used in Swift 4.2 to access the type property
from an instance method.- The forced unwrapping is safe because we know that the value is
an element ofallCases
. - Your
enum Direction: CaseIterable
compiles because the concreteenum Direction
type isEquatable
, and itsDirection.allCases
is anArray
– which has integer indices and alast
property.
Swift Increment through Enum
Update Starting with Swift 4.2 you can make use of the newly added support CaseIterable
protocol, which adds compiler support for generating a list of all cases for an enum. Though @ninestones's comment pointed put that we are not guaranteed for allCases
to return the cases in the same order as defined, the synthesized implementation does this, and it's unlikely that definition will change.
Your enum could then look something like this (no more hardcoded start value):
enum CopyState: CaseIterable {
case binary, hex, both
mutating func next() {
let allCases = type(of: self).allCases
self = allCases[(allCases.index(of: self)! + 1) % allCases.count]
}
}
You can make this piece of functionality available to all CaseIterable
enums:
extension CaseIterable where Self: Equatable {
mutating func next() {
let allCases = Self.allCases
// just a sanity check, as the possibility of a enum case to not be
// present in `allCases` is quite low
guard let selfIndex = allCases.index(of: self) else { return }
let nextIndex = Self.allCases.index(after: selfIndex)
self = allCases[nextIndex == allCases.endIndex ? allCases.startIndex : nextIndex]
}
}
enum CopyState: CaseIterable {
case binary, hex, both
}
var state = CopyState.hex
state.next()
print(state) // both
state.next()
print(state) // binary
Or, a little bit more verbose, but with a better separation of concerns:
extension Collection {
// adding support for computing indexes in a circular fashion
func circularIndex(after i: Index) -> Index {
let nextIndex = index(after: i)
return nextIndex == endIndex ? startIndex : nextIndex
}
}
extension Collection where Element: Equatable {
// adding support for retrieving the next element in a circular fashion
func circularElement(after element: Element) -> Element? {
return index(of: element).map { self[circularIndex(after: $0)] }
}
}
// Protocol to allow iterating in place (similar to a type conforming to both Sequence and IteratorProtocol)
protocol InPlaceIterable {
mutating func next()
}
extension InPlaceIterable where Self: CaseIterable, Self: Equatable {
// adding default implementation for enums
mutating func next() {
self = type(of: self).allCases.circularElement(after: self)!
}
}
// now the enums need only the protocol conformances, they get the
// functionalities for free
enum CopyState: CaseIterable, InPlaceIterable {
case binary, hex, both
}
You could use Int
as raw value for your enum (note that this is also the default raw value if you don't specify it), and use it like this:
enum CopyState: Int {
case binary, hex, both
mutating func next(){
self = CopyState(rawValue: rawValue + 1) ?? .binary
}
}
var state = CopyState.hex
state.next()
print(state) // both
state.next()
print(state) // binary
This works fine as long as you have the raw values of the enum cases in consecutive order. By default the compiler assigns consecutive raw values.
You'd also need to keep in mind to update the next()
method if the first case changes, otherwise it will no longer correctly work.
An alternative to the above limitation, suggested by @MartinR, is to force unwrap the raw value zero:
mutating func next(){
self = CopyState(rawValue: rawValue + 1) ?? CopyState(rawValue: 0)!
}
The above code won't require updating the method when the first enum case changes, however it has the potential of crashing the app if the starting raw value of the enum changes.
Swift: Index of an element in enum (CaseIterable)
Couple of changes are necessary:
- The return type needs to be
Self.AllCases.Index?
rather thanInt?
. In practice, these types will be equivalent, as seen below. - You also need to constrain any types to
Equatable
, because you need to be equatable in order to usefirstIndex(of:)
. Again, in practice, anyCaseIterable
will usually be an enum without associated values, meaning it will be equatable automatically. - (Optional change) This function will never return
nil
, because you're finding one case in aCaseIterable
. So you can remove the optionality on the return type (Self.AllCases.Index
) and force unwrap.
Example:
public extension CaseIterable where Self: Equatable {
public func ordinal() -> Self.AllCases.Index {
return Self.allCases.firstIndex(of: self)!
}
}
enum Example: CaseIterable {
case x
case y
}
Example.y.ordinal() // 1
type(of: Example.y.ordinal()) // Int
Personally, I'd add that "Ordinal" usually means something different than what you're doing, and I'd recommend changing the function name to elementIndex()
or something. But that's an aside.
How do you check elegantly case enum in the .first(where:) closure in Swift?
first
won't do anything useful. I think you want contains
.
items.contains { Item.foo ~= $0 }
Regardless, you'll need this:
/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter case: Looks like `Enum.case`.
public func ~= <Enum: Equatable, AssociatedValue>(
case: (AssociatedValue) -> Enum,
instance: Enum
) -> Bool {
Mirror.associatedValue(of: instance, ifCase: `case`) != nil
}
/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter case: Looks like `Enum.case`.
public func ~= <Enum, AssociatedValue>(
case: (AssociatedValue) -> Enum,
instance: Enum
) -> Bool {
Mirror.associatedValue(of: instance, ifCase: `case`) != nil
}
/// Match non-`Equatable` `enum` cases without associated values.
public func ~= <Enum>(pattern: Enum, instance: Enum) -> Bool {
guard (
[pattern, instance].allSatisfy {
let mirror = Mirror(reflecting: $0)
return
mirror.displayStyle == .enum
&& mirror.children.isEmpty
}
) else { return false }
return .equate(pattern, to: instance) { "\($0)" }
}
public extension Mirror {
/// Get an `enum` case's `associatedValue`.
static func associatedValue<AssociatedValue>(
of subject: Any,
_: AssociatedValue.Type = AssociatedValue.self
) -> AssociatedValue? {
guard let childValue = Self(reflecting: subject).children.first?.value
else { return nil }
if let associatedValue = childValue as? AssociatedValue {
return associatedValue
}
let labeledAssociatedValue = Self(reflecting: childValue).children.first
return labeledAssociatedValue?.value as? AssociatedValue
}
/// Get an `enum` case's `associatedValue`.
/// - Parameter case: Looks like `Enum.case`.
static func associatedValue<Enum: Equatable, AssociatedValue>(
of instance: Enum,
ifCase case: (AssociatedValue) throws -> Enum
) rethrows -> AssociatedValue? {
try associatedValue(of: instance)
.filter { try `case`($0) == instance }
}
/// Get an `enum` case's `associatedValue`.
/// - Parameter case: Looks like `Enum.case`.
static func associatedValue<Enum, AssociatedValue>(
of instance: Enum,
ifCase case: (AssociatedValue) throws -> Enum
) rethrows -> AssociatedValue? {
try associatedValue(of: instance).filter {
.equate(try `case`($0), to: instance) {
Self(reflecting: $0).children.first?.label
}
}
}
}
public extension Optional {
/// Transform `.some` into `.none`, if a condition fails.
/// - Parameters:
/// - isSome: The condition that will result in `nil`, when evaluated to `false`.
func filter(_ isSome: (Wrapped) throws -> Bool) rethrows -> Self {
try flatMap { try isSome($0) ? $0 : nil }
}
}
public extension Equatable {
/// Equate two values using a closure.
static func equate<Wrapped, Equatable: Swift.Equatable>(
_ optional0: Wrapped?, to optional1: Wrapped?,
using transform: (Wrapped) throws -> Equatable
) rethrows -> Bool {
try optional0.map(transform) == optional1.map(transform)
}
}
How can I get similar print result of NSLog for int enum in swift?
print
uses String.init(describing:)
under the hood to convert whatever you give it to a String
, so you can do that too:
NSLog("%@", String(describing: type))
But really though, the enum should conform to CustomStringConvertible
:
enum ImportType: Int, CustomStringConvertible {
case First = 1
case None
case Original
var description: String {
switch self {
case .First:
return "First"
case .None:
return "None"
case .Original:
return "Original"
}
}
}
and you should not rely on this default behaviour of String(describing:)
, because its behaviour is not specified unless the type conforms to TextOutputStreamable
, CustomStringConvertible
or CustomDebugStringConvertible
. See here for more info.
Swift's enum with one case causes BAD_ACCESS at runtime
This is definitely a bug with the compiler. Oddly enough, adding a second case
to your enum
works perfectly:
enum OneCaseEnum {
case X
case Y
}
class OneEnumWrapper {
var myVar: OneCaseEnum
init(v: OneCaseEnum)
{
self.myVar = v
}
}
let e = OneCaseEnum.X
print(e)
let x = OneEnumWrapper(v: e) // no crash
print(x)
You can use this as a workaround until this crash is identified and fixed.
rdar://25314388
SR-1035
Sample project
Related Topics
Swift Lazy Subscript Ignores Filter
Swift 4 Codable - API Provides Sometimes an Int Sometimes a String
How to Add Constraints Programmatically to My Uilabel
Adding a View to the Window Hierarchy
My Data Changes in Uitableviewcell When I Scroll Down and Get Back
How to Continue Ble Activities Onto Next View Controller
When Should I Use Optionals and When Should I Use Non-Optionals with Default Values
Realm Mobile Platform, How to Connect While Offline
Sending a Parameter Argument to Function Through Uitapgesturerecognizer Selector
How to Determine If a Variable Passed in Is Reference Type or Value Type
How to Make Firebase Database Data the Data Source for Uicollection View
Scenekit - Animation with Dae File Format
Macos/Swift Capture Audio with Avcapturesession
Create Skscene Subclasses Programmatically, Without Size Info
Class-Only Generic Constraints in Swift
Unsafemutablepointer in Swift as Replacement for Properly Sized C Array in Obj-C
How to Fix 'Line Length Violation: Line Should Be 120 Characters or Less' - Swiftlint