I'm trying to pass data to TextField using combine. by creating a data model and using observableObject, but when I use it in textField it shows me the error. Cannot convert value of type 'String' to expected argument type 'Binding< String >'. I'm unable to understand it.
dataModel
struct People: Identifiable {
var id = UUID()
var name: String
var amount: String
}
let peopleData = [
People(name: "A",amount: ""),
People(name: "B",amount: ""),
People(name: "C",amount: "")
]
ObservableObject
import Combine
class PeopleAllData: ObservableObject{
#Published var peopleStore: [People] = peopleData
}
TextField
#ObservedObject var store = PeopleAllData()
List{
ForEach(store.peopleStore){ item in
HStack {
TextField("person Name", text: item.name) //Error:- Cannot convert value of type 'String' to expected argument type 'Binding<String>'
Button(action: {}) {
Image(systemName: "minus.circle")
.foregroundColor(.red)
}
}
}
}
.frame(width: screen.width, height: screen.height)
You need Binding to array element via ObservedObject, like below
ForEach(store.peopleStore.indices, id: \.self){ i in
HStack {
TextField("person Name", text: $store.peopleStore[i].name)
Related
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
I'm using MVVM with Swift UI. I have the following Struct, ViewModel and View
struct Thing: Identifiable, Codable, Hashable {
var id: String = UUID().uuidString
var price: Double?
}
class MyViewModel: ObservableObject {
//****
#Published var things : [Thing] = [Thing(id: "abc1234")]{
didSet{
print(things)
}
}
}
struct MyView: View {
#StateObject var myViewModel: MyViewModel = MyViewModel()
private let numberFormatter: NumberFormatter
init() {
numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currency
numberFormatter.maximumFractionDigits = 2
}
var body: some View {
VStack{
List{
ForEach(Array($myViewModel.things.enumerated()), id: \.offset) { index, $thing in
HStack{
//AnotherView(thing: $thing)
TextField("$0.00", value: $myViewModel.things[index].price, formatter: numberFormatter)
.keyboardType(.numberPad)
}//HStack
}//ForEach
}//List
}//VStack
}//View
}
With the above code, anytime the textfield is changed the didSet print statement will show that price = nil
However if I change the line under the comment with the ***** to the following, initializing price to 0 the changes in the textfield are correctly written back to the [Thing] array and it prints that its Optional(x.xx)
#Published var things : [Thing] = [Thing(id: "abc1234", price: 0)]{
What I also just figured out is that if you use the above line with price initialized to 0, if you backspace the default $0.00 in the TextField, it sets the value back to nil, and then it never changes again.
Price should not be optional just default it to 0. There is also a mistake in the ForEach View (it is not a for loop it needs to be given an identifiable array), fix as follows:
ForEach($store.things){ $thing in
HStack{
//AnotherView(thing: $thing)
TextField("$0.00", value: $thing.price, formatter: NumberFormatter.myCurrencyFormatter)
.keyboardType(.numberPad)
}//HStack
Note the formatter needs to be a singleton, global or static because we shouldn't init objects in View init or body.
I try to assign a text field value to a EnvironmentObject, but getting this error.
Cannot convert value of type 'String' to expected argument type 'Binding"
import SwiftUI
class FilterSelections: ObservableObject {
#Published var filters: [String: Any] = [
"testFilter": "na"]
#Published var fromDate: Date = Date()
#Published var testNumber: String = ""
}
struct MainView: View {
var body: some View {
ZStack {
ScrollView {
VStack{
NumberSearchSubView()
Divider()
EmptyView()
}
}
}
}
}
struct NumberSearchSubView: View {
#State var searchByNumber: String = ""
#EnvironmentObject var filters: FilterSelections
var body: some View {
VStack(alignment:.leading, spacing:10) {
TextField("Enter your number", text: filters.testNumber).padding(10)
}
}
}
In order to use a #Published property as a Binding, you have to use the $ symbol as a prefix:
TextField("Enter your number", text: $filters.testNumber)
This sends the projected value (ie the Binding) instead of just the String value.
I am trying to save the result of a picker to user defaults. The user default save operation occurs in the class UserData via method saveBase.
I have tried a similar technique successfully with a button but my call after the picker gives the famous error:
Type '()' cannot conform to view.
struct aboutView: View {
#EnvironmentObject var userData: UserData
#State private var baseEntry: Int = 0
let base = ["Level 1", "Level 2","Level 3","Level 4"]
var body: some View {
Text("comment")
Text("comment")
Text("comment")
Section {
Picker(selection: $baseEntry, label: Text("Select Base >")) {
ForEach(0 ..< self.base.count) {
Text(self.base[$0]).tag($0)
}
self.userData.saveBase(baseEntry: self.baseEntry)
}
}
.padding()
}
}
class UserData: ObservableObject {
#Published var baseCurr: Int
func saveBase(baseEntry: Int) -> () {
baseCurr = baseEntry
let defaults = UserDefaults.standard
defaults.set(self.baseCurr, forKey: "base")
}
}
In the body you can only use Views - you can't perform operations like:
self.userData.saveBase(baseEntry: self.baseEntry)
You may use onChange to save the value:
Picker(selection: $baseEntry, label: Text("Select Base >")) {
ForEach(0 ..< self.base.count) {
Text(self.base[$0]).tag($0)
}
.onChange(of: baseEntry) {
self.userData.saveBase(baseEntry: $0)
}
}
Note that you can also use #AppStorage to automate saving/reading from UserDefaults:
#AppStorage("base") var baseEntry = 0
and use in the Picker in the same way as a #State variable:
Picker(selection: $baseEntry, label: Text("Select Base >")) {
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.