Using Foreach with a an Array of Bindings (Swiftui)

@Binding and ForEach in SwiftUI

You can use something like the code below. Note that you will get a deprecated warning, but to address that, check this other answer: https://stackoverflow.com/a/57333200/7786555

import SwiftUI

struct ContentView: View {
@State private var boolArr = [false, false, true, true, false]

var body: some View {
List {
ForEach(boolArr.indices) { idx in
Toggle(isOn: self.$boolArr[idx]) {
Text("boolVar = \(self.boolArr[idx] ? "ON":"OFF")")
}
}
}
}
}

Using ForEach with a an array of Bindings (SwiftUI)

Trying a different approach. The FormField maintains it's own internal state and publishes (via completion) when its text is committed:

struct FormField : View {
@State private var output: String = ""
let viewModel: FormFieldViewModel
var didUpdateText: (String) -> ()

var body: some View {
VStack {
TextField($output, placeholder: Text(viewModel.placeholder), onCommit: {
self.didUpdateText(self.output)
})

Line(color: Color.lightGray)
}.padding()
}
}
ForEach(viewModel.viewModels) { vm in
FormField(viewModel: vm) { (output) in
vm.output = output
}
}

SwiftUI ForEach with array or sub-array

You don’t have to use $ sign in front of your arrays.

ForEach(isExpanded ? items[..<items.count] : items[..<4], id: \.self) { sub in

}

Binding in SwiftUI ForEach from Parent to Child

You missed the $ before subtask.

If you don't write $subtask, then subtask is now a Binding<Subtask>. This would mean you can still do subtask.wrappedValue to use subtask normally and subtask to use the binding, but that's not as neat. With the $, $subtask is a Binding<Subtask> and subtask is a Subtask.

Change:

ForEach($model.subtasks) { subtask in
ChildView(subtask: $subtask)
}

To:

ForEach($model.subtasks) { $subtask in
ChildView(subtask: $subtask)
}

Or alternatively (and more confusingly, I don't recommend):

ForEach($model.subtasks) { subtask in
ChildView(subtask: subtask)
}

SwiftUI - using ForEach with a Binding array that does not conform to identifiable / hashable

Option 1:

Give your IncidentResponse an ID and then tell it to not try to decode the value:

struct IncidentResponse: Codable, Identifiable {
var id = UUID()
let incident: IncidentDetails?

enum CodingKeys: String, CodingKey {
case incident
}
}

Option 2:

Make incident and id non-optional and then use this to get the id:

ForEach(existingIncidents, id: \.incident.id) { incident in

I'll also note that IncidentResponse seems to be a somewhat meaningless wrapper at this point. When you do your decoding and store the values to existingIncidents (which you haven't shown), you could probably store them just as [IncidentDetails] instead. In that case, you just have to make the id property non-optional on IncidentDetails and declare it as Identifiable. For example:

struct IncidentDetails: Codable, Identifiable {
let id: String
let reason: IncidentReasonResponse?
let message: String?
let startedAt: String?
let endedAt: String?
}

//....

let existingIncidentsWrappers : [IncidentResponse] = //...
let existingIncidents : [IncidentDetails] = existingIncidentsWrappers.compactMap { $0.incident }

Binding in a ForEach in SwiftUI

Assuming your elements is state of items array, it can be

List {
ForEach(elements.indices, id: \.self) { i in
CheckBoxView(checked: $elements[i].checked)
}
}

How to use array of ForEach in SwiftUI

As @jnpdx said, you can use the map(...) method of Array to accomplish your goal:

let places: [Place] = viewModel.posts.map { post in
Place(latitude: post.location.latitude, longitude: post.location.longitude)
}

Using a protocol array with ForEach and bindable syntax

This is a thorny problem with your data types.

If you can change your data types, you can make this easier to solve.

For example, maybe you can model your data like this instead, using an enum instead of a protocol to represent the variants:

struct Testable {
let id: UUID
var name: String
var variant: Variant

enum Variant {
case animal(Animal)
case human(Human)
}

struct Animal {
var owner: String
}

struct Human {
var age: Int
}
}

It will also help to add accessors for the two variants' associated data:

extension Testable {
var animal: Animal? {
get {
guard case .animal(let animal) = variant else { return nil }
return animal
}
set {
guard let newValue = newValue, case .animal(_) = variant else { return }
variant = .animal(newValue)
}
}

var human: Human? {
get {
guard case .human(let human) = variant else { return nil }
return human
}
set {
guard let newValue = newValue, case .human(_) = variant else { return }
variant = .human(newValue)
}
}
}

Then you can write your view like this:

class ContentViewModel: ObservableObject {
@Published var testables: [Testable] = []
}

struct ContentView: View {
@StateObject var vm: ContentViewModel = ContentViewModel()
var body: some View {
VStack {
List {
ForEach($vm.testables, id: \.id) { $testable in
VStack {
TextField("Name", text: $testable.name)

if let human = Binding($testable.human) {
Stepper("Age: \(human.wrappedValue.age)", value: human.age)
}

else if let animal = Binding($testable.animal) {
HStack {
Text("Owner: ")
TextField("Owner", text: animal.owner)
}
}
}
}
}

HStack {
Button("Add animal") {
vm.testables.append(Testable(
id: UUID(),
name: "Mick",
variant: .animal(.init(owner: "harry"))
))
}
Button("Add Human") {
vm.testables.append(Testable(
id: UUID(),
name: "Ash",
variant: .human(.init(age: 26))
))
}
}
}
}
}

SwiftUI: How to filter Binding value in ForEach?

I think the issue is not the filtering per se, but the .lowercased() that the ForEach is rejecting. Rather than try to force that, there is a simpler way. Use a computed variable that does the filtering, and then roll your own Binding to send to the view like this:

struct ContentView: View {
@State var testArray = [
Test(number: 1, text: "1"),
Test(number: 2, text: "2"),
Test(number: 3, text: "3"),
Test(number: 4, text: "4"),
Test(number: 5, text: "5"),
Test(number: 6, text: "6")
]

@State private var searchText = ""
@State private var placeholder = "Search..."

var filteredTest: [Test] {
// If you want to return no items when there is no matching filter use this:
testArray.filter { ($0.text.lowercased().contains(searchText.lowercased()))}

// If you want the whole array unless the filter matches use this:
let returnTestArray = testArray.filter { ($0.text.lowercased().contains(searchText.lowercased()))}
guard !returnTestArray.isEmpty else { return testArray }
return returnTestArray
}

var body: some View {
NavigationView{
VStack{
SearchBar(text: $searchText, placeholder: $placeholder)
List {
ForEach(filteredTest) { test in
TestView10(test: Binding<Test> (
get: { test },
set: { newValue in
if let index = testArray.firstIndex(where: { $0.id == newValue.id } ) {
testArray[index] = newValue
}
}
))
}
}
}
}
}
}

I also renamed your array of data testArray to better reflect what it was, and in the ForEach, data now becomes test.



Related Topics



Leave a reply



Submit