SwiftUI presenting sheet with Binding variable doesn't work when first presented - macos

I'm trying to present a View in a sheet with a #Binding String variable that just shows/binds this variable in a TextField.
In my main ContentView I have an Array of Strings which I display with a ForEach looping over the indices of the Array, showing a Button each with the text of the looped-over-element.
The Buttons action is simple: set an #State "index"-variable to the pressed Buttons' Element-index and show the sheet.
Here is my ContentView:
struct ContentView: View {
#State var array = ["first", "second", "third"]
#State var showIndex = 0
#State var showSheet = false
var body: some View {
VStack {
ForEach (0 ..< array.count, id:\.self) { i in
Button("\(array[i])") {
showIndex = i
showSheet = true
}
}
// Text("\(showIndex)") // if I uncomment this line, it works!
}
.sheet(isPresented: $showSheet, content: {
SheetView(text: $array[showIndex])
})
.padding()
}
}
And here is the SheetView:
struct SheetView: View {
#Binding var text: String
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
TextField("text:", text: $text)
Button("dismiss") {
presentationMode.wrappedValue.dismiss()
}
}.padding()
}
}
The problem is, when I first open the app and press on the "second" Button, the sheet opens and displays "first" in the TextField. I can then dismiss the Sheet and press the "second" Button again with the same result.
If I then press the "third" or "first" Button everything works from then on. Pressing any Button results in the correct behaviour.
Preview
Interestingly, if I uncomment the line with the Text showing the showIndex-variable, it works from the first time on.
Is this a bug, or am I doing something wrong here?

You should use custom Binding, custom Struct for solving the issue, it is complex issue. See the Example:
struct ContentView: View {
#State private var array: [String] = ["first", "second", "third"]
#State private var customStruct: CustomStruct?
var body: some View {
VStack {
ForEach (array.indices, id:\.self) { index in
Button(action: { customStruct = CustomStruct(int: index) }, label: {
Text(array[index]).frame(width: 100)
})
}
}
.frame(width: 300, height: 300, alignment: .center)
.background(Color.gray.opacity(0.5))
.sheet(item: $customStruct, content: { item in SheetView(text: Binding.init(get: { () -> String in return array[item.int] },
set: { (newValue) in array[item.int] = newValue }) ) })
}
}
struct CustomStruct: Identifiable {
let id: UUID = UUID()
var int: Int
}
struct SheetView: View {
#Binding var text: String
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
TextField("text:", text: $text)
Button("dismiss") {
presentationMode.wrappedValue.dismiss()
}
}.padding()
}
}

I had this happen to me before. I believe it is a bug, in that until it is used in the UI, it doesn't seem to get set in the ForEach. I fixed it essentially in the same way you did, with a bit of subtlety. Use it in each Button as part of the Label but hide it like so:
Button(action: {
showIndex = i
showSheet = true
}, label: {
HStack {
Text("\(array[i])")
Text(showIndex.description)
.hidden()
}
})
This doesn't change your UI, but you use it so it gets properly updated. I can't seem to find where I had the issue in my app, and I have changed the UI to get away from this, but I can't remember how I did it. I will update this if I can find it. This is a bit of a kludge, but it works.

Passing a binding to the index fix the issue like this
struct ContentView: View {
#State var array = ["First", "Second", "Third"]
#State var showIndex: Int = 0
#State var showSheet = false
var body: some View {
VStack {
ForEach (0 ..< array.count, id:\.self) { i in
Button(action:{
showIndex = i
showSheet.toggle()
})
{
Text("\(array[i])")
}.sheet(isPresented: $showSheet){
SheetView(text: $array, index: $showIndex)
}
}
}
.padding()
}
}
struct SheetView: View {
#Binding var text: [String]
#Binding var index: Int
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
TextField("text:", text: $text[index])
Button("dismiss") {
presentationMode.wrappedValue.dismiss()
}
}.padding()
}
}
In SwiftUI2 when calling isPresented if you don't pass bindings you're going to have some weird issues.
This is a simple tweak if you want to keep it with the isPresented and make it work but i would advise you to use the item with a costum struct like the answer of swiftPunk

This is how I would do it. You'll lose your form edits if you don't use #State variables.
This Code is Untested
struct SheetView: View {
#Binding var text: String
#State var draft: String
#Environment(\.presentationMode) var presentationMode
init(text: Binding<String>) {
self._text = text
self._draft = State(initialValue: text.wrappedValue)
}
var body: some View {
VStack {
TextField("text:", text: $draft)
Button("dismiss") {
text = draft
presentationMode.wrappedValue.dismiss()
}
}.padding()
}
}

Related

Passing data with NavigationSplitView to the second column

I have a macOS app with two columns. The left column is a list that presents the filename and date of the unit (file) that I'm working on. The second column, to the right, should present the content of each file when selected.
I have an array that contains that information and I create a list for the left column that presents each item. I added a detail: with a TextEditor that allows the user to see the data and modify it if necessary. I have been trying to set the #State var text to the contents of currentunit.text but I don't know how to pass that the detail:. If I try to assign it (as in text = x) then I get an error saying that it doesn't conform to View.
I tried then to maybe load it by getting the index of the current selected unit, using the selectedUnitId, and using something like this to get the index:
func getIndex(uuid: UUID) -> Int? {
return data.units.firstIndex(where: {$0.id == uuid})
}
But I get nowhere with a collection of different errors.
Regardless, how do I pass data to the detail: part of the code? I have looked into many examples of NavigationSplitView and they are all very similar, just showing the basic usage and that's it.
Thanks!
Code:
struct Unit: Codable, Hashable, Identifiable {
let id: UUID
var text: String
var date = Date()
var dateText: String {
let df = DateFormatter()
df.dateFormat = "EEEE, MMM d yyyy, h:mm a"
return df.string(from: date)
}
var changed: Bool = false
}
final class UnitModel: ObservableObject {
#AppStorage("unit") public var units: [Unit] = []
init() {
self.units = self.units.sorted(by: {
$0.date.compare($1.date) == .orderedDescending
})
}
func sortList() {
self.units = self.units.sorted(by: {
$0.date.compare($1.date) == .orderedDescending
})
}
}
struct ContentView: View {
#EnvironmentObject private var data: UnitModel
#State var selectedUnitId: UUID?
#State var text: String = ""
var body: some View {
NavigationSplitView {
List(data.units, selection: $selectedUnitId) { currentunit in
VStack(alignment: .leading) {
Text(currentunit.filename)
Text(currentunit.dateText)
}
}
} detail: {
// here: how do I preload $text with the text from the unit?
VStack(alignment: .leading) {
TextEditor(text: $text)
}
}
}
}
I also tried:
struct ContentView: View {
#EnvironmentObject private var data: UnitModel
#State var selectedNoteId: UUID?
var body: some View {
NavigationSplitView {
List(data.units, selection: $selectedNoteId) { currentunit in
NavigationLink{
UnitView(unit: currentunit, text: currentunit.text)
} label: {
VStack(alignment: .leading) {
Text(currentunit.filename)
Text(currentunit.dateText)
}
}
}
} detail: {
Text("Select a unit.")
}
}
}
struct UnitView: View {
#EnvironmentObject private var data: UnitModel
var unit: Unit
#State var text: String
var body: some View {
VStack(alignment: .leading) {
TextEditor(text: $text)
}
}
}
But again, I don't know how to initialize the text variable with the text of the current unit. I only get the initial one selected, and even tho I can see a new unit selected, the text remains the same and doesn't update.
UPDATED if I change the code to use NavigationView then it works as it should, so what's going with the new way that Apple is make us use now? Namely NavigationSplitView and NavigationStack?
Here's the code that work as it should but it's deprecated according to Apple:
NavigationView {
List(data.units, selection: $selectedNoteId) { currentunit in
NavigationLink(
destination: UnitView(unit: currentunit, text: currentunit.text),
label: {
VStack(alignment: .leading) {
Text(currentunit.filename)
Text(currentunit.dateText)
}
}
)
}
Apple's Defining the source of truth using a custom binding
tutorial covers this. Your code would look something like this:
} detail: {
DetailView(unitID: selectedUnitID) // not sure why they used binding
}
struct DetailView: View {
let unitID: Unit.ID
#EnvironmentObject private var store: UnitModel
private var unitBinding: Binding<Unit> {
Binding {
if let id = unitID {
return store.unit(with: id) ?? Unit.emptyUnit()
} else {
return Unit.emptyUnit()
}
} set: { updatedUnit in
store.update(updatedUnit)
}
}
var body: some View {
if store.contains(unitID) {
VStack(alignment: .leading) {
TextEditor(text: unitBinding.text)
}
}
else {
Text("Select Unit")
}
}
}
Note there currently (as of Xcode 14.2) is a known bug with the text cursor when using a TextField in the detail pane. Check by entering text, move cursor to middle and try to enter a character. The bug is the cursor jumps to the end.

Using .searchable on a macOS causes the focus to always jump back to the search field

I'm trying to move away from having a TextField in the toolbar by using the new .searchable. But there seems to be a problem I can't solve. When you type the text you want to search, I can filter the list with that text, but when I place the mouse cursor on the first item and try to move down the list with the arrow key, with each arrow key press, the focus goes back to the search field, making it impossible to navigate up and down the list with the keyboard.
Maybe I'm not implementing it right, or maybe it doesn't work yet with macOS, either way, this is the code I'm using:
struct AllNotes: View {
#EnvironmentObject private var data: DataModel
#State var selectedNoteId: UUID?
#State var searchText: String = ""
var body: some View {
NavigationView {
List(data.notes.filter { searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText) }) { note in
NavigationLink(
destination: NoteView(note: note, text: note.text),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(getFirstLine(noteText: note.text)).font(.body).fontWeight(.bold)
}
}
}
.searchable(
text: $searchText,
placement: .toolbar,
prompt: "Search..."
)
.listStyle(InsetListStyle())
.toolbar {
// a few other buttons
}
}
}
}
The DataModel is simple a struct of NoteItem:
struct NoteItem: Codable, Hashable, Identifiable {
let id: UUID
var text: String
var changed: Bool = false
}
Am I missing anything? Am I implementing this right?
EDIT:
Based on suggestions from Apple and other sites, .searchable should be added under the navigation view. So I moved that there. The default behavior, as described by Apple, of adding it to the end of the toolbar is still happening, but that's ok. However the problem still persists, the focus jumps back to the search field each time you click on a list item.
struct AllNotes: View {
#EnvironmentObject private var data: DataModel
#State var selectedNoteId: UUID?
#State var searchText: String = ""
var body: some View {
NavigationView {
List(data.notes.filter { searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText) }) { note in
NavigationLink(
destination: NoteView(note: note, text: note.text),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(getFirstLine(noteText: note.text)).font(.body).fontWeight(.bold)
}
}
}
.listStyle(InsetListStyle())
.toolbar {
// a few other buttons
}
}
.searchable(
text: $searchText,
placement: .toolbar,
prompt: "Search..."
)
}
}
I think the problem is because you are showing the list in the sidebar but have the search field in the toolbar. So you could try moving the search field to the sidebar which does fix the problem with navigating items with arrow keys but I wasn't able to tab back to the search field. And InsetListStyle didn't seem compatible with searching so I commented that. And by the way, you are missing the default detail view for your NavigationView so you need to add that. Also your View structure needed tweaked so you pass the filtered results into the child View E.g.
struct NoteView: View {
let note: NoteItem
//let text: String
var body: some View {
Text(note.text)
}
}
struct NotesView: View {
#State private var selectedNoteId: UUID?
let notes: [NoteItem]
var body: some View {
List(notes) { note in
NavigationLink(
destination: NoteView(note: note), //text: note.text),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(note.text).font(.body).fontWeight(.bold)
}
}
}
// .listStyle(InsetListStyle())
}
}
struct SearchView: View {
#EnvironmentObject private var data: DataModel
#State var searchText: String = ""
var body: some View {
NavigationView {
NotesView(notes: filteredNotes)
Text("Make a selection")
// .toolbar {
// // a few other buttons
// }
}
.searchable(
text: $searchText,
placement: .sidebar,
prompt: "Search..."
)
}
var filteredNotes: [NoteItem] {
data.notes.filter { searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText)
}
}
}
struct ContentView: View {
#StateObject var model = DataModel()
var body: some View {
SearchView()
.environmentObject(model)
}
}
class DataModel: ObservableObject {
#Published var notes: [NoteItem] = [NoteItem(text: "Test1"), NoteItem(text: "Test2")]
}
struct NoteItem: Codable, Hashable, Identifiable {
let id = UUID()
var text: String
var changed: Bool = false
}

why is a passed parameter displaying the previous content of an EnvironmentObject

this is a Macos app where the parsclass is setup in a previous view that contains the YardageRowView below. That previous view is responsible for changing the contents of the parsclass. This is working is other views that use a NavigationLink to display the views.
When the parsclass is changed, this view is refreshed, but the previous value is put in the text field on the holeValueTestView.
I cannot comprehend how the value is not being passed into the holeValueTestView correctly
This is a view shown as a .sheet, and if I dismiss it and display it again, everything is fine.
if you create a macOS project called YardageSample and replace the ContentView.swift and YardageSampleApp.swift with the two files below, you can see that the display in red changes and the black numbers do not change until you click Done and redisplay the .sheet
//
// YardageSampleApp.swift
// YardageSample
//
// Created by Brian Quick on 2021-04-12.
//
import SwiftUI
#main
struct YardageSampleApp: App {
#StateObject var parsclass = parsClass()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(parsclass)
}
}
}
//
// ContentView.swift
// YardageSample
//
// Created by Brian Quick on 2021-04-12.
//
import SwiftUI
struct ContentView: View {
#StateObject var parsclass = parsClass()
enum ActiveSheet : String , Identifiable {
case CourseMaintenance
var id: String {
return self.rawValue
}
}
#State var activeSheet : ActiveSheet? = nil
var body: some View {
Button(action: {
self.activeSheet = .CourseMaintenance
}) {
Text("Course Maintenance")
}
.sheet(item: $activeSheet) { sheet in
switch sheet {
case .CourseMaintenance:
CourseMaintenance()
}
}.frame(width: 200, height: 200, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
}
}
class parsClass: ObservableObject {
#Published var pars = [parsRec]()
init() {
self.pars = [parsRec]()
self.pars.append(parsRec())
}
func create(newpars: [parsRec]) {
pars.removeAll()
pars = newpars
}
}
class parsRec: Identifiable, Codable {
var id = UUID()
var Hole = 1
var Yardage = 1
}
struct CourseMaintenance: View {
#EnvironmentObject var parsclass: parsClass
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Button(action: {presentationMode.wrappedValue.dismiss()}, label: {
Text("Done")
})
Button(action: {switchScores(number: 1)}, label: {
Text("Button 1")
})
Button(action: {switchScores(number: 2)}, label: {
Text("Button 2")
})
Button(action: {switchScores(number: 3)}, label: {
Text("Button 3")
})
CourseDetail().environmentObject(parsclass)
}.frame(width: 400, height: 400, alignment: .center)
}
func switchScores(number: Int) {
var newparRecs = [parsRec]()
for i in 0..<17 {
let myrec = parsRec()
myrec.Hole = i
myrec.Yardage = number
newparRecs.append(myrec)
}
parsclass.create(newpars: newparRecs)
}
}
struct CourseDetail: View {
#EnvironmentObject var parsclass: parsClass
var body: some View {
HStack(spacing: 0) {
ForEach(parsclass.pars.indices, id: \.self) { indice in
// this displays the previous value
holeValueTestView(value: String(parsclass.pars[indice].Yardage))
// this displays the correct value after parsclass has changed
Text(String(parsclass.pars[indice].Yardage))
.foregroundColor(.red)
}
}
}
}
struct holeValueTestView: View {
#State var value: String
var body: some View {
//TextField(String(value), text: $value)
Text(value)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
There are a couple of issues going on:
You have multiple instances of parsClass. One is defined in YardageSampleApp and passed into the view hierarchy as a #EnvironmentObject. The second is defined in ContentView as a #StateObject. Make sure you're only using one.
On holeValueTestView, you defined value as a #State variable. That gets set initially when the view is created by its parent and then it maintains its own state. So, when the environmentObject changed, because it was in charge of its own state at this point, it didn't update the value. You can simply remove #State and see the behavior that you want.
struct ContentView: View {
#EnvironmentObject var parsclass : parsClass //<-- Here
enum ActiveSheet : String , Identifiable {
case CourseMaintenance
var id: String {
return self.rawValue
}
}
#State var activeSheet : ActiveSheet? = nil
var body: some View {
Button(action: {
self.activeSheet = .CourseMaintenance
}) {
Text("Course Maintenance")
}
.sheet(item: $activeSheet) { sheet in
switch sheet {
case .CourseMaintenance:
CourseMaintenance()
}
}.frame(width: 200, height: 200, alignment: .center)
}
}
struct holeValueTestView: View {
var value: String //<-- Here
var body: some View {
Text(value)
}
}
As a side note:
In Swift, normally type names are capitalized. If you want to write idiomatic Swift, you would change your parsClass to ParsClass for example.

SwiftUI List how to identify what item is selected on macOS

Here is what I have based on this answer. The code currently allows the user to select a cell but I cannot distinguish which cell is selected or execute any code in response to the selection. In summary, how can I execute code based on the selected cell's name and execute on click. The cell currently highlights in blue where clicked, but I want to identify it and act accordingly based on that selection. Note: I am not looking to select the cell in editing mode. Also, how can I programmatically select a cell without click?
struct OtherView: View {
#State var list: [String]
#State var selectKeeper = Set<String>()
var body: some View {
NavigationView {
List(list, id: \.self, selection: $selectKeeper) { item in
Text(item)
}
}
}
}
Here is a gif demoing the selection
I found a workaround, but the text itself has to be clicked- clicking the cell does nothing:
struct OtherView: View {
#State var list: [String]
#State var selectKeeper = Set<String>()
var body: some View {
NavigationView {
List(list, id: \.self, selection: $selectKeeper) { item in
Text(item)
.onTapGesture {
print(item)
}
}
}
}
}
List selection works in Edit mode, so here is some demo of selection usage
struct OtherView: View {
#State var list: [String] = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"]
#State var selectKeeper = Set<String>()
var body: some View {
NavigationView {
List(list, id: \.self, selection: $selectKeeper) { item in
if self.selectKeeper.contains(item) {
Text(item).bold()
} else {
Text(item)
}
}.navigationBarItems(trailing: HStack {
if self.selectKeeper.count != 0 {
Button("Send") {
print("Sending selected... \(self.selectKeeper)")
}
}
EditButton()
})
}
}
}
To spare you the labour pains:
import SwiftUI
struct ContentView: View {
#State private var selection: String?
let list: [String] = ["First", "Second", "Third"]
var body: some View {
NavigationView {
HStack {
List(selection: $selection) {
ForEach(list, id: \.self) { item in
VStack {
Text(item)
}
}
}
TextField("Option", text: Binding(self.$selection) ?? .constant(""))
}
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
}
}
}
This solution deals with the problem #Isaac addressed.
screenshot
my 2 cents for custom selection and actions
works in swift 5.1 / iOS14:
see:
https://gist.github.com/ingconti/49497419e5edd84a5f3be52397c76eb4
I left a lot of debug "print".. to better understand.
You can react to the selected item by using onChange on your selection variable.
You can set the selection manually by simply setting the variable. This will also trigger onChange.
Here's some example code that will run directly in Playgrounds:
import SwiftUI
import PlaygroundSupport
struct OtherView: View {
#State var list: [String] = ["a", "b", "c"]
#State var selection: String?
var body: some View {
NavigationView {
VStack {
List(list, id: \.self, selection: $selection) { item in
Text(item)
}
.onChange(of: selection) {s in
// React to changes here
print(s)
}
Button("Select b") {
// Example of setting selection programmatically
selection = "b"
}
}
}
}
}
let view = OtherView()
PlaygroundPage.current.setLiveView(view)

How to access value from an item in ForEach list

How to access values from particular item on the list made with ForEach?
As you can see I was trying something like this (and many other options):
Text(item[2].onOff ? "On" : "Off")
I wanted to check the value of toggle of 2nd list item (for example) and update text on the screen saying if it's on or off.
And I know that it's something to do with #Binding and I was searching examples of this and trying few things, but I cannot make it to work. Maybe it is a beginner question. I would appreciate if someone could help me.
My ContentView:
struct ContentView: View {
// #Binding var onOff : Bool
#State private var onOff = false
#State private var test = false
var body: some View {
NavigationView {
List {
HStack {
Text("Is 2nd item on or off? ")
Text(onOff ? "On" : "Off")
// Text(item[2].onOff ? "On" : "Off")
}
ForEach((1...15), id: \.self) {item in
ListItemView()
}
}
.navigationBarTitle(Text("List"))
}
}
}
And ListItemView:
import SwiftUI
struct ListItemView: View {
#State private var onOff : Bool = false
// #Binding var onOff : Bool
var body: some View {
HStack {
Text("99")
.font(.title)
Text("List item")
Spacer()
Toggle(isOn: self.$onOff) {
Text("Label")
}
.labelsHidden()
}
}
}
I don't know what exactly you would like to achieve, but I made you a working example:
struct ListItemView: View {
#ObservedObject var model: ListItemModel
var body: some View {
HStack {
Text("99")
.font(.title)
Text("List item")
Spacer()
Toggle(isOn: self.$model.switchedOnOff) {
Text("Label")
}
.labelsHidden()
}
}
}
class ListItemModel: ObservableObject {
#Published var switchedOnOff: Bool = false
}
struct ContentView: View {
#State private var onOff = false
#State private var test = false
#State private var list = [
(id: 0, model: ListItemModel()),
(id: 1, model: ListItemModel()),
(id: 2, model: ListItemModel()),
(id: 3, model: ListItemModel()),
(id: 4, model: ListItemModel())
]
var body: some View {
NavigationView {
List {
HStack {
Text("Is 2nd item on or off? ")
Text(onOff ? "On" : "Off")
// Text(item[2].onOff ? "On" : "Off")
}
ForEach(self.list, id: \.id) {item in
ListItemView(model: item.model)
}
}
.navigationBarTitle(Text("List"))
}.onReceive(self.list[1].model.$switchedOnOff, perform: { switchedOnOff_second_item in
self.onOff = switchedOnOff_second_item
})
}
}
The #Published basically creates a Publisher, which the UI can listen to per onReceive().
Play around with this and you will figure out what these things do!
Good luck :)
import SwiftUI
struct ContentView: View {
#State private var onOffList = Array(repeating: true, count: 15)
var body: some View {
NavigationView {
List {
HStack {
Text("Is 2nd item on or off? ")
Text(onOffList[1] ? "On" : "Off")
}
ForEach((onOffList.indices), id: \.self) {idx in
ListItemView(onOff: self.$onOffList[idx])
}
}
.navigationBarTitle(Text("List"))
}
}
}
struct ListItemView: View {
#Binding var onOff : Bool
var body: some View {
HStack {
Text("99")
.font(.title)
Text("List item")
Spacer()
Toggle(isOn: $onOff) {
Text("Label")
}
.labelsHidden()
}
}
}
I understand that you are directing me to use ObservableObject. And probably it's the best way to go with final product. But I am still thinking about #Binding as I just need to pass values better between 2 views only. Maybe I still don't understand binding, but I came to this solution.
struct ContentView: View {
// #Binding var onOff : Bool
#State private var onOff = false
// #State private var test = false
var body: some View {
NavigationView {
List {
HStack {
Text("Is 2nd item on or off? ")
Text(onOff ? "On" : "Off")
// Text(self.item[2].$onOff ? "On" : "Off")
// Text(item[2].onOff ? "On" : "Off")
}
ForEach((1...15), id: \.self) {item in
ListItemView(onOff: self.$onOff)
}
}
.navigationBarTitle(Text("List"))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
and ListItemView:
import SwiftUI
struct ListItemView: View {
// #State private var onOff : Bool = false
#Binding var onOff : Bool
var body: some View {
HStack {
Text("99")
.font(.title)
Text("List item")
Spacer()
Toggle(isOn: self.$onOff) {
Text("Label")
}
.labelsHidden()
}
}
}
What is happening now is text is being updated after I tap toggle. But I have 2 problems:
tapping 1 toggle changes all of them. I think it's because of this line:
ListItemView(onOff: self.$onOff)
I still cannot access value of just one row. In my understanding ForEach((1...15), id: .self) make each row have their own id, but I don't know how to access it later on.

Resources