i want to increase a product of its tapped count. so if the user press a button the Model needs to be updated and the amount += 1
I try the following code and the model does not update the amount.
struct GridCell: View {
#EnvironmentObject var order: Order
class ViewModel: ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
#Published var product : Product {
willSet {
self.objectWillChange.send()
}
}
init(product: Product) {
self.product = product
}
public func increaseAmount() {
self.product.amount += 1
self.objectWillChange.send()
print("\(self.product.amount) / \(self.product.id)")
}
}
func updatePos(){
self.viewModel.increaseAmount()
let itempreis = viewModel.product.preis
order.totalAmount += itempreis
print(order.totalAmount)
}
#ObservedObject var viewModel: ViewModel
var body: some View {
Button(action: {
print("TAPPED")
self.updatePos()
}) {
...
}
}
and GridCell would be
GridCell(viewModel: GridCell.ViewModel(product: $0))
and Product is
struct Product : Identifiable, Equatable, Decodable {
var id: String
var name: String
let preis: Double
var amount: Int
}
In my print statement the amount is not updating and every time I press the button the value is like 1.
What did I forget?
Try this
public func increaseCharacterStrength() {
self.product.amount += 1
}
Because Product is a struct when you were creating the variable var viewproduct = self.product you were creating a local copy of product, so viewproduct.amount += 1 just increases the amount for that local variable, not for the actual model.
I think you are not defining view model correctly. Since I do not have the structure of the product and order, I simplified the model to meet the needs of our discussion. For the example provided below, you can create the instance of GridCell (like GridCell()) and tapping the button increases the quantity by 1. Also note one can simplify further by using #Published property wrapper in the view model instead of Passthrough
class Order: ObservableObject {
#Published var totalAmount : Int = 0
}
struct Product {
var id = UUID()
var amount: Int = 0
var preis: Int = 0
}
struct GridCell: View {
#EnvironmentObject var order: Order
func updatePos() {
self.viewModel.increaseCharacterStrength()
let itempreis = self.viewModel.product.preis
order.totalAmount += itempreis
//print(order.totalAmount)
print("---- \(self.viewModel.product.amount) / \(self.viewModel.product.id)")
}
#ObservedObject var viewModel = ViewModel(product: Product())
var body: some View {
Button(action: {
print("TAPPED")
self.updatePos()
}) {
Text("Increase order")
}
}
}
class ViewModel: ObservableObject {
let objectWillChange = PassthroughSubject < Void,
Never > ()
var product: Product {
willSet {
self.objectWillChange.send()
}
}
init(product: Product) {
self.product = product
}
public func increaseCharacterStrength() {
self.product.amount += 1
self.objectWillChange.send()
}
}
Related
I am a newbie to SwiftUI, and I have a simple question, how to update the #State content of a VIEW in another Class, a simulated Code is as follows
When I press the button, the fruits.price variable is not updated in the View. Is there something I'm missing, or is there something wrong I don't understand
class Fruits: ObservableObject {
#Published var price = 1
}
struct ContentView: View {
#ObservedObject var fruits = Fruits()
var extenalClass = ExtenalClass()
var body: some View {
VStack {
Text(String(fruits.price))
.padding()
Button("Add-Me") {
// fruits.price += 1
extenalClass.fChangePrice()
print (fruits.price)
}
}
}
}
class ExtenalClass: ObservableObject {
#ObservedObject var fruits = Fruits()
func fChangePrice() {
fruits.price = 888
print (fruits.price)
}
}
We don't use classes in SwiftUI for model types, we use structs. Any change to the struct is detected by SwiftUI as a change to the state, so body is called to get the new Views, e.g.
struct Fruit {
var price = 1
}
struct ContentView: View {
#State var fruit = Fruit()
var body: some View {
VStack {
Text(fruit.price, format: .currency)
.padding()
Button("Add-Me") {
fruit.price += 1
print (fruit.price)
}
}
}
}
Thanks jnpdx, Baglan for the guidance .... I just found out my answer (probably not the right way), which is to put #Published and "My Function" together in the same CLASS...that is, Function is placed in the CLASS containing #Published...or, another way of saying , put #Published in the CLASS that contains "My Function"
class Fruits: ObservableObject {
#Published var price = 1
// Move (ExtenalClass) Function to here
func fChangePrice() {
price = 888
print (price)
}
}
struct ContentView: View {
#ObservedObject var fruits = Fruits()
// var extenalClass = ExtenalClass()
var body: some View {
VStack {
Text(String(fruits.price))
.padding()
Button("Add-Me") {
// fruits.price += 1
fruits.fChangePrice() // Modified
print (fruits.price)
}
}
}
}
//class ExtenalClass: ObservableObject {
// #ObservedObject var fruits = Fruits()
// func fChangePrice() {
// fruits.price = 888
// print (fruits.price)
// }
//}
I'm beggining with SwiftUI and I wanted to develop a small simple app to practice. I have a problem with #Published property that don't pass through views and so don't update the view.
I explain : In the first view I calculate the vMoyenne property and update it. I wanted to show this value in the next view ("Passage") to be able to use it for some other calculation but I tried many thing and the value in the "Passage" View doesn't update...
Here is the code :
ContentView.swift :
struct ContentView: View {
var body: some View {
TabView {
SpeedView().tabItem {
Label("Vitesse", systemImage: "figure.run.circle.fill")
}
PassageView(parameters: Parameters()).tabItem {
Label("Passage", systemImage: "timer.circle.fill")
}
}
}
}
Parameters.swift
class Parameters: ObservableObject {
#Published var distance: Double?
static let units = ["m", "km"]
#Published var unit = 1
#Published var hour: Int = 0
#Published var minute: Int = 0
#Published var second: Int = 0
#Published var vMoyenne = 0.0
#Published var allure = 0.0
#Published var convertedDecimalToSeconds = 0
var time: Int?
...
func calcVMoy() -> Void{
var d = distance!
let t = Double(time!) / 3600
var unite: String {
return Parameters.units[unit]
}
var calc = 0.0
if unite == "km" {
calc = d / t
} else {
d = d / 1000
calc = d / t
}
vMoyenne = calc
}
...
init() {
}
}
**SpeedView.swift **
struct SpeedView: View {
#ObservedObject var parameters = Parameters()
...
...
Button {
showVMoy = true
disableChange = true
if parameters.distance == nil {
parameters.distance = 0
} else {
parameters.runCalc()
}
} label: {
Text("Calculer")
}
... *// Here I can show and see the calculated vMoyenne property without problem...*
...
}
And the PassageView.swift where I want to show the vMoyenne property...
struct PassageView: View {
#ObservedObject var parameters:Parameters
var body: some View {
Text("\(parameters.vMoyenne)") *//want to show the vMoyenne value that we calculate previously but it always show 0,000...*
}
}
Thanks a lot for your help !!
PS : I tried many things like using didSet but I don't understand what I did wrong...
I found some post on stackoverflow but when I tried it doesn't work...
If you update the ContentView to it should work. The problem was that the SpeedView and PassageView were not sharing the same parameters object
struct ContentView: View {
#StateObject var parameters: Parameters = .init()
var body: some View {
TabView {
SpeedView(parameters: parameters).tabItem {
Label("Vitesse", systemImage: "figure.run.circle.fill")
}
PassageView(parameters: parameters).tabItem {
Label("Passage", systemImage: "timer.circle.fill")
}
}
}
}
I came across a situation that you use class data as your data source, and display them in a swiftUI list view, when you update your data source, the swiftUI list view won't be updated, what can we do to make the class data updates interactive with swiftUI?
see code blow:
I define the environment object :
import Foundation
import Combine
class DataSource: ObservableObject {
public static let shared = DataSource()
#Published var datalist: [RowData] = []
func fetch() -> Void {
for n in 1...50 {
let data = RowData(title: "Index:\(n)", count: 0)
datalist.insert(data, at: 0)
}
}
func update() {
for data in datalist {
data.count = data.count+1
print("\(data.title) update count to :\(data.count)")
data.objectWillChange.send()
}
self.objectWillChange.send()
}
}
to display each data in a Row View:
import SwiftUI
struct RowView: View {
#State var data: RowData
var body: some View {
HStack{
Text(data.title)
Spacer()
Text("\(data.count)")
}.padding()
}
}
struct RowView_Previews: PreviewProvider {
static var previews: some View {
RowView(data: RowData(title: "text", count: 1))
}
}
class RowData: ObservableObject {
var title: String = ""
var count: Int = 0
init(title: String, count: Int) {
self.title = title
self.count = count
}
}
in content view, display the data in a list view, I would like to refresh all the view updates when click update button. the button triggers the update methods to update the class data value from data source.
struct ContentView: View {
#EnvironmentObject var data: DataSource
#State var shouldUpdate:Bool = false
#State var localData:[RowData] = []
var body: some View {
VStack {
Button(action: {
// your action here
self.data.update()
self.shouldUpdate.toggle()
self.localData.removeAll()
self.localData = self.data.datalist
}) {
Text("update")
}
List {
ForEach(0..<self.localData.count, id:\.self) { index in
RowView(data: self.localData[index])
}
}
}
}
}
Well... I don't see the reason to have localData, but, anyway, here is modified code that works.
Tested with Xcode 12 / iOS 14
class DataSource: ObservableObject {
public static let shared = DataSource()
#Published var datalist: [RowData] = []
func fetch() -> Void {
for n in 1...50 {
let data = RowData(title: "Index:\(n)", count: 0)
datalist.insert(data, at: 0)
}
}
func update() {
for data in datalist {
data.count = data.count+1
print("\(data.title) update count to :\(data.count)")
}
self.objectWillChange.send()
}
}
struct RowView: View {
#ObservedObject var data: RowData
var body: some View {
HStack{
Text(data.title)
Spacer()
Text("\(data.count)")
}.padding()
}
}
class RowData: ObservableObject {
#Published var title: String = ""
#Published var count: Int = 0
init(title: String, count: Int) {
self.title = title
self.count = count
}
}
struct ContentView: View {
#EnvironmentObject var data: DataSource
#State var localData:[RowData] = []
var body: some View {
VStack {
Button(action: {
// your action here
self.data.update()
self.localData = self.data.datalist
}) {
Text("update")
}
List {
ForEach(0..<self.localData.count, id:\.self) { index in
RowView(data: self.localData[index])
}
}
}
.onAppear {
self.data.fetch()
self.localData = self.data.datalist
}
}
}
I have a View with many Buttons and if the User tap on a Button the Viewmodel need to update the current Button with the increased value.
class ProductVM: ObservableObject {
#Published var product : Product
init(product: Product) {
self.product = product
}
public func increaseAmount() {
var myInt = Int(self.product.amount) ?? 0
myInt += 1
self.product.amount = String(myInt)
print(myInt)
print("...")
}
}
the problem is the myInt is every time just 1 and the value can't be updated.
HOW can i update the value and save it in the current Model so that the View know its increased ??!!
struct singleButtonView: View {
#ObservedObject var productVM : ProductVM
func updatePos(){
self.productVM.increaseAmount()
}
}
and i call it with
singleButtonView(productVM: ProductVM(product: product))
Nested ObservableObjects need to be updated manually. Here is an example how this could look like:
class Product: ObservableObject, Identifiable, Codable {
let id: Int
let name: String
let prize: Double
#Published var amount: Int = 0
enum CodingKeys: String, CodingKey {
case id
case name
case prize
case amount
}
init() {
self.id = 0
self.name = "name"
self.prize = 0
}
init(id: Int, name: String, prize: Double) {
self.id = id
self.name = name
self.prize = prize
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(Int.self, forKey: .id)
name = try values.decode(String.self, forKey: .name)
prize = try values.decode(Double.self, forKey: .prize)
amount = try values.decode(Int.self, forKey: .amount)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(prize, forKey: .prize)
try container.encode(amount, forKey: .amount)
}
}
class ProductVM: ObservableObject {
#Published var product: Product
var cancelable: AnyCancellable? = nil
init(product: Product) {
self.product = product
self.cancelable = product.objectWillChange.sink(receiveValue: {
self.objectWillChange.send()
})
}
public func increaseAmount() {
self.product.amount += 1
}
}
struct ContentView: View {
#ObservedObject var productVM = ProductVM(product: Product())
var body: some View {
VStack {
Button(action: {
self.productVM.increaseAmount()
}) {
Text("Add")
}
Text("\(self.productVM.product.amount)")
}
}
}
I hope this helps!
Credits
So I'm getting the error Unknown attribute ObservableObject next to the #ObservableObject var dataSource = DataSource() call below. the ObservableObject worked perfectly a couple days ago in another project but not anymore.
import SwiftUI
import Combine
class DataSource: ObservableObject {
var willChange = PassthroughSubject<Void,Never>()
var expenses = [Expense]() {
willSet { willChange.send() }
}
var savingsItems = [SavingsItem](){
willSet { willChange.send() }
}
//#State var monthlyIncomeText: String
//var monthlyIncome: Int = 1364
init(){
addNewExpense(withName: "Spotify", price: 14)
}
func addNewExpense(withName name: String, price: Int){
let newExpense = Expense(name: name, price: price)
expenses.append(newExpense)
}
func addNewSavingsItem(withName name: String, price: Int, percentage: Double){
let newSavingsItem = SavingsItem(name: name, price: price, timeTilCompletion: 0, percentage: percentage)
savingsItems.append(newSavingsItem)
}
}
struct ContentView: View {
#ObservableObject var dataSource = DataSource()
var body: some View {
VStack{
Text("Expenses")
List(dataSource.expenses) { expense in
ExpenseRow(expense: expense)
}
}
}
}
Could anyone help?
ObservableObject is a protocol that ObservedObjects must conform to. See here for documentation on ObservableObject, and here for documentation on ObservedObject, which is the property wrapper that you are looking for. Change your ContentView code to this:
struct ContentView: View {
#ObservedObject var dataSource = DataSource()
var body: some View {
VStack {
Text("Expenses")
List(dataSource.expenses) { expense in
ExpenseRow(expense: expense)
}
}
}
}