is there an alternative for didSet in SwiftUI? - ios

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"
}

Related

How to create textfields using Foreach

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()
}

ObservableObject not updating view in nested loop SWIFTUI

Regarding the following project :
You have an amountSum of 100
When you click on one user "plus" button, this specific user have to pay this amount but if you click on multiple user "plus" button, the amount to pay is divided between them equally.
Any idea how I can update the entire Model2.MustPayM2 prop when I click on the "plus" button please ?
import SwiftUI
struct Model1: Identifiable, Codable {
var id: String = UUID().uuidString
var nameM1: String
var amountM1: Double
var amountSumM1: Double = 100
var arrayM2: [Model2]
var isVisible: Bool = false
}
struct Model2: Identifiable, Codable {
var id: String = UUID().uuidString
var nameM2: String
var amountM2: Double = 0
var mustPayM2: Bool = false
}
class ViewModel1: ObservableObject {
#Published var Publi1: Model1
#Published var Publi1s: [Model1] = []
#Published var Publi2: Model2
#Published var Publi2s: [Model2] = []
init() {
let pub2 = Model2(nameM2: "init")
let pub1 = Model1(nameM1: "init", amountM1: 0, arrayM2: [pub2])
self.Publi2 = pub2
self.Publi1 = pub1
var newPub1s: [Model1] = []
for i in (0..<5) {
let newNameM1 = "name\(i+1)"
let newAmountM1 = Double(i+1)
var newModel1 = Model1(nameM1: newNameM1, amountM1: newAmountM1, arrayM2: [pub2])
var newPub2s: [Model2] = []
for i in (0..<5) {
let newNameM2 = "\(newNameM1)-user\(i+1)"
let newModel2 = Model2(nameM2: newNameM2)
newPub2s.append(newModel2)
}
newModel1.arrayM2 = newPub2s
newPub1s.append(newModel1)
}
Publi1s = newPub1s
Publi1 = newPub1s[0]
Publi2s = newPub1s[0].arrayM2
Publi2 = newPub1s[0].arrayM2[0]
}
}
struct View1: View {
#EnvironmentObject var VM1: ViewModel1
#State private var tt: String = ""
private let screenHeight = UIScreen.main.bounds.height
var body: some View {
ZStack {
VStack {
ForEach(0..<VM1.Publi2s.count, id: \.self) { i in
Text("\(VM1.Publi2s[i].nameM2)")
Text(tt)
Button {
VM1.Publi2s[i].mustPayM2.toggle()
var a = VM1.Publi2s.filter { $0.mustPayM2 == true }
let b = VM1.Publi1.amountM1 / Double(a.count)
// How can I update the new props between all users ??
// for j in 0..<a.count {
// a[j].amountM2 = b
// }
} label: {
Image(systemName: "plus")
}
}
Spacer()
Button {
VM1.Publi1.isVisible.toggle()
} label: {
Text("SHOW ME")
}
Spacer()
}
View2()
.offset(y: VM1.Publi1.isVisible ? 0 : screenHeight)
}
}
}
struct View2: View {
#EnvironmentObject var VM1: ViewModel1
var body: some View {
VStack {
Spacer()
ForEach(0..<VM1.Publi2s.count, id: \.self) { i in
Text("\(VM1.Publi2s[i].amountM2)")
}
}
}
}
struct View2_Previews: PreviewProvider {
static var previews: some View {
Group {
View1()
}
.environmentObject(ViewModel1())
}
}
You implementation seems overly complicated and error prone. I´ve practically rewritten the code for this. I´ve added comments to make it clear what and why I have done certain things. If you don´t understand why, don´t hesitate to ask a question. But please read and try to understand the code first.
//Create one Model containing the individuals
struct Person: Identifiable, Codable{
var id = UUID()
var name: String
var amountToPay: Double = 0.0
var shouldPay: Bool = false
}
//Create one Viewmodel
class Viewmodel:ObservableObject{
//Entities being observed by the View
#Published var persons: [Person] = []
init(){
//Create data
persons = (0...4).map { index in
Person(name: "name \(index)")
}
}
//Function that can be called by the View to toggle the state
func togglePersonPay(with id: UUID){
let index = persons.firstIndex { $0.id == id}
guard let index = index else {
return
}
//Assign new value. This will trigger the UI to update
persons[index].shouldPay.toggle()
}
//Function to calculate the individual amount that should be paid and assign it
func calculatePayment(for amount: Double){
//Get all persons wich should pay
let personsToPay = persons.filter { $0.shouldPay }
//Calcualte the individual amount
let individualAmount = amount / Double(personsToPay.count)
//and assign it. This implementation will trigger the UI only once to update
persons = persons.map { person in
var person = person
person.amountToPay = person.shouldPay ? individualAmount : 0
return person
}
}
}
struct PersonView: View{
//pull the viewmodel from the environment
#EnvironmentObject private var viewmodel: Viewmodel
//The Entity that holds the individual data
var person: Person
var body: some View{
VStack{
HStack{
Text(person.name)
Text("\(person.amountToPay, specifier: "%.2f")$")
}
Button{
//toggle the state
viewmodel.togglePersonPay(with: person.id)
} label: {
//Assign label depending on person state
Image(systemName: "\(person.shouldPay ? "minus" : "plus")")
}
}
}
}
struct ContentView: View{
//Create and observe the viewmodel
#StateObject private var viewmodel = Viewmodel()
var body: some View{
VStack{
//Create loop to display person.
//Dont´t itterate over the indices this is bad practice
// itterate over the items themselves
ForEach(viewmodel.persons){ person in
PersonView(person: person )
.environmentObject(viewmodel)
.padding(10)
}
Button{
//call the func to calculate the result
viewmodel.calculatePayment(for: 100)
}label: {
Text("SHOW ME")
}
}
}
}

SwiftUI - Should you use `#State var` or `let` in child view when using ForEach

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
}

Why published Array does not change after I change him in SwiftUI?

So, this is just a sample code from my project. After I call SecondView, I want to change names in the array to "LoL", and display them. Why init() does not change my array? Since it does not display new names
struct Person: Identifiable {
let id = UUID()
var name: String
var index: Int
}
class User: ObservableObject {
#Published var array = [Person(name: "Nick", index: 0),
Person(name: "John", index: 1)
]
}
struct ContentView: View {
#ObservedObject var user = User()
var body: some View {
VStack {
ForEach (user.array) { row in
SecondView(name: row.name, index: row.index)
}
}
}
}
struct SecondView: View {
#ObservedObject var user = User()
var name = ""
var index = 0
init() {
user.array[index].name = "LoL"
}
init(name: String, index: Int) {
self.name = name
self.index = index
}
var body: some View {
VStack {
Text(name)
}
}
}
It is because you are not calling init() method in SecondView but you are calling init(name:, index:). Notice how you use SecondView initializer inside your FirstView's iteration (ie. ForEach) loop.
Also your second view displays the name that is passed along the initializer init(name:index:), not the one from your user array. So, if you want to change name to something, do that before this init(name:index:) is called, and pass the name from user array.
You can do it inside your first view.
struct ContentView: View {
#ObservedObject var user = User()
init() {
user.array[0].name = "LoL"
}
var body: some View {
VStack {
ForEach (user.array) { row in
SecondView(name: row.name, index: row.index)
}
}
}
}
Notice that it will now change the first name to Lol because we change it inside ContentView's initializer which then uses the same modified name.

SwiftUI Picker in Form does not show checkmark

I have a Picker embedded in Form, however I can't get it working that it shows a checkmark and the selected value in the form.
NavigationView {
Form {
Section {
Picker(selection: $currencyCode, label: Text("Currency")) {
ForEach(0 ..< codes.count) {
Text(self.codes[$0]).tag($0)
}
}
}
}
}
TL;DR
Your variable currencyCode does not match the type of the ID for each element in your ForEach. Either iterate over the codes in your ForEach, or pass your Picker an index.
Below are three equivalent examples. Notice that the #State variable which is passed to Picker always matches the ID of element that the ForEach iterates over:
Also note that I have picked a default value for the #State variable which is not in the array ("", -1, UUID()), so nothing is shown when the form loads. If you want a default option, just make that the default value for your #State variable.
Example 1: Iterate over codes (i.e. String)
struct ContentView: View {
#State private var currencyCode: String = ""
var codes: [String] = ["EUR", "GBP", "USD"]
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $currencyCode, label: Text("Currency")) {
// ID is a String ----v
ForEach(codes, id: \.self) { (string: String) in
Text(string)
}
}
}
}
}
}
}
Example 2: Iterate over indices (i.e. Int)
struct ContentView: View {
#State private var selectedIndex: Int = -1
var codes: [String] = ["EUR", "GBP", "USD"]
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedIndex, label: Text("Currency")) {
// ID is an Int --------------v
ForEach(codes.indices, id: \.self) { (index: Int) in
Text(self.codes[index])
}
}
}
}
}
}
}
Example 3: Iterate over an identifiable struct by its ID type (i.e. UUID)
struct Code: Identifiable {
var id = UUID()
var value: String
init(_ value: String) {
self.value = value
}
}
struct ContentView: View {
#State private var selectedUUID = UUID()
var codes = [Code("EUR"), Code("GBP"), Code("USD")]
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedUUID, label: Text("Currency")) {
// ID is a UUID, because Code conforms to Identifiable
ForEach(self.codes) { (code: Code) in
Text(code.value)
}
}
}
}
}
}
}
It's difficult to say, what you are doing wrong, because your example doesn't include the declaration of codes or currencyCode. I suspect that the problem is with the binding being of a different type than the tag you are setting on a picker (which is an Int in your case).
Anyway, this works:
struct ContentView: View {
let codes = Array(CurrencyCode.allCases)
#State private var currencyCode: CurrencyCode?
var body: some View {
NavigationView {
Form {
Section {
Picker("Currency",
selection: $currencyCode) {
ForEach(codes, id: \.rawValue) {
Text($0.rawValue).tag(Optional<CurrencyCode>.some($0))
}
}
}
}
}
}
}
enum CurrencyCode: String, CaseIterable {
case eur = "EUR"
case gbp = "GBP"
case usd = "USD"
}

Resources