How to Loop Over Struct Properties in Swift

How to loop over struct properties in Swift?

Although old question, with Swift evolving this question has new answer. I think that you approach is way better for the described situation, however original question was how to iterate over struct properties, so here is my answer(works both for classes and structs)

You can use Mirror Structure Reference. The point is that after calling reflect to some object you get it's "mirror" which is pretty sparingly however still useful reflection.

So we could easily declare following protocol, where key is the name of the property and value is the actual value:

protocol PropertyLoopable
{
func allProperties() throws -> [String: Any]
}

Of course we should make use of new protocol extensions to provide default implementation for this protocol:

extension PropertyLoopable
{
func allProperties() throws -> [String: Any] {

var result: [String: Any] = [:]

let mirror = Mirror(reflecting: self)

guard let style = mirror.displayStyle where style == .Struct || style == .Class else {
//throw some error
throw NSError(domain: "hris.to", code: 777, userInfo: nil)
}

for (labelMaybe, valueMaybe) in mirror.children {
guard let label = labelMaybe else {
continue
}

result[label] = valueMaybe
}

return result
}
}

So now we can loop over the properties of any class or struct with this method. We just have to mark the class as PropertyLoopable.

In order to keep things static(as in the example) I will add also a singleton:

struct ReuseID: PropertyLoopable {
static let instance: ReuseID = ReuseID()
}

Whether singleton used or not, we could finally loop over the properties like follows:

do {
print(try ReuseID.instance.allProperties())
} catch _ {

}

And that's it with looping struct properties. Enjoy swift ;)

Loop through Swift struct to get keys and values

First of all let's use CamelCase for the struct name

struct MyStruct {
var a = "11215"
var b = "21212"
var c = "39932"
}

Next we need to create a value of type MyStruct

let elm = MyStruct()

Now we can build a Mirror value based on the elm value.

let mirror = Mirror(reflecting: elm)

The Mirror value does allow us to access all the properties of elm, here's how

for child in mirror.children  {
print("key: \(child.label), value: \(child.value)")
}

Result:

key: Optional("a"), value: 11215

key: Optional("b"), value: 21212

key: Optional("c"), value: 39932

Iterate over properties of a struct and check if they are equal to the properties of other value of the same type

In recent versions of swift, if all the properties of a struct are equatable, you can make the struct Equatable without having to implement the == function.

In your case, all of your properties are Equatable, so you can just declare the struct as Equatable and you're done.

If the vast majority of your properties are Equatable it might be easier to add "equatability" to those properties.

Loop through Struct to check a value

You can enumerate each key value pair in a dictionary like so:

for (i, alrt) in alertStack

Where "i" would be your int value

But: it may be better to try to find a more Swifty way of expressing your problem (what are you trying to do?) rather than trying to translate from PHP. For example, perhaps like:

let alerts: [Alert] = [
(60, 55, "green", "60 Seconds"),
(30, 25, "yellow", "30 Seconds!")
]
.map(Alert.init(begin:end:color:message:))

let color: String
let alertText: String

if let foundAlert = alerts.last(where: { $0.begin < secondsLeft }) {
color = foundAlert.color
alertText = foundAlert.message
}
else {
color = "default"
alertText = ""
}

(Maybe there is a reason but I don't know why you would want to have them in a dictionary keyed by their begin numbers)

If it helps I would imagine your problem may be expressed something like this:

struct Alert: Codable {
let color: String
let message: String

static let defaultAlert = Alert(color: "default", message: "")

static let thresholds: [ClosedRange<Double>: Alert] = [
55...60: Alert(color: "green", message: "60 Seconds"),
25...30: Alert(color: "yellow", message: "30 Seconds!")
]
}

func currentAlert(value: Double) -> Alert {
// Return the alert where the range of seconds contains
// the provided remaining seconds value
guard let found = Alert.thresholds.first(where: {
$0.key.contains(value)
}) else {
return .defaultAlert
}

return found.value
}

print(currentAlert(value: 57)) // 60 Seconds
print(currentAlert(value: 42)) // default
print(currentAlert(value: 26)) // 30 Seconds!

How to loop through keys in struct for SwiftUI?

Here is a demo of possible approach (for this simple case) - you can reflect properties and map them to shown values (for custom/complex types it will require more efforts by idea should be clear).

var body: some View {
let columnValues = Mirror(reflecting: pdt)
.children.map { "\($0.value)" }
return HStack {
ForEach(columnValues, id: \.self) { Text($0) }
}
}

Looping over the properties of Views (struct) in an Array is returning wrong values (init values)

Views are structs in SwiftUI. So passing them into a function will create a copy of those Views. And you are itterating over the original Views that have not been modified.

I think a ViewModel/Model would be best here.

struct Model: Identifiable{
var selected: Bool
let text: String
let id = UUID()
}

class Viewmodel: ObservableObject{
@Published var models: [Model] = [] //Create the datamodel here

func select(_ selectedModel: Model){
var selectedModel = selectedModel
selectedModel.selected.toggle() //Create a new copy and toggle selection

// get index of selected item
guard let index = models.firstIndex(where: {$0.id == selectedModel.id}) else{
return
}

// assign item. This will toggle changes in the view
models[index] = selectedModel
}

func post(){
//Create combined string
let combinedString = models.filter{$0.selected}.reduce("") { partialResult, model in
partialResult + model.text
}
//call post
postTextFunc(combinedString)
}

func postTextFunc(_ string: String){

}
}

struct InnerView: View{
@EnvironmentObject private var viewmodel: Viewmodel
var selected: Model // provide the individual data here
var body: some View{
Text(selected.text)
.onTapGesture {
viewmodel.select(selected)
}
}
}

struct TestView: View{

@StateObject private var viewmodel = Viewmodel()

var body: some View {
ScrollView {
ForEach(viewmodel.models, id: \.id) { model in
InnerView(selected: model)
.environmentObject(viewmodel) // pass viewmodel in environment
}
}
Button {
viewmodel.post()
} label: {
Text("Submit")
}
}
}


Related Topics



Leave a reply



Submit