How we can read and write to same ObservableObject in SwiftUI?
how we can make StateObject and ObservedObject working on a same data?
You can make it via shared instance, in example:
class TextModel: ObservableObject {
static let shared = TextModel() // << here !!
@Published var stringOfText: String = "No Data!"
}
struct ContentView: View {
@ObservedObject var readStringOfTextView = TextModel.shared // << here !!
// ... other code
}
struct TextView: View {
@StateObject var textModel = TextModel.shared // << here !!
// ... other code
}
Do I have to use an ObservableObject in SwiftUI?
You don't have to use @ObservedObject
to ensure that updates to your model object are updating your view.
If you want to use a struct
as your model object, you can use @State
and your view will be updated correctly whenever your @State
struct
is updated.
There are lots of different property wrappers that you can use to update your SwiftUI views whenever your model object is updated. You can use both value and reference types as your model objects, however, depending on your choice, you have to use different property wrappers.
@State
can only be used on value types and @State
properties can only be updated from inside the view itself (hence they must be private
).
@ObservedObject
(and all other ...Object property wrappers, such as @EnvironmentObject
and @StateObject
) can only be used with classes that conform to ObservableObject
. If you want to be able to update your model objects from both inside and outside your view, you must use an ObservableObject
conformant type with the appropriate property wrapper, you cannot use @State
in this case.
So think about what sources your model objects can be updated from (only from user input captured directly inside your View
or from outside the view as well - such as from other views or from the network), whether you need value or reference semantics and make the appropriate choice for your data model accordingly.
For more information on the differences between @ObservedObject
and @StateObject
, see What is the difference between ObservedObject and StateObject in SwiftUI.
How do you edit an ObservableObject’s properties in SwiftUI from another class?
To make SwiftUI view follow state updates, your controller needs to be ObservableObject
.
SwiftUI view will update when objectWillChange
is triggered - it's done automatically for properties annotated with Published
, but you can trigger it manually too.
Using same publisher of your state, you can sync two observable objects, for example like this:
class Controller: ObservableObject {
let state: State
private var cancellable: AnyCancellable?
init(_ state: State) {
self.state = state
cancellable = state.objectWillChange.sink {
self.objectWillChange.send()
}
}
func changeState() {
state.text = "bar"
}
}
struct ContentView: View {
@StateObject var controller = Controller(State())
SwiftUI - ObservableObject doesn't cause update
A new SocialMealPlan
object currentMealPlan
is being created and initialized every time the view needs to be redrawn/recreated. So one object triggers the update (by assignment to one of the Published vars), but the new updated view refers to its own new freshly initialized copy of currentMealPlan
.
Option 1: Make currentMealPlan a @StateObject
(so one copy representing a state is kept and referred to). ie @StateObject var currentMealplan = SocialMealplan(owner: YKUser.none, mealplan: Mealplan.none)
Option 2: Or keep the @ObservedObject, but create it before and outside th view. But if other views also need to refer to the currentMealPlan
, create one before the View and pass it as an environment variable. ie MealPlanView().environmentObject(currentMealPlan)
and then in the view @EnvironmentObject var currentMealPlan: SocialMealPlan
SwiftUI Change Observed Object From Another View
It is changing the value. The problem is you are instantiating an instance of GlobalValue locally in your struct. It has no life outside of the struct. I you want to use an Observable Object
as a global store like that, you need to create it once and use that instance.
The easiest way to do this is to addstatic let shared = GlobalValue()
in your class, and in your struct use globalValue = GlobalValue.shared
which essentially gives you a singleton. You will have one instance that all the views can read and write to.
How to bind an ObservableObject?
To bind to properties of an observable object, you'd create a property in the view that will hold an instance of the object and bind to it just like you would with any local @State
or @Binding
property:
struct SomeView: View {
@StateObject var gv = GlobalVariables()
var body: some View {
// ...
.sheet(isPresented: $gv.someCondition) {
// ...
}
}
}
(Bear in mind that isPresented:
expects a Binding<Bool>
, so someCondition
has to be a non-optional Bool
)
- Use
@StateObject
ifGlobalVariables
was instantiated and owned by the view - Use
@ObservedObject
- if it's instantiated outside the view and an instance is passed viainit
- Use
@EnvironmentObject
- same as@ObservedObject
, but instance is passed via.environmentObject
How to read property from a struct when being in @ObservableObject
ViewModel
class getJoke: ObservableObject {
@Published var currentDate = Date()
}
View that can change passing data
struct CustomDatePicker: View {
@Binding var currentDate: Date
var body: some View{
VStack {
DatePicker(selection: $currentDate, displayedComponents: .date){
Text("Select your date")
}
.datePickerStyle(.compact)
}
}
}
And put everything together
struct ContentView: View {
@StateObject var vm = getJoke()
var body: some View {
VStack(spacing: 40) {
CustomDatePicker(currentDate: $vm.currentDate)
Button {
print(vm.currentDate)
} label: {
Text("Show selected date")
}
}
}
}
Classes and Observable Object
You create two different instance of Data in your subviews, instead you need to share one, so create it in ContentView
and pass to subviews as below
struct ContentView: View {
@ObservedObject var data = Data()
var body: some View {
VStack {
TextView(data: data)
ButtonView(data: data)
}
}
}
struct TextView: View {
@ObservedObject var data: Data
var body: some View {
Text(data.currentWord)
}
}
struct ButtonView: View {
@ObservedObject var data: Data
var body: some View {
Button(action: {self.data.randomWord()}) {
Text("Random word")
}
}
}
Also, as variant, for such scenario can be used EnvironmentObject
pattern. There are a lot of examples here on SO you can find about environment objects usage - just search by keywords.
SwiftUI - ObservableObject created multiple times
Latest SwiftUI updates have brought solution to this problem. (iOS 14 onwards)
@StateObject
is what we should use instead of @ObservedObject
, but only where that object is created and not everywhere in the sub-views where we are passing the same object.
For eg-
class User: ObservableObject {
var name = "mohit"
}
struct ContentView: View {
@StateObject var user = User()
var body: some View {
VStack {
Text("name: \(user.name)")
NameCount(user: self.user)
}
}
}
struct NameCount: View {
@ObservedObject var user
var body: some View {
Text("count: \(user.name.count)")
}
}
In the above example, only the view responsible (ContentView) for creating that object is annotating the User
object with @StateObject
and all other views (NameCount) that share the object is using @ObservedObject
.
By this approach whenever your parent view(ContentView) is re-created, the User
object will not be re-created and it will persist its @State, while your child views just observing
to the same User
object doesn't have to care about its re-creation.
SwiftUI Using ObservableObject in View on New Sheet
@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
...
}
Related Topics
Swift Textview Align with English and Arabic Languages
Assigning Values to Tuple in for Loop
Cannot Convert Value of Type 'Nsmutablearray' to Expected Argument Type '[Skaction]'
Mapview Shows White Overlay on Interaction
Trouble Calling a Method in an Init
Core Data with Swiftui Mvvm Feedback
Swift: Get The Compile Time Name of Variable (Referencing to a Class)
Uitableview Didselectrow Returns Wrong Row Index Value
How to Make UItabbar Image Rounded in Swift Programmatically
User Already Authenticated in App and Firebase Realtime Database Returns Failed: Permission_Denied
How to Fill Triangle in Swift Using Core Graphics
Need Clarification on Typealias Syntax in Swift
Capturing Still Image with Avfoundation