Picker selection not updating - swiftui

I'm trying to select a default account number from a list of available accounts. The data is from an FMDB data selection. I've created a test view with two types of pickers in it. One that lists the accounts I've retrieved into an array of data records which is a swift struct. The other picker is one that comes from an on-line example to select colors. The colors "selection" bound value updates as expected, but the "selection" bound value that I set does not change when one of two accounts is presented and selected. Below is the code from my test view that compiles and runs. When I select either account value which is [12345678] or [12345679] which appear as two rows in the picker the selection binding value doesn't change. But for the colors selection value it updates. I'm pretty confused here...
The struct for the accounts record is:
// Account record for FMDB
struct AccountRecord: Hashable {
var account_id: Int!
var account_code: Int!
var account_name: String!
var running_balance: Double!
var hidden_balance: Double!
var actual_balance: Double!
}
import SwiftUI
struct PickerTestView: View {
#State private var selectedAccount = 0
#State private var selectedColor = 0
var acctRecords: [Accounts.AccountRecord] {
return Accounts.shared.selectAllAccounts()
}
var colors = ["Red", "Green", "Blue", "Tartan"]
var body: some View {
VStack{
Picker(selection: $selectedAccount, label: Text(""))
{
ForEach (self.acctRecords, id: \.self) { acct in
Text("\(acct.account_code!)")
}
}
Text("selectedAccount = \(selectedAccount)")
.font(.largeTitle)
Picker(selection: $selectedColor, label: Text("Please choose a color")) {
ForEach(0 ..< colors.count) {
Text(self.colors[$0])
}
}
Text("Selectedcolor = \(selectedColor)")
Text("You selected \(colors[selectedColor])")
}
}
}
struct PickerTestView_Previews: PreviewProvider {
static var previews: some View {
PickerTestView()
}
}

Two things are happening:
You need a .tag() on your Picker elements to tell the system which element belongs to what item:
Picker(selection: $selectedAccount, label: Text(""))
{
ForEach (self.acctRecords, id: \.self) { acct in
Text("\(acct.account_code!)").tag(acct.account_id)
}
}
SwiftUI needs the types of the selection parameter and the tag type to be the same. Because in your model, account_id is defined as Int! and not Int, your selectedAccount needs to be Int! as well:
#State private var selectedAccount : Int! = 0
The following works with some test data embedded in:
struct PickerTestView: View {
#State private var selectedAccount : Int! = 1
#State private var selectedColor = 0
var acctRecords: [AccountRecord] {
return [.init(account_id: 1, account_code: 1, account_name: "1", running_balance: 0, hidden_balance: 0, actual_balance: 0),
.init(account_id: 2, account_code: 2, account_name: "2", running_balance: 0, hidden_balance: 0, actual_balance: 0),
.init(account_id: 3, account_code: 3, account_name: "3", running_balance: 0, hidden_balance: 0, actual_balance: 0)
]
}
var colors = ["Red", "Green", "Blue", "Tartan"]
var body: some View {
VStack{
Picker(selection: $selectedAccount, label: Text(""))
{
ForEach (self.acctRecords, id: \.self) { acct in
Text("\(acct.account_code!)").tag(acct.account_id)
}
}
Text("selectedAccount = \(selectedAccount)")
.font(.largeTitle)
}
}
}

Try to add .onChange:
Picker(selection: $selectedAccount, label: Text("")) {
ForEach (self.acctRecords, id: \.self) { acct in
Text("\(acct.account_code!)").tag(acct.account_code) // <- add tag here
}
}
.onChange(of: selectedAccount) {
selectedAccount = $0
}

Related

in SwifUI how to define variables which depends of an input Entity in the init of a view and then populate different pickers

I have a CoreData entity called PokSession which contains multiple attributes (date, currency, period, nbheure).
I want to design a view which display those informations (and allow me to modify them) for a given entity.
So basically from a previous view, I call an other view like this:
#FetchRequest(entity: PokSession.entity(), sortDescriptors: [
NSSortDescriptor(keyPath: \PokSession.date, ascending: false)
]) var poksessions: FetchedResults<PokSession>
ForEach(poksessions, id: \.date) { session in
DetailSessionPokUIView (session: session)
}
.onDelete(perform: deleteSessions)
which leads to the following view:
struct DetailSessionPokUIView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var session: PokSession
#State private var date: Date
#State private var currency: String
#State private var periode: String
#State private var nbheure: Double
let liste_currency = ["CAD", "EUR", "USD", "GBP"]
let liste_periode = ["matinée", "après-midi", "soirée"]
init(session: PokSession) {
date = session.date!
currency = session.currency!
periode = session.periode!
nbheure = session.nbheure
}
var body: some View {
NavigationView {
Form {
Section {
DatePicker(selection: $date , displayedComponents: .date){
Text("Date")
}
HStack{
Picker("Devise", selection: $currency) {
ForEach(liste_currency, id: \.self) { currency in
Text(currency)
}
}
}
HStack{
Picker("Période de jeu", selection: $periode) {
ForEach(liste_periode, id: \.self) { periode in
Text(periode)
}
}
}
HStack{
Text("Temps de jeu (h)")
.multilineTextAlignment(.leading)
Slider(value: $nbheure, in: 0...24, step: 1)
Text("\(nbheure, specifier: "%.0f")")
Image(systemName: "clock")
}
}
} // Form
} // NavigationView
}
}
But I am having error message inside my init(), saying " Variable 'self.session' used before being initialized".
I dont really understand why as "session" is an input in my init().
How can I use attributes of the selected PokSection entity to populate my DatePicker and my other Pickers default value
It shouldn't be difficult I guess but I am struggling...
Basically, I just want to have my Pickers set with the value coming from the selected PokSession.
And I want to see it in a Picker because I want to be able to modify it.
Thanks for your help

SwiftUI: NavigationLinks dependent on Toggle states

I am trying to create a NavigationLink destination that changes if any one of several toggle switches are set to TRUE. I have worked out the if/then logic for the links using tags and a state variable. That is, if I manually set toggleCount = the links work correctly.
What I would like to do however, is set toggleCount = Number of True Switches. You can see my failed attempt to loop through the array and increment toggleCount. It fails because it does not conform to View.
Any advice on a clean way to implement this? I don't necessarily need to count the number of true switches, I just need to know if any one of them were set to true.
import SwiftUI
struct ToggleStruct : Identifiable {
var id : Int
var name : String
var position : Bool
}
struct ContentView: View {
#State var toggleArray = [ToggleStruct(id: 1, name: "Toggle 1", position: false),
ToggleStruct(id: 2, name: "Toggle 2", position: false),
ToggleStruct(id: 3, name: "Toggle 3", position: false)
]
#State private var selection: Int? = nil
var toggleCount = 0
var body: some View {
NavigationView {
VStack {
Text("Toggle Count: \(toggleCount)")
ForEach(0..<toggleArray.count) { i in
Toggle(isOn: $toggleArray[i].position, label: {
Text(toggleArray[i].name)
})
}
NavigationLink(
destination: Text("Destination A"),
tag: 1,
selection: $selection,
label: {EmptyView()})
NavigationLink(
destination: Text("Destination B"),
tag: 2,
selection: $selection,
label: {EmptyView()})
Button(action: {
// ForEach(0..<toggleArray.count) { x in
// if toggleArray[x].position {
// toggleCount += 1
// }
// }
if toggleCount > 0 {
selection = 1
} else {
selection = 2
}
}, label: {
Text("Continue")
})
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Would something like this work for you? You could change the logic in destinationView to be whatever you needed.
import SwiftUI
struct ContentView: View {
#State var toggleOneIsOn = false
#State var toggleTwoIsOn = false
#State var toggleThreeIsOn = false
var body: some View {
NavigationView {
Form {
Toggle("Toggle One", isOn: $toggleOneIsOn)
Toggle("Toggle Two", isOn: $toggleTwoIsOn)
Toggle("Toggle Three", isOn: $toggleThreeIsOn)
NavigationLink("Foo", destination: destinationView)
}
}
}
var destinationView: some View {
switch (toggleOneIsOn, toggleTwoIsOn, toggleThreeIsOn) {
case _ where toggleOneIsOn || toggleTwoIsOn || toggleThreeIsOn:
return Text("At least one toggle is on")
default:
return Text("All toggles are off")
}
}
}

Picker appears disabled inside a Form... bug?

Consider this code:
struct ContentView: View {
var colors = ["Red", "Green", "Blue", "Tartan"]
#State private var selectedColor = "Red"
var body: some View {
Form {
Section (header:Text("color")) {
Picker("Please choose a color", selection: $selectedColor) {
ForEach(colors, id: \.self) {
Text($0)
}
}
}
}
}
}
Run it. The result is the picker disabled. Why? How to I enable it?
The problem is the picker being inside a form. Section makes no difference.
You have to add Form in Navigation View
struct ContentView: View {
var colors = ["Red", "Green", "Blue", "Tartan"]
#State private var selectedColor = "Red"
var body: some View {
NavigationView {
Form {
Section (header:Text("color")) {
Picker("Please choose a color", selection: $selectedColor) {
ForEach(colors, id: \.self) {
Text($0)
}
}
}
}
.navigationBarTitle("Color Picker")
}
}
}

How to create SwiftUI List of NavigationLinks with dynamic view names

I am trying to create a simple list of Views for the user to visit, I cannot figure out how to replace view name with an array variable. In the example below destination: is hard coded as AVExample(), which is one of my views, but how do I use the names in the array?
struct test: View {
var views = ["AVExample", "ColorPickerExample", "DatePickerExample"]
var body: some View {
NavigationView {
List (views, id: \.self){ view in
NavigationLink(
destination: AVExample(),
label: {
Text("\(view)")
})
}
}
}
}
You can create a struct for the views and then use that array of structure. For Example:
struct ViewsList: Identifiable {
static func == (lhs: ViewsList, rhs: ViewsList) -> Bool {
return lhs.id == rhs.id
}
var id: Int
var name: String
var viewContent: AnyView
}
And then in your view class(test), create an array of ViewList structure:
var views = [ViewsList.init(id: 1, name: "Example", viewContent: AnyView(AVExample())), ViewsList.init(id: 2, name: "ColorPicker", viewContent: AnyView(ColorPicker()))]
Then you can loop over this array as below :
NavigationView {
List (views, id: \.id){ view in
NavigationLink(
destination: view.viewContent,
label: {
Text("\(view.name)")
})
}
Many thanks for the replies. I came up with the following solution which seems to work well.
private struct aView {
var id = UUID()
var view: AnyView
var label: String
}
private var views = [
aView(view: AnyView(AppStorageExample()), label: "App Storage"),
aView(view: AnyView(AppStoreRecommend()), label: "App Store Recommended"),
aView(view: AnyView(AVExample()), label: "AV Player"),
aView(view: AnyView(ColorPickerExample()), label: "Color Picker"),
]
var body: some View {
List (views, id: \.id) { view in
NavigationLink(
destination: view.view,
label: {
Text("\(view.label)")
})
}
}

presentationMode.wrappedValue.dismiss() not working properly

I'm using the sheet method to display a simple form and I pass into it a couple of varsiables. The problem is that if I click the button which performs the .dismiss() method after changing the variables passed in it doesn't work. Instead if I directly click the button it works normally.
Here's the code:
struct EditProductForm: View {
var listIndex : Int
var product : Product
#State var quantity: Int
#State var productName : String
#EnvironmentObject var data : Data
#Environment(\.presentationMode) var presentationModeEdit
func editProduct(){
self.data.editProduct(listIndex: self.listIndex, product: self.product, productName: self.productName, quantity: self.quantity)
}
var body: some View {
VStack{
Spacer()
VStack(spacing: 64){
Text("Edit Product")
TextField("Edit the name", text: $productName)
Picker(selection: $quantity, label: Text("Quantity")){
Text("OK").tag(Constants.Status.OK)
Text("Almost finished").tag(Constants.Status.ALMOST_NONE)
Text("Finished").tag(Constants.Status.NONE)
}.pickerStyle(SegmentedPickerStyle())
Button(action: {
self.editProduct()
self.presentationModeEdit.wrappedValue.dismiss()
}){
Text("Save")
}
}
Spacer()
}.padding(.horizontal)
}
}
I also checked if the isPresented variable changes value and it's actually toggled when i click the button but the sheet stays there.
Here's the code where I use the form:
ForEach(self.list.content, id: \.self) { item in
Button(action: {
self.show_modal_edit[self.list.content.firstIndex(of: item)!] = true
}){
ProductCell(item: item)
}.sheet(isPresented: self.$show_modal_edit[self.list.content.firstIndex(of: item)!]){
EditProductForm(
listIndex: self.listIndex,
product: item,
quantity: item.quantity,
productName: item.productName
).environmentObject(self.data)
}
}
show_modal_edit is a list of Bool, I checked the values and apparently the correct one is passed to the isPresented field of .sheet().
I've setup the following test version of your code and all is working well for me on ios 13.4 and macos catalyst after renaming Data to Datax.
This points to the function in editProduct()
self.data.editProduct(listIndex: self.listIndex, product: self.product, productName: self.productName, quantity: self.quantity)
as the possible source of your problem. Specifically, using Data as the name for your type. It seems to clash with the system struct Data type. Try renaming your ObservableObject class to something else (Datax in my test).
import SwiftUI
class Datax: ObservableObject {
#Published var xxx = "xxx"
func editProduct(listIndex: Int, product: String, productName: String, quantity: Int) {
print("---> editProduct")
}
}
struct ContentView: View {
var data = Datax()
#State var showEditProductForm = false
var body: some View {
VStack {
Text("main view")
Button("EditProductForm") {
self.showEditProductForm.toggle()
}
}.sheet(isPresented: $showEditProductForm) {
EditProductForm(listIndex: 2, product: "product", quantity: 1, productName: "productName")
.environmentObject(self.data)
}
}
}
struct EditProductForm: View {
#EnvironmentObject var data: Datax
#Environment(\.presentationMode) var presentationModeEdit: Binding<PresentationMode>
var listIndex: Int
var product: String
#State var quantity: Int
#State var productName: String
func editProduct() {
self.data.editProduct(listIndex: self.listIndex, product: self.product, productName: self.productName, quantity: self.quantity)
}
var body: some View {
VStack{
Spacer()
VStack(spacing: 64){
Text("Edit Product")
TextField("Edit the name", text: $productName)
Picker(selection: $quantity, label: Text("Quantity")){
Text("OK").tag(0)
Text("Almost finished").tag(1)
Text("Finished").tag(2)
}.pickerStyle(SegmentedPickerStyle())
Button(action: {
self.editProduct()
self.presentationModeEdit.wrappedValue.dismiss()
}){
Text("Save")
}
}
Spacer()
}.padding(.horizontal)
}
}
Hope this helps track down your issue.