Swift Increment Through Enum

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.

how to increment a swift int enumeration

This is not C, where enums are integers. In swift an enum is a proper type in it's own right and you cannot perform math operations on it.

However, you can grab the raw value which is an integer, and do math on that. Then create a new enum:

var newState = MainState(rawValue: state.rawValue + 1)

Note that "newState" is an optional. You'll get null if rawValue + 1 doesn't exist in the enum.

Looping through enum values in Swift

Is there another way? Sure. Is it better, that's for you to decide:

func generateDeck() -> Card[]
{
let ranksPerSuit = 13
var deck = Card[]()

for index in 0..52
{
let suit = Suit.fromRaw(index / ranksPerSuit)
let rank = Rank.fromRaw(index % ranksPerSuit + 1)

let card = Card(rank: rank!, suit: suit!)
deck.append(card)
}

return deck
}

let deck = generateDeck()

for card : Card in deck { println("\(card.description)") }

To use this, you will need to make sure that Rank and Suit enums both use Int for their type definitions (ex: enum Rank : Int).

Rank.Ace should equal 1 and the first Suit case should equal 0.

If you want to loop similar to your existing code, you should still make your enums Int types so you can use Rank.King.toRaw() and the like.

The Apple documentation states that enums are not restricted to being 'simply integer values', but certainly can be if you desire them to be.

UPDATE

Idea taken from comment by @jay-imerman, and applicable to Swift 5

extension Rank: CaseIterable {}
extension Suit: CaseIterable {}

func generateDeck() -> [Card] {
var deck = [Card]();

Rank.allCases.forEach {
let rank = $0

Suit.allCases.forEach {
let suit = $0

deck.append(Card(rank: rank, suit: suit))
}
}

return deck;
}

How to enumerate an enum with String type?

Swift 4.2+

Starting with Swift 4.2 (with Xcode 10), just add protocol conformance to CaseIterable to benefit from allCases. To add this protocol conformance, you simply need to write somewhere:

extension Suit: CaseIterable {}

If the enum is your own, you may specify the conformance directly in the declaration:

enum Suit: String, CaseIterable { case spades = "♠"; case hearts = "♥"; case diamonds = "♦"; case clubs = "♣" }

Then the following code will print all possible values:

Suit.allCases.forEach {
print($0.rawValue)
}


Compatibility with earlier Swift versions (3.x and 4.x)

If you need to support Swift 3.x or 4.0, you may mimic the Swift 4.2 implementation by adding the following code:

#if !swift(>=4.2)
public protocol CaseIterable {
associatedtype AllCases: Collection where AllCases.Element == Self
static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
static var allCases: [Self] {
return [Self](AnySequence { () -> AnyIterator<Self> in
var raw = 0
var first: Self?
return AnyIterator {
let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
if raw == 0 {
first = current
} else if current == first {
return nil
}
raw += 1
return current
}
})
}
}
#endif

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 a last property.
  • In order to compare the elements with == they have to be Equatable.
  • Collection indices are not necessarily integers, they must be incremented
    with index(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 also Equatable (but the compiler does not figure out that
    by itself). Therefore Self: 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 of allCases.
  • Your enum Direction: CaseIterable compiles because the concrete
    enum Direction type is Equatable, and its Direction.allCases is an Array – which has integer indices and a last property.

How to iterate through a enum to create a custom picker?

You're trying to use ForEach on a single ActivityLevelSelector item. For ForEach to work, it needs to be a collection of items (an Array, for example). This might be more what you're looking for:

struct ContentView: View {
let activityLevels: [ActivityLevelSelector] = ActivityLevelSelector.allCases

var body: some View {
NavigationView {
VStack {
Text("Please select your activity level.")
.font(.caption)
LazyHGrid(rows: [GridItem(.fixed(2))]) {
ForEach(activityLevels, id: \.self) { levelSelection in
Text(levelSelection.description)
}
}
}
}
}
}

If .allCases is indeed what you want, you could simplify even a bit more, getting rid of the let activityLevels and doing::

ForEach(ActivityLevelSelector.allCases, id: \.self) { levelSelection in

Note also that you can't just print inside the ForEach -- you need to return a View of some sort.

Swift - Iterate through all cases of a nested enumeration

The error
"Cannot assign value of type 'UserSettings.SocialSettings' to type 'UserSettings'" is on cell.passedUserSetting = currentSetting.
You are giving context, but passedUserSetting is a UserSettings.

But when you do:

let currentSetting = UserSettings.SocialSettings.allCases[indexPath.row]

So, it means currentSetting is a UserSettings.SocialSettings.

So you are trying to set a value of type B to expected value of type A. It won't work.

You can have with you enum a UserSettings with a UserSettings.SocialSettings:

cell.passedUserSetting = .socialSettings(currentSetting)

Enumeration on enums with associated values - Swift

You can make your enum conform to CaseIterable, then simply iterate through allCases to create typeDescription.

enum MyEnum: CaseIterable {
case caseA(data: [Int])
case caseB(data: [String])
case caseC(data: [Date])
case caseD(data: [Data])

static var allCases: [MyEnum] = [.caseA(data: []), .caseB(data: []), .caseC(data: []), .caseD(data: [])]

var caseDescription: String {
switch self {
case .caseA:
return "caseA"
case .caseB:
return "caseB"
case .caseC:
return "caseC"
case .caseD:
return "caseD"
}
}

static var typeDescription: String {
return allCases.map {$0.caseDescription}.joined(separator: ", ")
}
}


Related Topics



Leave a reply



Submit