ObservableObject doesn't update view - ios

I'm pretty new to SwiftUI (and Swift I haven't touch for a while either) so bear with me:
I have this view:
import SwiftUI
import Combine
var settings = UserSettings()
struct Promotion: View {
#State var isModal: Bool = true
#State private var selectedNamespace = 2
#State private var namespaces = settings.namespaces
var body: some View {
VStack {
Picker(selection: $selectedNamespace, label: Text("Namespaces")) {
ForEach(0 ..< namespaces.count) {
Text(settings.namespaces[$0])
}
}
}.sheet(isPresented: $isModal, content: {
Login()
})
}
}
What I do here, is to call a Login view upon launch, login, and when successful, I set the
var settings
as such in the LoginView
settings.namespaces = ["just", "some", "values"]
my UserSettings class is defined as such
class UserSettings: ObservableObject {
#Published var namespaces = [String]()
}
According to my recently obtained knowledge, my Login view is setting the namespaces property of my UserSettings class. Since this class is an ObservableObject, any view using that class should update to reflect the changes.
However, my Picker remains empty.
Is that because of a fundamental misunderstanding, or am I just missing a comma or so?

You have to pair ObservableObject with ObservedObject in view, so view is notified about changes and refreshed.
Try the following
struct Promotion: View {
#ObservedObject var settings = UserSettings() // << move here
#State var isModal: Bool = true
#State private var selectedNamespace = 2
// #State private var namespaces = settings.namespaces // << not needed
var body: some View {
VStack {
Picker(selection: $selectedNamespace, label: Text("Namespaces")) {
ForEach(namespaces.indices, id: \.self) {
Text(settings.namespaces[$0])
}
}
}.sheet(isPresented: $isModal, content: {
Login(settings: self.settings) // inject settings
})
}
}
struct Login: View {
#ObservedObject var settings: UserSettings // << declare only !!
// ... other code
}

Related

Swiftui form re-using at multiple place not working

I have created model and using that model Im modify variable data at multiple places I can modify and enter data succesfully in FirstView. I could able to modify data in the SecondView. In SecondView, Whatever content I type in the textfield it goes away instanly (in short not allowing to enter data and ofc no error shown)
I want to know am i using proper object variable to call model every time
class MainViewModel: ObservableObject {
#Published var name = ""
#Published var age = ""
}
// Using at one place
struct FirstView : View {
#StateObject var mainViewModel = MainViewModel()
var body: some View {
Form {
TextField("", text: self.$MainViewModel.name)
TextField("", text: self.$MainViewModel.age)
}
}
}
// ReUsing same at another place
struct SecondView : View {
#EnvironmentObject var mainViewModel = MainViewModel()
var body: some View {
Form {
TextField("", text: self.$MainViewModel.name)
TextField("", text: self.$MainViewModel.age)
}
}
}
I have tried using #EnvironmentObject using at both view but doesnt work either here
Change
#EnvironmentObject var mainViewModel = MainViewModel()
To
#EnvironmentObject var mainViewModel : MainViewModel
Make sure you are injecting in the parent view
.environmentObject(mainViewModel)
#lorem ipsum explain the question perfectly. I am just converting his comments into working code. Please have look. This will make you more clear about your issue about injecting from parent.
import SwiftUI
#main
struct StackOverflowApp: App {
#State private var searchText = ""
var body: some Scene {
WindowGroup {
NavigationView {
FirstView()
.environmentObject(MainViewModel())
}
}
}
}
import SwiftUI
class MainViewModel: ObservableObject {
#Published var name = ""
#Published var age = ""
}
// Using at one place
struct FirstView : View {
#EnvironmentObject var mainViewModel : MainViewModel
var body: some View {
VStack {
Form {
TextField("", text: $mainViewModel.name)
TextField("", text: $mainViewModel.age)
}
NavigationLink {
SecondView()
.environmentObject(mainViewModel)
// Either you can inject new or same object from child to parent. #lorem ipsum
// .environmentObject(MainViewModel())
} label: {
Text("Second View")
}
}
}
}
// ReUsing same at another place
struct SecondView : View {
#EnvironmentObject var mainViewModel : MainViewModel
var body: some View {
Form {
TextField("", text: $mainViewModel.name)
TextField("", text: $mainViewModel.age)
}
}
}

Why does SwiftUI not update a view here?

The background color and text in ReadyDashboardView below don't update when isConnected is updated. Obviously I want to update the view when the connection is made. I was expecting that publishing everything in the chain to that variable would make it update instantly in swiftui. Instead, it's always rendering using the value provided when the variable is instantiated. Here's a very simplified look at the situation:
Is there another swiftui feature i should be using or am I going to have to make some sweeping changes to my codebase?
import SwiftUI
#main
struct TestApp: App {
#StateObject private var env = PrinterEnv()
var body: some Scene {
WindowGroup {
ReadyDashboardView()
.environmentObject(env)
}
}
}
struct ReadyDashboardView: View {
#EnvironmentObject var env: PrinterEnv
var body: some View {
VStack {
HStack {
Spacer()
VStack {
Text(env.selectedPrinter?.isConnected ?? false ? "Printer Ready" : "Not Connected")
.padding(.bottom)
}
Spacer()
}
.background(env.selectedPrinter?.isConnected ?? false ? .green : .red)
Button("Connect") { env.selectedPrinter?.isConnected = true }
Button("Disconnect") { env.selectedPrinter?.isConnected = false }
}
}
}
class PrinterEnv: ObservableObject {
#Published var configuredPrinters: [Printer] = []
#Published var selectedPrinter: Printer?
init() {
configuredPrinters.append(contentsOf: [Printer()])
selectedPrinter = configuredPrinters.first
}
}
class Printer: ObservableObject {
#Published var isConnected = false
}
I suggest you do not nest ObservableObject, it does not work very well.
Try a Printer struct for example, such as:
struct Printer: Identifiable {
let id = UUID()
var isConnected = false
}
I do not think the problem is specifically related to nested observable objects. Nesting them is working fine and is even recommended by community members as the best way to manage app wide states and ensure performance is acceptable.
See this: https://www.fivestars.blog/articles/app-state/
That said, I believe it's probably related to the wrapping of the observable object with #Published property wrapper.
Try this in a playground:
import SwiftUI
class AppState: ObservableObject {
let fooState = FooState()
let barState = BarState()
}
class FooState: ObservableObject {
#Published var foo: Int = 42
}
class BarState: ObservableObject {
#Published var bar: String = Date().debugDescription
}
struct FooView: View {
#EnvironmentObject var fooState: FooState
var body: some View {
Text("foo: \(fooState.foo)")
}
}
struct BarView: View {
#EnvironmentObject var barState: BarState
var body: some View {
Text("bar: \(barState.bar)")
}
}
struct ContentView: View {
#EnvironmentObject var appState: AppState
var body: some View {
VStack {
Spacer()
FooView()
.environmentObject(appState.fooState)
Button("update foo") {
appState.fooState.foo = Int.random(in: 1...100)
}
Spacer()
BarView()
.environmentObject(appState.barState)
Button("update bar") {
appState.barState.bar = Date().debugDescription
}
Spacer()
}
}
}
import PlaygroundSupport
PlaygroundPage.current.liveView = UIHostingController(
rootView: ContentView()
.frame(width: 320, height: 414)
.environmentObject(AppState())
)

Importing the data typed by the user to a view?

In the process of making my first Finance App, I want the user to type their Credit Card Name and las four numbers (probably more info since this is a draft) into this Modally presented view, to then be seen in a cards index, widget-look-like.
struct CardListView: View {
#State var isPresentingAddModal = false
#State var emisorTarjeta = ""
#State var numeroTarjeta = ""
var headerView: some View {
HStack {
Text("Tus tarjetas")
Spacer()
Button("Añadir nueva") {
self.isPresentingAddModal.toggle()
}
.sheet(isPresented: $isPresentingAddModal, content: {
HStack {
Text("Emisor de tarjeta")
TextField("Seleccionar emisor de tarjeta", text: $emisorTarjeta)
}
HStack {
Text("Número de tarjeta")
TextField("Escribí tu número de tarjeta", text: $numeroTarjeta)
}
Button(action: {
self.isPresentingAddModal.toggle()
print("\(self.emisorTarjeta)")
}, label: {
Text("Añadir")
})
Spacer()
})
}
The question now is how to pass the info typed from the two textFields, to the view where the cards will be created. The button "Añadir" currently works as a dismiss button instead of an add one, since I don't know how to create that.
(Also, a lot of code like paddings and backgroundColors have been erased to make it clearer to see)
Enitre view of the homeView
Where the "añadir" button is
there are several ways to do this. One simple way is to use "#State" and "#Binding" like this:
In "CardListView" use this:
#Binding var emisorTarjeta: String
#Binding var numeroTarjeta: String
and in the "CardViewCreator" use:
#State var emisorTarjeta = ""
#State var numeroTarjeta = ""
Another way is to use "ObservableObject", create a class like this:
class CardModel: ObservableObject {
#Published var emisorTarjeta = ""
#Published var numeroTarjeta = ""
}
In the your "CardViewCreator" or some parent view:
#StateObject var cardModel = CardModel()
and pass it to the "CardListView" like this:
struct CardListView: View {
#ObservedObject var cardModel: CardModel
...
}
You can also use "EnvironmentObject" in a similar way.
It all depends on your case. I recommend reading up on "ObservedObject"
and using that.
A really simple way of doing this is to pass in a closure to run when the add button is tapped. Here's an example, which also shows how to dismiss the presented sheet
import SwiftUI
struct Card: Identifiable {
let id = UUID()
let provider: String
let number: String
}
struct ContentView: View {
#State private var cards = [Card]()
#State private var showingSheet = false
var body: some View {
VStack {
List(cards, rowContent: CardView.init)
.padding(.bottom, 10)
Button("Add") {
showingSheet = true
}
.padding()
}
.sheet(isPresented: $showingSheet) {
AddSheet(completion: addCard)
}
}
func addCard(provider: String, number: String) {
let newCard = Card(provider: provider, number: number)
cards.append(newCard)
}
}
struct CardView: View {
let card: Card
var body: some View {
VStack(alignment: .leading) {
Text(card.provider)
Text(card.number)
}
}
}
struct AddSheet: View {
#Environment(\.presentationMode) var presentationMode
#State private var provider = ""
#State private var number = ""
let completion: (String, String) -> Void
var body: some View {
VStack {
TextField("Provider", text: $provider).padding()
TextField("Number", text: $number).padding()
Button("Add") {
completion(provider, number)
presentationMode.wrappedValue.dismiss()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you want to actually save the information passed in the textfield you would have to save it somewhere and later fetch it when required But this is only if you want to be able to access the information passed into the cards index after you have closed down the application and opened it up once again.

#Published Var not updating subview

My problem is that my #Published variable isn't updating my swiftui subview. It changes from false to true, however the view itself isn't updating. Below I pasted a very simplified version of my problem. Also, I passed the api variable that was created within the FirstView to the SubView, however the view still didn't change. If this isn't enough information and you are okay with looking at the full project I can share it down below.
import Foundation
import SwiftUI
struct FirstView: View {
#ObservedObject var Api = API()
var body: some View {
VStack{
SubView()
Button(action:{
Api.apicall()
}){
Text("search")
}
}
}
}
struct SubView:View{
#ObservedObject var daApi = API()
var array = [1,2,3]
var body: some View{
ForEach(0..<array.count){ number in
Text("\(number)")
}
if daApi.viewPresent == true{
Text("Swag")
}
}
}
class API:ObservableObject{
#Published var viewPresent:Bool = false
func apicall(){
viewPresent = true
//The Long ApiCall
viewPresent = false
}
}
Right now, you have 2 different instances of API. You need to share the same instance between both Views:
struct FirstView: View {
#ObservedObject var Api = API()
var body: some View {
VStack{
SubView(daApi: Api)
Button(action:{
Api.apicall()
}){
Text("search")
}
}
}
}
struct SubView:View{
#ObservedObject var daApi : API
var array = [1,2,3]
var body: some View{
ForEach(0..<array.count){ number in
Text("\(number)")
}
if daApi.viewPresent == true {
Text("Swag")
}
}
}
class API:ObservableObject{
#Published var viewPresent:Bool = false
func apicall(){
viewPresent = true
}
}
Also, in your apiCall, you set viewPresent to false and then true immediately again, so I removed one of those.
In Swift, generally variable names start with a lowercase letter -- Api should probably be changed to api.

SwiftUI Using ObservableObject in View on New Sheet

I am struggling with figuring out how to use a value assigned to a variable in an ObservableObject class in another view on another sheet. I see that it gets updated, but when I access it in the new view on the new sheet it is reset to the initialized value. How do I get it to retain the new value so I can use it in a new view on a new sheet?
ContentData.swift
import SwiftUI
import Combine
class ContentData: ObservableObject {
#Published var text: String = "Yes"
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#ObservedObject var contentData = ContentData()
#State private var inputText: String = ""
#State private var showNewView: Bool = false
var body: some View {
VStack {
TextField("Text", text: $inputText, onCommit: {
self.assignText()
})
Button(action: {
self.showNewView = true
}) {
Text("Go To New View")
}
.sheet(isPresented: $showNewView) {
NewView(contentData: ContentData())
}
}
}
func assignText() {
print(contentData.text)
contentData.text = inputText
print(contentData.text)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(contentData: ContentData())
}
}
NewView.swift
import SwiftUI
struct NewView: View {
#ObservedObject var contentData = ContentData()
var body: some View {
VStack {
Text(contentData.text)
}
}
}
struct NewView_Previews: PreviewProvider {
static var previews: some View {
NewView(contentData: ContentData())
}
}
I have tried many, many different methods I have seen from other examples. I tried doing it with #EnviromentObject but could not get that to work either. I also tried a different version of the NewView.swift where I initialized the value with:
init(contentData: ContentData) {
self.contentData = contentData
self._newText = State<String>(initialValue: contentData.text)
}
I think I am close, but I do not see what I am missing. Any help would be very much appreciated. Thanks.
#ObservedObject var contentData = ContentData()
ContentData() in the above line creates a new instance of the class ContentData.
You should pass the same instance from ContentView to NewView to retain the values. Like,
.sheet(isPresented: $showNewView) {
NewView(contentData: self.contentData)
}
Stop creating new instance of ContentData in NewView and add the ability to inject ContentData from outside,
struct NewView: View {
#ObservedObject var contentData: ContentData
...
}

Resources