My ObservableObject Class Array Variable 'timeStep' Fails To Hold Values - swift

I have two ObservableObject classes: CarLotData and StopWatchManager. What I am trying to do is use my StopWatchManager class to load CarLotData property timeStep--which is a array Double type.
Here is my code for StopWatchManager:
import Foundation
import SwiftUI
class StopWatchManager: ObservableObject {
#ObservedObject var carLotData = CarLotData()
var timer = Timer()
#Published var mode: stopWatchMode = .stopped
#Published var secondsElapsed = 0.0
enum stopWatchMode {
case running
case stopped
case paused
}
func start() {
mode = .running
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
self.secondsElapsed = self.secondsElapsed + 0.1
}
}
func stop() {
timer.invalidate()
if carLotData.timeStep.count < 7 {
carLotData.timeStep.append(secondsElapsed)
print(carLotData.timeStep)
mode = .stopped
}
}
}
Here is my code for CarLotData:
import SwiftUI
import Foundation
class CarLotData: ObservableObject {
#Published var carLotData: Int = 0
#Published var carStep: [Double] = []
var id = UUID()
var newUsed: Int = 0
var powerType: Int = 0
var downPayment: Int = 0
var salesmanType: Int = 0
var appointment: Int = 0
var internet: Int = 0
var retail: Int = 0
var carType: Int = 0
var carBrand: Int = 0
var purchaseType: Int = 0
var creditRank: Int = 0
var warranty: Int = 0
var service: Int = 0
var thirdParty: Int = 0
var timeStep: [Double] = []
var salesman: Int = 0
}
Lastly here is my view where I get "timeStep = []" where I want an array of Double values.
import SwiftUI
struct CarLotDataView: View {
#ObservedObject var carLotData = CarLotData()
#ObservedObject var stopWachManager = StopWatchManager()
var body: some View {
List {
Group {
Text("Customer ID: \(carLotData.id)")
Text("Salesman: \(carLotData.salesman)")
Text("New/Used: \(carLotData.newUsed)")
Text("Power Type: \(carLotData.powerType)")
Text("Down Payment: \(carLotData.downPayment)")
Text("Salesman Type: \(carLotData.salesmanType)")
Text("Appointment: \(carLotData.appointment)")
}
Group {
Text("Internet: \(carLotData.internet)")
Text("Retail: \(carLotData.retail)")
Text("Car Type: \(carLotData.carType)")
Text("Car Brand: \(carLotData.carBrand)")
Text("Purchase Type: \(carLotData.purchaseType)")
Text("Credit Rankd: \(carLotData.creditRank)")
Text("Warranty: \(carLotData.warranty)")
Text("Service: \(carLotData.service)")
Text("Third Party: \(carLotData.thirdParty)")
Text("Time Steps: \(carLotData.timeStep)" as String)
}
}
}
}
Everything else works it's just that the values for my timer don't get transferred to my timeStep array. Any insights would be appreciated.

You are trying to access and update CarLotData from StopWatchManager and you are doing, but you are using completely another one in your View, You have to use the same one that you updated!
The trick is always using the same reference you started!
add this line to your CarLotData:
static let shared: CarLotData = CarLotData()
then use this code on StopWatchManager:
let carLotData = CarLotData.shared
Do not use #ObservedObject inside Class, just use let, it will done the job.
At last use this in your View:
#ObservedObject var carLotData = CarLotData.shared
For my personal app, I would use this down code:
#StateObject var carLotData = CarLotData.shared

You are creating CarLotData twice. The right way would be to make sure you access the correct reference.
The other way is to make CarLotData a singleton. But that's only the right approach if you'll be using this data across many views.
class CarLotData: ObservableObject {
static public let shared = CarLotData()
private init() { }
}
Then you'd use it like so. Making sure to never call CarLotData()
Text("Internet: \(CarLotData.shared.internet)")

Related

Binding ObservedObject Init to ContentView

I'm trying to pull more content from server when the user comes to end of the page. I start with loading 5 posts and then 5+5=10 posts. But I cannot bind range so that the view updates with new numbers. Is there a better approach to this or what am I doing wrong here? I tried many things from similiar questions but here is the code:
struct ContentView: View {
#ObservedObject var feedPosts : PostArrayObject
#State var rangeEnd: Int
init(){
let state = State(initialValue: 1)
self._rangeEnd = state
self.feedPosts = PostArrayObject(personalFeed: true, chunkSize: state.projectedValue)
}
var body: some View {
TabView {
NavigationView {
FeedView(posts: feedPosts, title: "Feed", rangeEnd: $rangeEnd)
.toolbar {
....
class PostArrayObject: ObservableObject {
#Published var range: Range<Int> = 0..<1
init(personalFeed: Bool, chunkSize: Binding<Int>) {
DataService.instance.downloadPostsForFollowingInChunks(chunkSize: chunkSize.wrappedValue) { (returnedPosts) in
let shuffledPosts = returnedPosts.shuffled()
self.dataArray.append(contentsOf: shuffledPosts)
self.postsLoaded = true
}
}
struct FeedView: View {
#ObservedObject var posts: PostArrayObject
var title: String
var chunkSize: Int = 5
#Binding var rangeEnd: Int
....
func loadMore(){
rangeEnd = chunkSize
posts.range = 0..<posts.range.upperBound + rangeEnd
}
}

Binding #Binding / #State to the #Publisher to decouple VM and View Layer

I want to decouple my ViewModel and View layer to increase testability of my Views.
Therefore, I want to keep my property states inside view and only init them as I needed.
But I cannot initialize my #Binding or #States with #Published properties. Is there a way to couple them inside init function?
I just add example code below to
instead of
import SwiftUI
class ViewModel: ObservableObject {
#Published var str: String = "a"
#Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
init() {
print("ViewModel initialized")
}
}
struct ContentView: View {
#ObservedObject vM = ViewModel()
var body: some View {
Button(action: { vM.int += 1; print(int) }, label: {
Text("Button")
})
}
}
I want to achieve this without using #ObservedObject inside my view.
import SwiftUI
class ViewModel: ObservableObject {
#Published var str: String = "a"
#Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
init() {
print("ViewModel initialized")
}
}
struct ContentView: View {
#Binding var str: String
#Binding var int: Int
var body: some View {
Button(action: { int += 1; print(int) }, label: {
Text("Button")
})
}
}
extension ContentView {
init(viewModel:ObservedObject<ViewModel> = ObservedObject(wrappedValue: ViewModel())) {
// str: Binding<String> and viewModel.str: Published<String>.publisher
// type so that I cannot bind my bindings to viewModel. I must accomplish
// this by using #ObservedObject but this time my view couples with ViewModel
_str = viewModel.wrappedValue.$str
_int = viewModel.wrappedValue.$int
print("ViewCreated")
}
}
// Testing Init
ContentView(str: Binding<String>, int: Binding<Int>)
// ViewModel Init
ContentView(viewModel: ViewModel)
This way I can't bind them each other, I just want to bind my binding or state properties to published properties.
I have realized that by Binding(get:{}, set{}), I can accomplish that. if anyone want to separate their ViewModel and View layer, they can use this approach:
import SwiftUI
class ViewModel: ObservableObject {
#Published var str: String = "a"
#Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
init() {
print("ViewModel initialized")
}
}
struct ContentView: View {
#Binding var str: String
#Binding var int: Int
var body: some View {
Button(action: { int += 1; print(int) }, label: {
Text("Button")
})
}
}
extension ContentView {
init(viewModel:ViewModel = ViewModel()) {
_str = Binding ( get: { viewModel.str }, set: { viewModel.str = $0 } )
_int = Binding ( get: { viewModel.int }, set: { viewModel.int = $0 } )
print("ViewCreated")
}
}

SwfitUI creating a function that acts like a #published variable

I have a class which has two published variables:
class Controller: ObservableObject {
#Published var myArray: Array<Int>: [1,2,3,4,5]
#Published var currIndex: Int = 0
func currItem() -> Int {
return myArray[curIndex]
}
}
I want my view to subscribe to the function "currItem" instead of the currIndex variable
Is there an elegant way to achieve it?
without subscribing to the function I need to write some boilerplate code:
struct myView: View {
var controller: Controller = Controller()
var body: some View {
Text(controller.myArray[controller.currIndex]) // <-- Replace this with controller.currItem()
}
}
You can do it even much better than that, like this:
import SwiftUI
struct ContentView: View {
#StateObject var controller: Controller = Controller()
var body: some View {
Text(controller.currItem?.description ?? "Error!")
}
}
class Controller: ObservableObject {
#Published var myArray: Array<Int> = [1,2,3,4,5]
#Published var currIndex: Int? = 0
var currItem: Int? {
get {
if let unwrappedIndex: Int = currIndex {
if myArray.indices.contains(unwrappedIndex) {
return myArray[unwrappedIndex]
}
else {
print("Error! there is no such index found!")
return nil
}
}
else {
print("Error! you did not provide a value for currIndex!")
return nil
}
}
}
}

Can I use a generic type in a state variable in SwiftUI?

Is there a way to use a generic type in a state variable in Swift?
I have a view which presents 3 buttons. Each button loads an array of items of either MyGenericObject<TypeA>, MyGenericObject<TypeB> or MyGenericObject<TypeC> type and the aim is to then present a list of objects from the chosen array.
But XCode throws an error when I write #State var objects: [MyGenericObject<T>]?
The error is there even when working with an individual object instead of an array, as in the following example:
struct ObjectView: View {
// I would like the following to be #State var object: MyGenericObject<T> instead...
#State var object: MyGenericObject<TypeA>?
var body: some View {
Text("\(object?.data.name)")
// because I would then like to do something like this, if possible:
if type(of: object) == MyGenericObject<TypeA> {
...show list of item of this type
} else
if type(of: object) == MyGenericObject<TypeB> {
...show list of item of this type
}
else {
...show list of item of type MyGenericObject<TypeC>
}
// or better yet, use a switch
}
How can I use a generic type in a #State variable in SwiftUI? And is there a better way to achieve what I want?
I stumbled across this question and took a look at the gist. It's a little hard to tell if this is exactly what you want, but it seems like it might get you on the right path:
protocol InstrumentType {
var name : String { get set }
var int : Int { get set }
var bool : Bool { get set }
}
class Instrument<T : InstrumentType>: ObservableObject {
#Published var object: T
init(object: T){
self.object = object
}
}
class Trumpet: NSObject, ObservableObject, InstrumentType {
#Published var name: String
#Published var int: Int
#Published var bool: Bool
init(name: String = "newTrumpet", int: Int = 1, bool: Bool = true){
self.name = name
self.int = int
self.bool = bool
}
}
class Guitar: NSObject, ObservableObject, InstrumentType {
#Published var name: String
#Published var int: Int
#Published var bool: Bool
init(name: String = "newGuitar", int: Int = 2, bool: Bool = true){
self.name = name
self.int = int
self.bool = bool
}
}
class Clarinet: NSObject, ObservableObject, InstrumentType {
#Published var name: String
#Published var int: Int
#Published var bool: Bool
init(name: String = "newClarinet", int: Int = 3, bool: Bool = true){
self.name = name
self.int = int
self.bool = bool
}
}
struct ContentView : View {
var body: some View {
_ContentView(instrument: Instrument(object: Trumpet(name: "testTrumpet")))
}
}
//VIEWS
struct _ContentView<T : InstrumentType>: View {
#StateObject var instrument: Instrument<T>
var body: some View {
InstrumentView(instrument: instrument)
.environmentObject(instrument)
.frame(width: 300, height: 100)
}
}
struct InstrumentView<T : InstrumentType>: View {
#ObservedObject var instrument: Instrument<T>
var body: some View {
VStack {
if type(of: instrument) == Instrument<Trumpet>.self {
Text("Instrument is a trumpet")
Text("\(instrument.object.name)")
Text("\(instrument.object.int)")
Text("\(instrument.object.bool ? "true" : "false")")
} else
if type(of: instrument) == Instrument<Guitar>.self {
Text("Instrument is a guitar")
Text("\(instrument.object.name)")
Text("\(instrument.object.int)")
}
else {
Text("Instrument is a clarinet")
}
Button("Update int") {
instrument.object.int += 1
}
}
}
}
Added a protocol InstrumentType that defines the available properties on each instrument -- that allowed me to get rid of the Metadata, since it was all stored on each instrument anyway
Constrained each generic to InstrumentType
I was a little confused by the #StateObject for each type of instrument -- I assumed that maybe what I did was what you were looking for (one generic #StateObject, but perhaps this is where the answer differs from the intent)
I was able to use environmentObject and object.name in the way you were hoping
Added a Button to show that the #Published properties propagate correctly.

How to model data using Combine & SwiftUI

I've been learning Swift & SwiftUI and all has been going well until very recently. I have successfully used #Published properties to keep my data & views in sync. However, I now want to display some data that is a combination of several #Published properties.
The simplified data model class:
import Foundation
final class GameAPI: ObservableObject {
struct PlayerStats: Identifiable, Codable {
var gamesPlayed: Int
var wins: Int
var losses: Int
}
struct Player: Identifiable, Codable {
var id = UUID()
var name: String
var stats: PlayerStats
}
struct Room: Identifiable, Codable {
var id = UUID()
var name: String
var players: [Player]
}
struct ServerStats {
var totalGamesPlayed: Int
var totalPlayers: Int
var totalPlayersOnline: Int
}
#Published var players: [Player] = []
#Published var rooms: [Room] = []
func addPlayer(name: String) {
players.append(Player(
name: name,
stats: PlayerStats(
gamesPlayed: 0,
wins: 0,
losses: 0
)
))
}
func removePlayer(id: UUID) { ... }
func updatePlayerStats(playerId: UUID, stats: PlayerStats) { ... }
func getLeaderboard() -> [Player] {
return players.sorted({ $0.stats.wins > $1.stats.wins }).prefix(10)
}
func getServerStats() -> ServerStats {
return ServerStats(
totalGamesPlayed: ...,
totalPlayers: ...,
totalPlayersOnline: ...,
)
}
}
View:
import SwiftUI
struct LeaderboardTabView: View {
#EnvironmentObject var gameAPI: GameAPI
var body: some View {
VStack {
Text("TOP PLAYERS")
Leaderboard(model: gameAPI.getLeaderboard())
// ^^^ How can I make the view automatically refresh when players are added/removed or any of the player stats change?
}
}
}
How can I wire up my views to leaderboard & server stats data so that the view refreshes whenever the data model changes?