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 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)
Introspection and iteration on an Enum
You can write a generic struct that provide that iteration ability. In example below the enum raw values must start with 0 (it does it by default) and have no gaps between raw values (can't have raw values such as 0,1,2,3,5 -- 4 is missing)
public struct EnumGenerator<T> : GeneratorType, SequenceType {
private let enumInit: Int -> T?
private var i: Int = 0
public mutating func next() -> T? { return enumInit(i++) }
public init(_ initFunc: Int -> T?){ self.enumInit = initFunc}
}
enum HeaderStyles:Int{
case h1
case h2
case h3
}
for item in EnumGenerator(HeaderStyles.init){
print(item)
}
Looping over Objective-C enum in Swift?
In C, and therefore in Objective-C, enum
s are defined to have this behaviour:
If the first enumerator has no =, the value of its enumeration
constant is 0. Each subsequent enumerator with no = defines its
enumeration constant as the value of the constant expression obtained
by adding 1 to the value of the previous enumeration constant.
So in your case you could do:
for enumerationValue in A...C
If the enum
were more complicated then you'd need to do something more complicated. C doesn't ordinarily offer introspection on anything, including enumerations, so if it were more like:
typedef NS_ENUM(NSInteger, Enum) { A, B = 26, C, D = -9 };
Then you're probably approaching having to create a literal array of A, B, C and D, and enumerate through that.
Iterable Array of Enums
You will want to make sure you cover all combinations of attributes and make sure each card has one of each of the four types of attributes. I would suggest using nested loops:
for shape in cardShape.allValues {
for color in cardColor.allValues {
for number in cardNumber.allValues {
for shading in cardShading.allValues {
var card = Card()
card.addProperty(shape)
card.addProperty(color)
card.addProperty(number)
card.addProperty(shading)
cards.append(card)
}
}
}
}
I believe your Card
struct
is a bit too complex. If you change your representation, it will be easier to create the cards.
Have your card represent the different attributes as their own property:
struct Card {
let shape: CardShape
let color: CardColor
let number: CardNumber
let shading: CardShading
}
Then use nested loops to create your cards:
for shape in CardShape.allValues {
for color in CardColor.allValues {
for number in CardNumber.allValues {
for shading in CardShading.allValues {
cards.append(Card(shape: shape, color: color, number: number, shading: shading))
}
}
}
}
Notes:
- Your enums should start with uppercase characters, and your enum values should start with lowercase characters.
- Using separate properties for each attribute will make it much easier to check for matching attributes between cards.
- You get an initializer by default that initializes all properties. By initializing them with nested loops, you will be able to create all possible cards.
- Change your
allValues
properties to return arrays of the specific attribute type (for example[CardShape]
).
Alternate Answer:
Instead of using nested arrays, you could use MartinR's combinations
function to create the list of combinations of the properties. Adding an init
to Card
that takes [Property]
, you can create the cards in two lines of code:
struct Card {
var shape = CardShape.none
var color = CardColor.none
var number = CardNumber.none
var shading = CardShading.none
init(properties: [Property]) {
for property in properties {
switch property {
case let shape as CardShape:
self.shape = shape
case let color as CardColor:
self.color = color
case let number as CardNumber:
self.number = number
case let shading as CardShading:
self.shading = shading
default:
break
}
}
}
}
// https://stackoverflow.com/a/45136672/1630618
func combinations<T>(options: [[T]]) -> AnySequence<[T]> {
guard let lastOption = options.last else {
return AnySequence(CollectionOfOne([]))
}
let headCombinations = combinations(options: Array(options.dropLast()))
return AnySequence(headCombinations.lazy.flatMap { head in
lastOption.lazy.map { head + [$0] }
})
}
struct SetGame {
let cards: [Card]
init(){
let properties: [Property.Type] = [CardShape.self, CardColor.self, CardNumber.self, CardShading.self]
cards = combinations(options: properties.map { $0.allValues }).map(Card.init)
}
}
How this works:
properties.map { $0.allValues }
callsallValues
on each item of theproperties
array creating an[[Property]]
with[[.square, .triangle, .circle], [.red, .purple, .green], [.one, .two, .three], [.solid, .striped, .outlined]]
- This is passed to
combinations
which creates a sequence with all 81 combinations of these properties:[[.square, .red, .one, .solid], ..., [.circle, .green, .three, .outlined]]
. map
is run on this sequence to callCard.init
with each combination which results in an[Card]
with 81 cards.
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.
Related Topics
Using Guard with a Non-Optional Value Assignment
Dyld: Library Not Loaded: @Rpath/Libswiftcore.Dylib Problem with New Xcode (10.2)
How to Set Uisegmentedcontrol Tint Color for Individual Segment
Swiftui Swizzling Disabled by Default, Phone Auth Not Working
Aws S3 Transfer Manager ${Cognito-Identity.Amazonaws.Com:Sub} Policy Variable Access Denied
Swift - How to Create a View with a Shape Cropped in It
Swift 4 - Notification Center Addobserver Issue
Sub-Classing Nstextstorage Causes Significant Memory Issues
Firebase Query Containing Value
Strange Error Nw_Protocol_Get_Quic_Image_Block_Invoke Dlopen Libquic Failed
Can't Pass Date to Nspredicate(Format: ...) Without "As Cvararg"
Views Compressed by Other Views in Swiftui VStack and List
Convert Firdatasnapshot to Custom Type
Cannot Use Mutating Member on Immutable Value of Type
How to Edit the Uiblureffect Intensity
App Crashes When Playing Audio on iOS13.1