Passing data with ObservableObject keyword - swift

How can I move data to other screens with ObservableObject keyword?
I save the data on the first page to the variable I created with the keyword Published, but I cannot access this data on the second page.
User Model
import Combine
struct User: Codable, Identifiable {
var id = UUID()
var name: String
var surName: String
}
UserDataStore
import Combine
class UserDataStore: ObservableObject {
#Published var users: [User] = []
}
ContentView
I get information from the user with TextField objects on the contentView screen. After pressing the button, I add it to the array in the UserDataStore. I redirect to the detail page.
struct ContentView: View {
#State var name: String = ""
#State var surName: String = ""
#State var user = User(name: "", surName: "")
#State var show: Bool = false
#ObservedObject var userStore = UserDataStore()
var body: some View {
NavigationView {
VStack(spacing: 50) {
TextField("isim", text: $name)
TextField("soyÄ°sim", text: $surName)
NavigationLink(
destination: DetailView(),
isActive: $show,
label: {
Button(action: {
self.user.name = name
self.user.surName = surName
self.userStore.users.append(user)
self.show = true
}) {
Text("Kaydet")
}
})
}
}
}
}
DetailView
On the detail page, I try to view the recorded information, but I cannot.
struct DetailView: View {
#ObservedObject var user = UserDataStore()
var body: some View {
ForEach(user.users) { item in
Text("\(item.name)")
}
}
}

Like #"New Dev" explained you're initializing a new instance of UserDataStore therefore your data isn't accessible from the DetailView.
You can use an EnvironmentObject to access the data from ContentView to DetailView.
In order to do this you would have to set the NavigationLinks destination to:
destination: DetailView().environmentObject(userStore)
Then you can access it from the DetailView like this:
#EnvironmentObject var user: UserDataStore

Related

How to bind the Publisher of ObservableObject by SwiftUI

I don't know how to bind Publisher in ObservableObject class on view file.
User
struct User: Identifiable,Decodable{
var id: String?
var email: String
var username: String
}
ViewModel
class UserProfileViewModel: ObservableObject{
#Published var user: User?
//fetch data and bind the user property
}
UserProfile
struct UserProfile: View {
#ObservedObject private var userProfileVM = UserProfileViewModel()
var body: some View {
VStack{
UserForm(name: Binding($userProfileVM.user.username)!,
email: Binding($userProfileVM.user.email)!
) }
}
}
UserForm
struct UserForm: View {
#Binding var name: String
#Binding var email: String
var body: some View {
TextField("name", text: $name)
.keyboardType(.namePhonePad)
TextField("email", text: $email)
}
}
I don't know how to bind userProfileVM.user.username and userProfileVM.user.email in UserProfile on UserForm.
Now I got the errors Value of optional type 'User?' must be unwrapped to refer to member 'email' of wrapped base type 'User' & Chain the optional using '?' to access member 'email' only for non-'nil' base values in UserProfile.
Please how to resolve it.
Generally if you are using ! there is something incorrect. It should be very rare.
You need to check if the #Published var user: User? is not nil.
struct UserProfile: View {
//StateObject is for initializing ObservedObject is for passng around.
#StateObject private var userProfileVM = UserProfileViewModel()
var body: some View {
VStack{
if let userB = Binding($userProfileVM.user){
//Check if there is a user.
UserForm(name: userB.username, email: userB.email)
}else{
//Show/do something if there isn't
ProgressView()
.task{
//Load user somehow...
}
}
}
}
}

SwiftUI: reading data from ObservableObject

I am new to SwiftUI and created a Todo model. I then have a class that represents the container so that I can have an array of Todos and methods on it later on. I made TodoContainer an ObservableObject and then observing it after instantiating it: #ObservedObject var items = TodoContainer().
I am iterating through the list and expecting anytime I click the button, shouldn't it appear in the list since it's creating a new Todo each time and allTodos should have the array of all Todos?
import SwiftUI
struct Todo: Identifiable {
var id = UUID()
var name: String;
var priority: Int;
}
class TodoContainer: ObservableObject {
#Published var allTodos = [Todo]();
}
struct ContentView: View {
#ObservedObject var items = TodoContainer()
var body: some View {
Button("Create New") {
Todo(name: "New one", priority: Int.random(in: 1..<100))
}
List(items.allTodos, id: \.id){item in
Text(item.name)
}
}
}
You need to add the created todo to the view model's allTodos. Xcode may have shown you an error about an unused expression previously.
import SwiftUI
struct Todo: Identifiable {
var id = UUID()
var name: String;
var priority: Int;
}
class TodoContainer: ObservableObject {
#Published var allTodos = [Todo]();
}
struct ContentView: View {
#ObservedObject var items = TodoContainer()
var body: some View {
Button("Create New") {
let todo = Todo(name: "New one", priority: Int.random(in: 1..<100))
items.allTodos.append(todo) // here
}
List(items.allTodos, id: \.id){item in
Text(item.name)
}
}
}

How to bind a #publish datamodel in SwiftUI?

Is there any way to bind a data model in swiftui?
I have coded like below and need to build a struct so that I can use it in multiple views but the problem is to know how to bind a #publish data model in swiftui?
var birds: [PlayerItem] = [PlayerItem(id: UUID(), playershow: false)]
var dogs: [PlayerItem] = [PlayerItem(id: UUID(), playershow: true)]
class Controller: ObservableObject {
#Published var bird = birds
#Published var dog = dogs
}
struct PlayerItem: Hashable {
var id = UUID()
var playerShow: Bool
}
struct ContentView: View {
#EnvironmentObject var control: Controller
var body: some View {
setButton(isOn: $Controller.bird)
}
}
struct setButton: View {
#Binding var isOn: [PlayerItem]
var body: some View {
Button(action: {
self.isOn[0].toggle()
}) {
Text(isOn[0] ? "Off" : "On")
}
}
}
I wrote the following code:
#Binding var isOn: [PlayerItem]
However, it complained the following:
Value of type 'EnvironmentObject<controller>.Wrapper' has no dynamic member 'isOn' using the key path from the root type 'Controller'
try the following code, it shows how to use #Binding and how you have to use playershow
class Controller: ObservableObject {
#Published var bird = [Playeritem(id: UUID(), playershow: false)]
#Published var dog = [Playeritem(id: UUID(), playershow: true)]
}
struct Playeritem: Hashable {
var id = UUID()
var playershow: Bool
}
struct ContentView: View {
#StateObject var control = Controller() // <-- for testing
var body: some View {
setButton(isOn: $control.bird) // <-- here control
}
}
struct setButton: View {
#Binding var isOn: [Playeritem]
var body: some View {
Button(action: {
self.isOn[0].playershow.toggle() // <-- here playershow
}) {
Text(isOn[0].playershow ? "Off" : "On") // <-- here playershow
}
}
}

change a value inside a swiftUI view based in a change made in his child

I have this code:
Main view
import SwiftUI
struct ContentView: View {
#EnvironmentObject var data:Pessoa
var body: some View {
NavigationView{
VStack{
NavigationLink(destination:view2(data: data)){
Text(data.data.firstObject as! String)
}
}
}.environmentObject(data)
}
}
2nd view
import SwiftUI
struct view2: View {
var data:Pessoa
var body: some View {
Button(action: {
self.data.data[0] = "New btn Text"
}){
Text("Edit Btn Texr")
}.environmentObject(data)
}
}
Class Pessoa
class Pessoa:ObservableObject {
var data:NSMutableArray
init() {
self.data = NSMutableArray()
self.data.add("Btn")
}
}
How I can update the main view when I return form the 2nd view.
Yes I need to pass the object or at least, the array.
The main idea is in a structure like:
V1 -> v2 -> V3
if I make a change in some parameter of a class, in the V3, how I can propagate (in the layout) this change to the v2 and v1
Just to get you up and running, you could use the #Published property wrapper and for your example you actually don't need #EnvironmentObject. You can use #ObservedObject instead...
class Pessoa: ObservableObject {
#Published var data: Array<String>
init() {
self.data = Array()
self.data.append("Btn")
}
}
struct view2: View {
var data: Pessoa
var body: some View {
Button(action: {
self.data.data[0] = "New btn Text"
}){
Text("Edit Btn Texr")
}
}
}
struct ContentView: View {
#ObservedObject var data = Pessoa()
var body: some View {
NavigationView{
VStack{
NavigationLink(destination:view2(data: data)){
Text(data.data.first ?? "N/A")
}
}
}
}
}
But you should check the link of Joakim...

ObservableObject has a different instantiation in my class

I have an ObservableObject that stores the current country the user is wanting information on and I want this to be shared across all views and classes. I properly declared it in the scene delegate so there is no issue with that.
import Foundation
class GlobalData: ObservableObject {
#Published var whichCountry: String = "italy"
}
This is my main view of where I call an environment object to get whichCountry. When the users click the button it will open ListOfCountriesView() and pass that EnvironemtnObject through it to update the new country the users want.
import SwiftUI
struct InDepthView: View {
#State var showList = false
#EnvironmentObject var globalData: GlobalData
#ObservedObject var data = getDepthData(globalData: GlobalData())
var body: some View {
VStack(alignment: .leading) {
Button(action: { self.showList.toggle() }) {
VStack(alignment: .leading) {
HStack {
Text("\(self.data.globalDatam.whichCountry.uppercased())")
}
}
}
.sheet(isPresented: $showList) {
ListOfCountriesView().environmentObject(GlobalData())
}
}
}
}
import SwiftUI
struct ListOfCountriesView: View {
#EnvironmentObject var globalData: GlobalData
var body: some View {
ScrollView {
VStack(spacing: 15) {
Text("List of Countries")
.padding(.top, 25)
Button(action: {
self.globalData.whichCountry = "usa"
self.presentationMode.wrappedValue.dismiss()
}) {
VStack {
Text("\(self.globalData.whichCountry)")
.font(.system(size: 25))
Divider()
}
}
}
}
}
}
struct ListOfCountriesView_Previews: PreviewProvider {
static var previews: some View {
ListOfCountriesView().environmentObject(GlobalData())
}
}
When the user changes the country I want this class which is inside my InDepthView.swift file to be updated with the new string. But for some reason, it is still displaying "italy" when it should have changed to "usa" based on what happened in ListOfCountriesView(). So I know that there is two instantiations of GlobalData but I'm not sure how to fix this issue. Any help would be greatly appreciated as I have been spending the past two days trying to fix this issue!
class getDepthData: ObservableObject {
#Published var data : Specific!
#Published var countries : HistoricalSpecific!
var globalDatam: GlobalData
init(globalData: GlobalData) {
globalDatam = globalData
print(globalDatam.whichCountry + " init()")
updateData()
}
func updateData() {
let url = "https://corona.lmao.ninja/v2/countries/" // specific country
let session = URLSession(configuration: .default
session.dataTask(with: URL(string: url+"\(self.globalDatam.whichCountry)")!) { (data, _, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
let json = try! JSONDecoder().decode(Specific.self, from: data!)
DispatchQueue.main.async {
self.data = json
}
}.resume()
}
}
///////
I added this to the code like you mentioned. but recieving an error
import SwiftUI
struct InDepthView: View {
#State var showList = false
#State var pickerSelectedItem = 1
#EnvironmentObject var globalData: GlobalData
#ObservedObject var data: getDepthData
init() {
self.data = getDepthData(globalData: self.globalData)
}
ERRROR : self' used before all stored properties are initialized
You're creating a second GlobalData instance when you call
#ObservedObject var data = getDepthData(globalData: GlobalData())
Edit: Removed example that was passing the environment object in as an argument. That doesn't work and it crashes.
You will need to refactor a bit to either structure your app a bit differently altogether, or you could remove the environment object, and instead initialise GlobalData() in your first view and then just pass it into each subsequent view as an #ObservedObject, rather than as #EnvironmentObject via scene delegate.
The following is pseudocode but I hope clarifies what I mean:
struct ContentView: View {
#ObservedObject var globalData = GlobalData()
var body: some View {
...
NavigationLink("Linky link", destination: SecondView(globalData: globalData, data: getDepthData(globalData: globalData))
}
}
struct SecondView: View {
#ObservedObject var globalData: GlobalData
#ObservedObject var data: getDepthData
init(globalData: GlobalData, data: getDepthData) {
self.globalData = globalData
self.data = getDepthData
}
...
}