I need to create Textfield every time i click on button, but when i use foreach to create that, when i write smth. in new textfield it is written also in others. I want that every new textfield will be different that i could write different thing in each of them.
This is what i am using.
#State var arr: [String] = []
#StateObject var homePageVM: HomePageViewModel = HomePageViewModel()
ForEach(arr, id: \.self) { item in
TextField("text", text: item)
}
and in button click
Button {
arr.append($homepageVM.textfieldText)
} label: {
Text("Button")
}
How can i solve this?
struct TextItem: Identifiable {
let id = UUID()
var text: String = ""
}
class Model: ObservableObject {
#Published var textItems: [TextItem] = []
func addTextItem() {
textItems.append(TextItem())
}
// funcs for loading and save model data
}
#StateObject var model = Model()
ForEach($model.textItems) { $item in
TextField("text", text: $item.text)
}
Button("Add") {
model.addTextItem()
}
Related
I am trying to add rows to a view as the user presses the add button. There are two buttons. One which adds a card and one which adds an expense inside the card. Im confident I have the code working to add cards but when I try to add an Expense inside a card it adds expenses to every card that is shown. How can I make it so that when the user presses the add expense button only the expense rows are added to the one card.
I have two structs one for Card and one for Expense, that I am using to store data.
struct Card: Identifiable {
var id = UUID()
var title: String
var expenses: [Expense]
}
struct Expense: Identifiable {
var id = UUID()
var expenseType: String
var amount: Double = 0.0
}
ContentView()
struct ContentView: View {
#State private var cards = [Card]()
#State private var expense = [Expense]()
var title = ""
var expenseType = ""
var amount: Double = 0.0
var body: some View {
NavigationStack {
Form {
List {
Button("Add card") {
addCard()
}
ForEach($cards) { a in
Section {
TextField("Title", text: a.title)
Button("Add expense") {
addExpenses()
}
ForEach($expense) { b in
TextField("my expense", text: b.expensetype)
TextField("amount", value: b.amount, format: .number)
}
}
}
}
}
}
}
func addCard() {
cards.append(Card(title: title, expenses: expense))
}
func addExpenses() {
expense.append(Expense(expenseType: "", amount: 0.0))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Any help would be really appreciated.....
It doesn't seem like you need the following line, because each card has an expense array, you should remove it.
#State private var expense = [Expense]()
Then move the addExpenses func inside struct Card
struct Card: Identifiable {
var id = UUID()
var title: String
var expenses: [Expense]
mutating func addExpenses() {
expenses.append(Expense(expenseType: "", amount: 0.0))
}
}
Then call
a.wrappedValue.addExpenses()
In the Button
Button("Add expense") {
a.wrappedValue.addExpenses()
}
I have 2 views where the
first view passes list of items and selected item in that to second view and
second view returns the updated selected item if user changes.
I am getting error 'Type of expression is ambiguous without more context' when i am sending the model property 'idx'.
//I cant make any changes to this model so cant confirm it with ObservableObject or put a bool property like 'isSelected'
class Model {
var idx: String?
....
}
class FirstViewModel: ObservableObject {
var list: [Model]
#Published var selectedModel: Model?
func getSecondViewModel() -> SecondViewModel {
let vm2 = SecondViewModel( //error >> Type of expression is ambiguous without more context
list: list,
selected: selectedModel?.idx // >> issue might be here but showing at above line
)
return vm2
}
}
struct FirstView: View {
#ObservableObject firstViewModel: FirstViewModel
var body: some View {
..
.sheet(isPresented: $showView2) {
NavigationView {
SecondView(viewModel: firstViewModel.getSecondViewModel())
}
}
..
}
}
class SecondViewModel: ObservableObject {
var list: [Model]
#Published var selected: String?
init(list: [Model], selected: Published<String?>) {
self.list = list
_selected = selected
}
func setSelected(idx: String) {
self.selected = idx
}
}
struct SecondView: View {
#ObservableObject secondViewModel: SecondViewModel
#Environment(\.presentationMode) var presentationMode
var body: some View {
...
.onTapGesture {
secondViewModel.setSelected(idx: selectedIndex)
presentationMode.wrappedValue.dismiss()
}
...
}
}
In case if I am sending 'Model' object directly to the SecondViewModel its working fine. I need to make changes the type and couple of other areas and instantiate the SecondViewModel as below
let vm2 = SecondViewModel(
list: list,
selected: _selectedModel
)
Since I need only idx I don't want to send entire model.
Also the reason for error might be but not sure the Model is #Published and the idx is not.
Any help is appreciated
Here is some code, in keeping with your original code that allows you to
use the secondViewModel as a nested model.
It passes firstViewModel to the SecondView, because
secondViewModel is contained in the firstViewModel. It also uses
firstViewModel.objectWillChange.send() to tell the model to update.
My comment is still valid, you need to create only one SecondViewModel that you use. Currently, your func getSecondViewModel() returns a new SecondViewModel every time you use it.
Re-structure your code so that you do not need to have nested ObservableObjects.
struct Model {
var idx = ""
}
struct ContentView: View {
#StateObject var firstMdl = FirstViewModel()
var body: some View {
VStack (spacing: 55){
FirstView(firstViewModel: firstMdl)
Text(firstMdl.secondViewModel.selected ?? "secondViewModel NO selected data")
}
}
}
class FirstViewModel: ObservableObject {
var list: [Model]
#Published var selectedModel: Model?
let secondViewModel: SecondViewModel // <-- here only one source of truth
// -- here
init() {
self.list = []
self.selectedModel = nil
self.secondViewModel = SecondViewModel(list: list, selected: nil)
}
// -- here
func getSecondViewModel() -> SecondViewModel {
secondViewModel.selected = selectedModel?.idx
return secondViewModel
}
}
class SecondViewModel: ObservableObject {
var list: [Model]
#Published var selected: String?
init(list: [Model], selected: String?) { // <-- here
self.list = list
self.selected = selected // <-- here
}
func setSelected(idx: String) {
selected = idx
}
}
struct FirstView: View {
#ObservedObject var firstViewModel: FirstViewModel // <-- here
#State var showView2 = false
var body: some View {
Button("click me", action: {showView2 = true}).padding(20).border(.green)
.sheet(isPresented: $showView2) {
SecondView(firstViewModel: firstViewModel)
}
}
}
struct SecondView: View {
#ObservedObject var firstViewModel: FirstViewModel // <-- here
#Environment(\.dismiss) var dismiss
#State var selectedIndex = "---> have some data now"
var body: some View {
Text("SecondView tap here to dismiss").padding(20).border(.red)
.onTapGesture {
firstViewModel.objectWillChange.send() // <-- here
firstViewModel.getSecondViewModel().setSelected(idx: selectedIndex) // <-- here
// alternatively
// firstViewModel.secondViewModel.selected = selectedIndex
dismiss()
}
}
}
I think I've a gap in understanding what exactly #State means, especially when it comes to displaying contents from a ForEach loop.
My scenario: I've created minimum reproducible example. Below is a parent view with a ForEach loop. Each child view has aNavigationLink.
// Parent code which passes a Course instance down to the child view - i.e. CourseView
struct ContentView: View {
#StateObject private var viewModel: ViewModel = .init()
var body: some View {
NavigationView {
VStack {
ForEach(viewModel.courses) { course in
NavigationLink(course.name + " by " + course.instructor) {
CourseView(course: course, viewModel: viewModel)
}
}
}
}
}
}
class ViewModel: ObservableObject {
#Published var courses: [Course] = [
Course(name: "CS101", instructor: "John"),
Course(name: "NS404", instructor: "Daisy")
]
}
struct Course: Identifiable {
var id: String = UUID().uuidString
var name: String
var instructor: String
}
Actual Dilemma: I've tried two variations for the CourseView, one with let constant and another with a #State var for the course field. Additional comments in the code below.
The one with the let constant successfully updates the child view when the navigation link is open. However, the one with #State var doesn't update the view.
struct CourseView: View {
// Case 1: Using let constant (works as expected)
let course: Course
// Case 2: Using #State var (doesn't update the UI)
// #State var course: Course
#ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Text("\(course.name) by \(course.instructor)")
Button("Edit Instructor", action: editInstructor)
}
}
// Case 1: It works and UI gets updated
// Case 2: Doesn't work as is.
// I've to directly update the #State var instead of updating the clone -
// which sometimes doesn't update the var in my actual project
// (that I'm trying to reproduce). It definitely works here though.
private func editInstructor() {
let instructor = course.instructor == "Bob" ? "John" : "Bob"
var course = course
course.instructor = instructor
save(course)
}
// Simulating a database save, akin to something like GRDB
// Here, I'm just updating the array to see if ForEach picks up the changes
private func save(_ courseToSave: Course) {
guard let index = viewModel.courses.firstIndex(where: { $0.id == course.id }) else {
return
}
viewModel.courses[index] = courseToSave
}
}
What I'm looking for is the best practice for a scenario where looping through an array of models is required and the model is updated in DB from within the child view.
Here is a right way for you, do not forget that we do not need put logic in View! the view should be dummy as possible!
struct ContentView: View {
#StateObject private var viewModel: ViewModel = ViewModel.shared
var body: some View {
NavigationView {
VStack {
ForEach(viewModel.courses) { course in
NavigationLink(course.name + " by " + course.instructor, destination: CourseView(course: course, viewModel: viewModel))
}
}
}
}
}
struct CourseView: View {
let course: Course
#ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Text("\(course.name) by \(course.instructor)")
Button("Update Instructor", action: { viewModel.update(course) })
}
}
}
class ViewModel: ObservableObject {
static let shared: ViewModel = ViewModel()
#Published var courses: [Course] = [
Course(name: "CS101", instructor: "John"),
Course(name: "NS404", instructor: "Daisy")
]
func update(_ course: Course) {
guard let index = courses.firstIndex(where: { $0.id == course.id }) else {
return
}
courses[index] = Course(name: course.name, instructor: (course.instructor == "Bob") ? "John" : "Bob")
}
}
struct Course: Identifiable {
let id: String = UUID().uuidString
var name: String
var instructor: String
}
Basically as the title states. I have the picker called Ingredients and when I go into the list and click an element it should work as a button (or maybe not) and use the add function to append that element into the ingredients list which is a state variable which should then in turn update the list at the bottom and display its elements, but it doesnt. I have done other projects with a similar idea of an updating list but never with a picker. Any help appreciated. Also worth mentioning is that the TEST button works for what i want to achieve and the #ObservedObject can be ignored.
import SwiftUI
struct AddRecipe: View {
#ObservedObject var recipe: RecipeFinal
#State private var name = ""
#State private var time = 0
#State private var diff = ""
#State private var ingredients = [String]()
static var diffT = ["Easy", "Medium", "Difficult"]
static var ingred = ["Onion","Salt","Oil","Tomato", "Garlic",
"Peppers","Bread","Vinegar"]
var body: some View {
NavigationView {
Form {
TextField("Name", text: $name)
Stepper(value: $time, in: 0...120, step: 15) {
Text("Time: \(time) minutes")
}
Picker ("Difficulty", selection: $diff) {
ForEach (AddRecipe.self.diffT, id: \.self) {
Text($0)
}
}
Button("TEST") {
self.ingredients.append("TEST")
}
Picker("Ingredients", selection: $ingredients) {
ForEach (AddRecipe.self.ingred, id: \.self) { ing in
Button(action: {
self.add(element: ing)
}) {
Text("\(ing)")
}
}
}
Section(header: Text("Ingredients")) {
List (self.ingredients, id: \.self) {
Text($0)
}
}
}
}
}
func add (element: String) {
self.ingredients.append(element)
}
}
struct AddRecipe_Previews: PreviewProvider {
static var previews: some View {
AddRecipe(recipe: RecipeFinal())
}
}
I want to change name right after I get User(). DidSet does not work here. is there an alternative for didSet in SwiftUI?
struct Person: Identifiable {
let id = UUID()
var name: String
var number: Int
}
class User: ObservableObject {
#Published var array = [Person(name: "Nick", number: 3),
Person(name: "John", number: 2)
]
}
struct ContentView: View {
#ObservedObject var user = User() {
didSet {
user.array[0].name = "LoL"
}
}
var body: some View {
VStack {
ForEach (user.array) { row in
Text(row.name)
}
}
}
}
If I correctly understood your expectation (having your code) there are couple of options to reach the goal:
Option 1: Set up created user in init (as properties created before init)
init() {
self.user.array[0].name = "LoL"
}
Option 2: Set up it on view appearance
VStack {
ForEach (user.array) { row in
Text(row.name)
}
}
.onAppear {
self.user.array[0].name = "LoL"
}