@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
Using Environmentobject in Watchos
Swift: Dictionary Access via Index
Swift3:How to Handle Precedencegroup Now Operator Should Be Declare with a Body
Confusion Regarding Overriding Class Properties in Swift
Load a Pcm into a Avaudiopcmbuffer
Using Just with Flatmap Produce Failure Mismatch. Combine
How Does Optional Covariance Work in Swift
Function Taking a Variable Number of Arguments
One-Way Platform Collisions in Sprite Kit
Check for Launching from Uilocalnotification in Swift
How to Test That Statictexts Contains a String Using Xctest