#Published String Variable Not Updating View - ios

My #Published string variable in Observableobejct does not update the Text somehow.
Exact same codes works in different view, but I can't figure out why this isn't working in this view.
When I searched Google all I figured out was about checking the initialization of observableobject, setting didSet to variable, but all of them did not work and I just reverted back.
The below is the code that does NOT update the view when string changes in viewdidappear().
struct StorageUpgradeView: View {
#ObservedObject private var storagevm = StorageVM()
#Environment(\.presentationMode) var presentationMode
var body: some View {
ZStack {
VStack {
ZStack(alignment: .center) {
HStack {
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Image("Image_arrowleft")
.resizable()
.frame(width: 30, height: 30, alignment: .leading)
})
Spacer()
}
Text("Storage Upgrade".localized)
.font(.system(size: 24))
}
.padding()
Text(storagevm.month_price)
Text("\(storagevm.halfyear_price)")
Spacer()
}
}
.onAppear(perform: {
storagevm.viewdidappear()
})
.frame(maxWidth: .infinity, maxHeight: .infinity)
.foregroundColor(Color("Theme_OnSurface"))
.background(Color("Theme_Surface").ignoresSafeArea())
}
}
class StorageVM: ObservableObject {
#Published var month_price = "plpl"
#Published var halfyear_price: String
init() {
halfyear_price = "asdf"
}
func viewdidappear() {
month_price = "BlahBlah"
halfyear_price = "were"
}
}
But view prints just well when I switch the view by changing tabs. What is the problem here? I couldn't figure out after two days of googling and banging my head.

Whenever you want to observe a #Published attribute, you want to use the $ sign before the element. So try using Text(storagevm.$month_price)

Using StateObject instead of ObservedObject worked well for me! Be sure that your iOS target needs to be 14.0 or higher for this solution to work.

Related

SwiftUI App seems to change value of StateObject by itself when it goes to background

While building my app I stumbled upon a bug that keeps me up at night.
I have a View which has a button that when pressed is supposed to change value of a field of a StateObject which should activate a NavigationLink. Up until now app works as expected. However when I am in the View that navigationLink leads to and I put my app to background by quitting it and opening it again the navigation view pops back to root view.
Also, I set up an onReceive to print when "isShowingFocusView" (the field which activates the NaigationLink) changes. When quitting it shows that value changes by itself to false thus deactivating navigationLink.
This is the view model which contains the field that is the core of my problem.
class OverlayButtonViewModel: ObservableObject {
#Published var isShowingFocusView: Bool
init() {
isShowingFocusView = false
}
}
This is where I define the ViewModel and pass it as parameter to buttonView which pressed activates the NavigationLink.
struct OverlayButtonView: View {
#StateObject var viewModel = OverlayButtonViewModel()
// some view hierarchy
HStack {
Spacer()
OverlayFocusButtonView(show: $viewModel.isShowingFocusView)
.offsetToPositionIfNot(self.showingButtons, self.homeLocation)
.opacityIfNot(self.showingButtons, 0)
.padding(.trailing, 45)
.onChange(of: viewModel.isShowingFocusView) { value in
print("Current value \(value)")
}
}
Here is how I defined the view that consists the problematic NavigationLink:
struct OverlayFocusButtonView: View {
#Binding var show: Bool
var body: some View {
NavigationLink(destination: FocusView(show: $show), isActive: $show) {
Circle()
.foregroundColor(Color("IdestBlue"))
.frame(width: 60, height: 60)
.overlay(Image("FocusModeIcon")
.resizable()
.renderingMode(.template)
.foregroundColor(.white)
.frame(width: 30, height: 30, alignment: .center)
)
}.onTapGesture {
show = true
}
}
}
Lastly, this is the part of the view where I deactivate the NavigationLink
struct FocusView: View {
#Binding var show: Bool
HStack{
Image(systemName: "arrow.backward")
.foregroundColor(.black)
.font(.system(size: 20, weight: .bold))
.padding(.leading, 20)
.padding(.top, 10)
.onTapGesture {
show = false
}
Spacer()
}
}
My understanding of the topic is that in order for app to not reinitiate field when reloading view it must be a State property and this is what I am doing, yet still the value change occurs.
To be clear: I expect that when I go back to my app I will see the same view as the one I saw when I was quitting.
Thank you for your help in advance. I really don't see what I am doing wrong here so if you have any idea what might be incorrect please help :)
The issue, I believe, is where you create the state object. You're creating the object when the view is initialized. However, SwiftUI may re-instantiate that object for certain reasons, like backgrounding the application.
Instead, I would hoist the creation of the #StateObject up into your {AppName}App.swift file and pass it in as an environment object there.
#main
struct MyApp: App {
#StateObject var viewModel = OverlayButtonViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(viewModel)
}
}
}
struct OverlayButtonView: View {
#EnvironmentObject var viewModel: OverlayButtonViewModel
var body: some View {
SomeContent($viewModel)
}
}

How do you send values to other classes using ObservableObject in SwiftUI

Im trying to create a simple tip Calculator but i am having a problem once a user has added in the information in the app instead of calculating the tip, the function is returning as if there is an issue somewhere.
Im trying to make it so that when a user inputs a number in the text field and selects a percentage the total tip is displayed underneath.
How do i fix this problem?
Ive added a print statement to print the word "returning" and it keeps printing this word so i think the problem is somewhere in the calculateTip function:
This is my Code for my ContentView class:
struct ContentView: View {
//MARK: - PROPERTIES
#ObservedObject public var tipVM = TipViewModel()
//MARK: - FUNCTIONS
private func endEditing() {
hideKeyboard()
}
//MARK: - BODY
var body: some View {
Background {
NavigationView {
ZStack {
VStack {
HStack(spacing: 10) {
Text("Tip Calculator")
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.heavy)
.padding(.leading, 4)
.foregroundColor(.blue)
Spacer()
Button(action: {
// tipVM.clearFields()
}, label: {
Text("Clear")
.font(.system(size: 16, weight: .semibold, design: .rounded))
.padding(.horizontal, 10)
.frame(minWidth: 70, minHeight: 24)
.background(
Capsule().stroke(lineWidth: 2)
)
.foregroundColor(.blue)
}) //: BUTTON
} //: HSTACK
.padding()
Spacer(minLength: 80)
TextField("Enter Amount: ", text: $tipVM.amount)
.padding()
.background(Color.secondary)
.foregroundColor(.white)
.font(.system(.title3, design: .rounded))
Picker(selection: $tipVM.tipPercentage, label: Text("Picker"), content: {
ForEach(0 ..< tipVM.tipChoices.count) { index in
Text("\(self.tipVM.tipChoices[index])%").tag(index)
.font(.system(.body, design: .rounded)).padding()
}.padding()
.background(subtitleColor)
.foregroundColor(.white)
}).onTapGesture(perform: {
tipVM.calculateTip()
})
.pickerStyle(SegmentedPickerStyle())
Text(tipVM.tip == nil ? "£0" : "\(tipVM.tip!)")
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.bold)
.foregroundColor(.blue)
.padding()
} //: VSTACK
} //: ZSTACK
.navigationBarHidden(true)
} //: NAVIGATION VIEW
.navigationViewStyle(StackNavigationViewStyle())
}.onTapGesture {
self.endEditing()
}
.ignoresSafeArea(.keyboard, edges: .all)
} //: BACKGROUND
}
And here is my code for my TipViewModel Class:
import Foundation
import SwiftUI
import Combine
class TipViewModel: ObservableObject {
var amount: String = ""
var tipPercentage: Int = 0
var tip: Double?
let tipChoices = [10,15,20,25,30]
let didChange = PassthroughSubject<TipViewModel, Never>()
func calculateTip() {
guard let amount = Double(amount) else {
print("returning")
return
}
self.tip = amount * (Double(tipPercentage)/100)
self.didChange.send(self)
}
}
I would appreciate any help thanks.
The usual way is to mark the properties with #Published whose changes are going to be monitored. The extra Combine subject is not needed.
And declare tip as non-optional
import Foundation
import SwiftUI
// import Combine
class TipViewModel: ObservableObject {
var amount: String = ""
var tipPercentage: Int = 0
#Published var tip = 0.0
let tipChoices = [10,15,20,25,30]
func calculateTip() {
guard let numAmount = Double(amount) else {
print("returning")
return
}
self.tip = numAmount * (Double(tipPercentage)/100)
}
}
Secondly if the current view struct creates (aka owns) the observable object use always #StateObject rather than #ObservedObject. The latter is for objects which are initialized at higher levels in the view hierarchy and just passed through.
struct ContentView: View {
//MARK: - PROPERTIES
#StatedObject private var tipVM = TipViewModel()
...
Text("£\(tipVM.tip)")

How to change the label of a button by using an if statement in SwiftUI

I want my button to look different after pressing the button. I am trying to do so by using a ZStack and an if statement. I don't understand why it won't toggle between a black button and a white button... P.S. I am getting experience using ObservableObject protocol.
class WelcomeButton: ObservableObject {
#Published var hasBeenPressed = false
}
struct WelcomeScreenButton: View {
#ObservedObject var welcomeButton = WelcomeButton()
var body: some View {
ZStack {
if welcomeButton.hasBeenPressed {
Circle()
.fill(Color(.white))
}
else {
Circle()
.fill(Color(.black))
}
}
}
}
struct ContentView: View {
#ObservedObject var welcomeButton = WelcomeButton()
var body: some View {
VStack {
Button(action: {welcomeButton.hasBeenPressed.toggle()})
{
WelcomeScreenButton()
}
.frame(width: 375, height: 375, alignment: .center)
}
}
}
You're very close, but you have to have the same instance of the WelcomeButton ObservableObject shared between the two. Right now, they're two separate instances, so when you update hasBeenPressed on one, the other one doesn't know to change its state.
struct WelcomeScreenButton: View {
#ObservedObject var welcomeButton : WelcomeButton //<-- here
var body: some View {
ZStack {
if welcomeButton.hasBeenPressed {
Circle()
.fill(Color(.white))
}
else {
Circle()
.fill(Color(.black))
}
}
}
}
struct ContentView: View {
#ObservedObject var welcomeButton = WelcomeButton()
var body: some View {
VStack {
Button(action: {welcomeButton.hasBeenPressed.toggle()})
{
WelcomeScreenButton(welcomeButton: welcomeButton) //<-- here
}
.frame(width: 375, height: 375, alignment: .center)
}
}
}
PS -- just as a general practice, it might be good to make the naming a little different just to keep things straight. WelcomeButton sounds like it's going to be a button. Naming it WelcomeButtonViewModel instead would make its intention more clear, although it wouldn't change the functionality at all.

Background Image not appearing in XCODE 12

I have a Zstack within ContentView in xcode I'm attempting to add a background image to. This image is intended to serve as the background for the entire screen, but it does not show up. The image is saved in xcassets with the correct label for the code below. This has been double, no triple-checked.
My code:
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#State var phoneNumberCompleted = false;
#State var phoneNumber = ""
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View
{
ZStack
{
NavigationView
{
Form
{
Section(header: Text("Account Credentials"))
{
TextField("Phone Number", text: $phoneNumber)
if phoneNumberCompleted
{
Button(action: {
}) {
HStack
{
Spacer()
Text("Log in")
.font(.system(size: 20))
.padding(2)
.background(Color.red)
.foregroundColor(Color.white)
.cornerRadius(10)
.shadow(color: Color.gray, radius: 2)
Spacer()
}
}
}
}
Section(header: Text("ABOUT APP"))
{
HStack
{
Text("Version")
Spacer()
Text("0.0.1")
}
}
}
.navigationBarTitle("Welcome to Mapwork")
}
}.background(Image("bg"))
.opacity(100)
}
}
I'm wondering if I am using the correct approach or if something is wrong here. Any help appreciated.
First of all you need to add the code below to make the background clear:
init() {
UITableView.appearance().backgroundColor = .clear
}
Then you can add your background without a problem and here it is the result:
.navigationBarTitle("Welcome to Mapwork")
.background(Image("image1"))
Note: I don't know why are you using opacity anyway, for your information the opacity value it must be between 0 and 1.

Problems with layout of some rows in SwiftUI list

I have tried to find a similar problem asked before, but failed.
I have a simple view with list. I am using a ForEach to show 10 iterations of the some list item to create a layout before I will add real data to this list. I have a problem with last 2 rows not rendering correctly. But sometimes it’s other row. I have tested on an iPhone too and sometimes it’s one row, sometimes another. The code for the view with list is this:
import SwiftUI
struct LocksView: View {
#State private var locksPaid = 0
var body: some View {
NavigationView {
List {
DateView()
.listRowInsets(EdgeInsets())
Picker(selection: $locksPaid, label: Text("Picker")) {
Text("All").tag(0)
Text("Not paid (2)").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.padding(10)
ForEach(0 ..< 10) {item in
LocksItemView()
}
}
.navigationBarTitle(Text("Locks"))
.navigationBarItems(trailing: EditButton())
}
}
}
The code for list items is this:
import SwiftUI
struct LocksItemView: View {
#State private var paid : Bool = false
var body: some View {
HStack {
Text("L15")
.font(.title)
.fontWeight(.heavy)
.multilineTextAlignment(.center)
.frame(width: 80)
VStack(alignment: .leading) {
Text("nickname")
.fontWeight(.bold)
Text("category")
Text("4 000 THB")
.fontWeight(.bold)
}
Spacer()
Toggle(isOn: self.$paid) {
Text("Paid")
}
.labelsHidden()
}
}
}
Why is toggle broken in some rows on my list? Why it moves to the left side?
It is not last items. If you set in your ForEach 20 instead of 10 and scroll up & down you'll see much more interesting issue.
I assume the reason, actually List bug, is the same as in this topic.
Workaround If it is not critical to you then use ScrollView instead of List, as tested there is no bug for it. Otherwise, file a Radar and wait for fix.
I tried your code at simulators first and had same issue too. But then I remembered, that there are some problems with 13.2 iOS and tried to run it on my device (iPhone 7, iOS 13.1.1) and everything works fine! I think that is the problem in 13.2 iOS, not in the List. There is sample, how I changed code for demonstration that everything is ok:
import SwiftUI
struct LocksView: View {
#State private var locksPaid = 0
var body: some View {
NavigationView {
List {
Picker(selection: $locksPaid, label: Text("Picker")) {
Text("All").tag(0)
Text("Not paid (2)").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.padding(10)
ForEach(0 ..< 200) {item in
LocksItemView(number: item)
}
}
.navigationBarTitle(Text("Locks"))
.navigationBarItems(trailing: EditButton())
}
}
}
struct LocksItemView: View {
#State private var paid : Bool = false
var number: Int
var body: some View {
HStack {
Text("L\(self.number)")
.font(.title)
.fontWeight(.heavy)
.multilineTextAlignment(.center)
.frame(width: 80)
VStack(alignment: .leading) {
Text("nickname")
.fontWeight(.bold)
Text("category")
Text("4 000 THB")
.fontWeight(.bold)
}
Spacer()
Toggle(isOn: self.$paid) {
Text("Paid")
}
.labelsHidden()
}
}
}
and on my phone the result is:
so there are bugs in 13.2 version and I hope Apple will fix them all

Resources