SwiftUI: Get toggle state from items inside a List - list

I'm building an app where I generate a dynamic list inside a view, the items inside this list have a toggle button. If a button in the parent view is pressed and all the items have their toggle activated. Then a function is carried on.
How can I get the #state of all the list items in order to do the function when the button is pressed.
Here is some basic code for it:
struct OrderView: View {
var pOrder: OrderObject
var body: some View {
VStack(alignment: .leading){
Button(action: buttonAction) { Text("myBttn") }
List(pOrder.contents, id: \.name) { item in
Child(pOrder: item)
}
}
}
}
And here is the code for the child view
struct Child: View {
var pContents: Contents
#State var selected: Bool = false
var body: some View {
Toggle(isOn: $selected){ Text("Item") }
}
}

struct ObjectOrder: Identifiable {
var id = UUID()
var order = ""
var isToggled = false
init(order: String) {
self.order = order
}
}
struct ContentView: View {
#State var pOrder = [
ObjectOrder(order: "Order1"),
ObjectOrder(order: "Order2"),
ObjectOrder(order: "Order3"),
ObjectOrder(order: "Order4"),
ObjectOrder(order: "Order5")
]
var body: some View {
List(pOrder.indices) { index in
HStack {
Text("\(self.pOrder[index].order)")
Toggle("", isOn: self.$pOrder[index].isToggled)
}
}
}
}
Try using .indices would give you an index to your object in the array. Adding a property to track the toggle (isToggled in the example above) would allow you to store and keep track of the objects toggled status.

Related

SwiftUI Childview not not refreshing on #Binding update

I have a conditional view based on 2 binding vars in MyBootomSheet.
Idea is, show detail view if an item is selected in parent view, else show list of items.
ParentView has the logic to select/unselect item.
The code works as expected for the first time. But once an item is selected in the parent view the view never gets updated ever again, even after another item selected or item is unselected.
Any idea how this can be resolved ?
TIA!
struct ParentView: View {
#StateObject var dataSource = MapViewSource()
#State var selectedItem:SomeModel? = nil
var body: some View {
//
}.bottomSheet() {
MyBottomSheet(items:self.$dataSource.items, selectedItem:self.$selectedItem)
}
}
struct MyBottomSheet: View {
#Binding var items:[SomeModel]
#Binding var selectedItem:SomeModel?
var body: some View {
if self.selectedItem != nil {
ItemDetail(item: self.selectedItem!)
}
else {
List(self.items id: \.itemId) { item in
ItemRow(item: item)
}
}
}
}
State uses Binding for childviews but StateObject needs an ObservedObject:
class SomeObservableObject: ObservableObject {
// if this wasn't published, then the view wouldn't update when only changing the value
#Published var value: String
init(_ value: String) {
self.value = value
}
}
struct ParentView: View {
#State var state: String = "This is a State"
#StateObject var stateObject: SomeObservableObject = .init("This is an ObservableObject")
var body: some View {
ChildView(state: $state, stateObject: stateObject)
}
}
struct ChildView: View {
#Binding var state: String
#ObservedObject var stateObject: SomeObservableObject
var body: some View {
VStack {
Text("State: \(state)")
Text("StateObject: \(stateObject.value)")
}
}
}
``

SwiftUI state discarded when scrolling items in a list

I'm very new to Swift and SwiftUI so apologies for the very basic question. I must be misunderstanding something about the SwiftUI lifecycle and it's interaction with #State.
I've have a list, and when you click on the row, it increments a counter. If I click on some row items to increment the counter, scroll down, and scroll back up again - the state is reset to 0 again. Can anyone point out where I'm going wrong? Many thanks.
struct TestView : View {
#State private var listItems:[String] = (0..<50).map { String($0) }
var body: some View {
List(listItems, id: \.self) { listItem in
TestViewRow(item: listItem)
}
}
}
struct TestViewRow: View {
var item: String
#State private var count = 0
var body: some View {
HStack {
Button(item, action: {
self.count += 1
})
Text(String(self.count))
Spacer()
}
}
}
Items in a List are potentially lazily-loaded, depending on the os (macOS vs iOS), length of the list, number of items on the screen, etc.
If a list item is loaded and then its state is changed, that state is not reassigned to the item if that item has been since unloaded/reloaded into the List.
Instead of storing #State on each List row, you could move the state to the parent view, which wouldn't be unloaded:
struct ContentView : View {
#State private var listItems:[(item:String,count:Int)] = (0..<50).map { (item:String($0),count:0) }
var body: some View {
List(Array(listItems.enumerated()), id: \.0) { (index,item) in
TestViewRow(item: item.item, count: $listItems[index].count)
}
}
}
struct TestViewRow: View {
var item: String
#Binding var count : Int
var body: some View {
HStack {
Button(item, action: {
count += 1
})
Text(String(count))
Spacer()
}
}
}

Unable to repeat Picker Selection

Scenario:
I have a simple picker within a form.
I select a picker item (with chevron) from the form row.
I choose an item (row) from a list of items in the result panel.
The result panel slides away to reveal the original panel.
I am NOT able to repeat this procedure.
Here's my code:
class ChosenView: ObservableObject {
static let choices = ["Modal", "PopOver", "Circle", "CircleImage", "Scroll", "Segment", "Tab", "Multi-Line"]
#Published
var type = 0
}
struct ContentView: View {
#ObservedObject var chosenView = ChosenView()
#State private var isPresented = false
var body: some View {
VStack {
NavigationView {
Form {
Picker(selection: $chosenView.type, label: Text("The Panels")) {
ForEach(0..<ChosenView.choices.count) {
Text(ChosenView.choices[$0]).tag($0)
}
}
}.navigationBarTitle(Text("Available Views"))
.actionSheet(isPresented: $isPresented, content: {
ActionSheet(title: Text("Hello"))
})
}
Section {
Button(action: launchView) {
Text("Select: \(ChosenView.choices[chosenView.type])")
}
}
Spacer()
}
}
private func launchView() {
isPresented = true
}
}
What am I missing?
Why can't I repeat picker selection rather than having to reboot?

SwiftUI dynamically pop NavigationLink with a list of links

I want to pop a NavigationLink from within code. I followed this article and it works for one link (https://swiftui-lab.com/bug-navigationlink-isactive/). However, when using a list of links, one has to use a separate boolean for each NavigationLink.
So I thought the smart way to do this is with an EnvironmentObject that holds a dictionary with a boolean for each ChildView:
class Navigation: ObservableObject{
#Published var show:[UUID:Bool] = [:]
}
Let's say we want to have a variable number child views (of type MyObject).
What needs to be changed in order to make it work?
struct MyObject:Identifiable{
var id = UUID()
var name:String
}
struct ContentView: View {
#EnvironmentObject var navigation:Navigation
var objects = [MyObject(name: "view1"), MyObject(name: "view2"), MyObject(name: "view3")]
init() {
for object in objects{
self.navigation.show[object.id] = false
}
}
var body: some View {
NavigationView{
VStack{
ForEach(objects, id:\.self.id){ object in
NavigationLink(destination: Child(object:object), isActive: self.$navigation.show[object.id], label: {
Text(object.name)
})
}
}
}
}
}
struct Child: View {
#EnvironmentObject var navi:Navigation
var object:MyObject
var body: some View {
Button(action:{self.navi.show[self.object.id] = false}, label: {
Text("back")
})
}
}
The view that the NavigationLink navigates to has an environment variable set called presentationMode. This variable lets you programatically pop your child view back to the parent view.
So instead of having to keep track of all the display states, we can simplify your Child struct to something like this:
struct Child: View {
#Environment(\.presentationMode) private var presentation
var object:MyObject
var body: some View {
Button(action:{ self.presentation.wrappedValue.dismiss() }, label: {
Text("back")
})
}
}

SwiftUI – How can I change a #State property with a list item tap?

Within my List I have some static items. I want to dynamically hide/show an item when the user taps another list item, i.e. I want to change a #State property when the user taps a specific list item.
How do I do that?
struct EditTransactionView : View {
#State var date = Date()
#State private var showingDateSelector = false // How do I change this with a tap on the date list item?
var body: some View {
NavigationView {
List {
DateView(date: $date)
if showingDateSelector {
DatePicker(
$date,
maximumDate: Date(),
displayedComponents: .date )
}
}
}
}
}
Something like this :
struct EditTransactionView : View {
#State var date = Date()
#State private var showingDateSelector = false // How do I change this with a tap on the date list item?
var body: some View {
NavigationView {
List {
Button(action: { self.showingDateSelector.toggle() }) {
DateView(date: $date)
}
if showingDateSelector {
DatePicker(
$date,
maximumDate: Date(),
displayedComponents: .date )
}
}
}
}
}