SwiftUI Sharing model data between views - swift

I have such a structure, this structure seems wrong to me as the approach I want to ask you. So when I want to use 2 models in 1 view, I have to put it in foreach in one view. This is what I want. Using the data I use in my user's profile on other pages I want. How should I do this? How do you guys do it?
Let me give an example for your better understanding:
I want to show my user's Username data on the Homepage, how should I do this?. In fact, after initializing my model once, I want to use it in other views. What is the right approach.
import SwiftUI
struct ContentView: View {
#StateObject var network = ProfileNetwork()
var body: some View {
TabView{
ProfileView().tabItem { Image(systemName: "house") }
ForEach(self.network.userprofile,id:\.id){a in
ShopView(profile_model: a)
}.tabItem { Image(systemName: "house") }
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class ProfileNetwork : ObservableObject {
#Published var userprofile : [UserPRofile] = [UserPRofile(name: "Test", coin: 1, id: "dsa")]
}
struct ProfileView : View {
#StateObject var network = ProfileNetwork()
var body: some View {
ForEach(self.network.userprofile, id:\.id){ i in
ProfileViewModel(profile_model: i)
}
}
}
struct ProfileViewModel : View {
var profile_model : UserPRofile
var body: some View {
Text(self.profile_model.name)
}
}
struct UserPRofile : Decodable{
var name : String
var coin : Int
var id : String
}
class ShopeNetwork : ObservableObject {
#Published var shop : [ShopStore] = [ShopStore(id: "sda", image: "dasd", price: 100, name: "sda")]
}
struct ShopView : View {
#StateObject var network = ShopeNetwork()
var profile_model : UserPRofile
var body: some View {
ForEach(self.network.shop, id:\.id){ c in
ShopViewModel(shop_model: c, profile_model: profile_model)
}
}
}
struct ShopViewModel : View {
var shop_model : ShopStore
var profile_model : UserPRofile
var body: some View {
Text(profile_model.name)
Text(self.shop_model.name)
}
}
struct ShopStore : Decodable {
var id : String
var image : String
var price : Int
var name : String
}

A possible solution is to create an #EnvironmentObject and inject it at the root level:
class AppState: ObservableObject {
#Published var userProfile: UserPRofile?
}
#main
struct TestApp: App {
#StateObject private var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appState)
}
}
}
struct ProfileView: View {
#EnvironmentObject private var appState: AppState // access as an `#EnvironmentObject`
#StateObject var network = ProfileNetwork()
var body: some View {
VStack {
ForEach(self.network.userprofile, id: \.id) { i in
ProfileViewModel(profile_model: i)
}
}
.onAppear {
appState.userProfile = network.userprofile.first // set `userProfile` globally
}
}
}
struct ShopView: View {
#EnvironmentObject private var appState: AppState // use it in any other view
...

Swift 5, iOS 14
Make the class a singleton so it becomes shareable easily.
class ProfileNetwork : ObservableObject {
#Published var userprofile : [UserPRofile] = [UserPRofile(name: "Test", coin: 1, id: "dsa")]
static var shared = ProfileNetwork()
}
And then refer to it with the shared handle.
struct ContentView: View {
#StateObject var network = ProfileNetwork.shared
var body: some View {
....
}
struct ProfileView : View {
#StateObject var network = ProfileNetwork.shared
var body: some View {
....
}

Related

How to observer a property in swift ui

How to observe property value in SwiftUI.
I know some basic publisher and observer patterns. But here is a scenario i am not able to implement.
class ScanedDevice: NSObject, Identifiable {
//some variables
var currentStatusText: String = "Pending"
}
here CurrentStatusText is changed by some other callback method that update the status.
Here there is Model class i am using
class SampleModel: ObservableObject{
#Published var devicesToUpdated : [ScanedDevice] = []
}
swiftui component:
struct ReviewView: View {
#ObservedObject var model: SampleModel
var body: some View {
ForEach(model.devicesToUpdated){ device in
Text(device.currentStatusText)
}
}
}
Here in UI I want to see the real-time status
I tried using publisher inside ScanDevice class but sure can to use it in 2 layer
You can observe your class ScanedDevice, however you need to manually use a objectWillChange.send(),
to action the observable change, as shown in this example code.
class ScanedDevice: NSObject, Identifiable {
var name: String = "some name"
var currentStatusText: String = "Pending"
init(name: String) {
self.name = name
}
}
class SampleViewModel: ObservableObject{
#Published var devicesToUpdated: [ScanedDevice] = []
}
struct ReviewView: View {
#ObservedObject var viewmodel: SampleViewModel
var body: some View {
VStack (spacing: 33) {
ForEach(viewmodel.devicesToUpdated){ device in
HStack {
Text(device.name)
Text(device.currentStatusText).foregroundColor(.red)
}
Button("Change \(device.name)") {
viewmodel.objectWillChange.send() // <--- here
device.currentStatusText = UUID().uuidString
}.buttonStyle(.bordered)
}
}
}
}
struct ContentView: View {
#StateObject var viewmodel = SampleViewModel()
var body: some View {
ReviewView(viewmodel: viewmodel)
.onAppear {
viewmodel.devicesToUpdated = [ScanedDevice(name: "device-1"), ScanedDevice(name: "device-2")]
}
}
}

Cannot find ... in scope

I want the string in text field take the values of name of Player [0]. I try this and I have this error "Cannot find '$PlayerList' in scope".
import SwiftUI
class PlayerList : ObservableObject {
#Published var Players = [
Player(name: ""),
Player(name: ""),
]
init() {
}
}
struct Player : Identifiable {
var name : String
var id = UUID()
}
struct ViewJouer: View {
#StateObject var viewModel: PlayerList = PlayerList()
var body: some View {
VStack {
TextField("Player 1", text: $PlayerList.Players[0].name)
}
}
}
struct ViewJouer_Previews: PreviewProvider {
static var previews: some View {
ViewJouer()
}
}
You don't have the PlayerList variable in the view, you called it viewModel.
Moreover, please use the Swift notation: variables start with lowercase letters.
Here's the corrected code:
import SwiftUI
class PlayerList : ObservableObject {
// Variables start with lowercase letter
#Published var players = [
Player(name: ""),
Player(name: ""),
]
}
struct Player : Identifiable {
var name : String
// If you don't change the id, make it a constant
let id = UUID()
}
struct ViewJouer: View {
// Better make your local variables private
#StateObject private var viewModel = PlayerList()
var body: some View {
// The player list is part of the view model
VStack {
TextField("Player 1", text: $viewModel.players[0].name)
}
}
}
struct ViewJouer_Previews: PreviewProvider {
static var previews: some View {
ViewJouer()
}
}

How to display a random item in a list

I want when the app is launched it shows a random question in my list and I only know how to show all my questions. Please can anyone help?
This is my view
import SwiftUI
struct ViewJouer2: View {
#EnvironmentObject var data : DefisList
var body: some View {
List {
ForEach(data.defis) { Defi in
DefiRow(Defi: Defi)
}
}
}
}
struct ViewJouer2_Previews: PreviewProvider {
static var previews: some View {
ViewJouer2()
.environmentObject(DefisList())
}
}
This is my Data
import SwiftUI
class DefisList : ObservableObject {
#Published var defis = [
Defi(question: "How old are you?"),
Defi(question: "How are you"),
Defi(question: "What is your name?"),
]
}
struct Defi : Identifiable {
var question : String
var id = UUID()
}
If I understood your question correctly, you can use randomElement method to randomize the item you want to show. For example, your code can look like next:
import SwiftUI
struct ViewJouer2: View {
#EnvironmentObject var data : DefisList
var body: some View {
List {
if (!data.defis.isEmpty) {
DefiRow(Defi: data.defis.randomElement()!)
}
}
}
}
As an alternative you can use Int.random:
import SwiftUI
struct ViewJouer2: View {
#EnvironmentObject var data : DefisList
var body: some View {
List {
if (!data.defis.isEmpty) {
DefiRow(Defi: data.defis[Int.random(in: 0..<data.defis.count)])
}
}
}
}

Add a button to refresh a element in a list

I have a list of questions and when the app is launched one randomly pops up. Now I want when a button is clicked it shows another random question in the list. Any ideas?
This is my view
import SwiftUI
struct ViewJouer2: View {
#EnvironmentObject var data : DefisList
var body: some View {
List {
if let randomDefi = data.defis.randomElement() {
DefiRow(Defi: randomDefi)
}
}
}
}
struct ViewJouer2_Previews: PreviewProvider {
static var previews: some View {
ViewJouer2()
.environmentObject(DefisList())
}
}
This is my Data
import SwiftUI
class DefisList : ObservableObject {
#Published var defis = [
Defi(question: "How old are you?"),
Defi(question: "How are you"),
Defi(question: "What is your name?"),
]
}
struct Defi : Identifiable {
var question : String
var id = UUID()
}
Make randomDefi a #Published variable of your ObservedObject. Add a method called randomize() to choose a random one and call that from your button.
import SwiftUI
struct ViewJouer2: View {
#EnvironmentObject var data : DefisList
var body: some View {
List {
DefiRow(defi: data.randomDefi)
}
Button("random") {
data.randomize()
}
}
}
class DefisList : ObservableObject {
#Published var randomDefi = Defi(question: "")
private var defis = [
Defi(question: "How old are you?"),
Defi(question: "How are you?"),
Defi(question: "What is your name?"),
]
init() {
randomize()
}
func randomize() {
randomDefi = defis.randomElement()!
}
}
struct Defi : Identifiable {
var question : String
var id = UUID()
}
struct DefiRow: View {
let defi: Defi
var body: some View {
Text(defi.question)
}
}

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
}
}
}