How to create subarrays without duplication Swift - arrays

I have a Meal structure in my SwiftUI project
struct Meal: Identifiable, Codable, Equatable {
var id = UUID().uuidString
var name: String
var time: String
var type: String
var recommendation: Bool
}
I also have the ContentViewModel class
class ContentViewModel: ObservableObject {
init() {
let allItemsInit = Bundle.main.decode([Meal].self, from: "menu.json")
self.allItems = allItemsInit
self.recomendationItems = allItemsInit.filter {$0.recommendation == true}
}
#Published var allItems: [Meal] = []
#Published var recomendationItems: [Meal] = []
}
Is it a correct approach that I just assign certain elements to the new array of recomendationItems, thereby duplicating them.
recomendationItems - just example, there will be a large number of such subarrays.

You don't need "subarrays" -- your View will get updated whenever allItems changes, so you can use other computed properties to provide the subarrays rather than making them actual separate containers.
For example:
class ContentViewModel: ObservableObject {
init() {
self.allItems = Bundle.main.decode([Meal].self, from: "menu.json")
}
#Published var allItems: [Meal] = []
var recommendedItems: [Meal] {
return allItems.filter {$0.recommendation == true}
}
}

Related

SwiftUI: How to pass arguments to class from view?

I have a class that conforms to ObservableObject, which takes some arguments. When I then use #ObservedObject var someName = className() in my view to access all the functions and data in the class, I get an error saying:
Missing arguments for parameters 'pickedVideo', 'pickedImage', 'retrievedImages', 'retrievedVideos' in call
I am aware that I somehow have to pass the arguments from my view to the class.
But how do I pass variables from my view to my class?
Class:
class DBFunctions : ObservableObject {
init(pickedVideo: [String], pickedImage: [UIImage], retrievedImages: [UIImage], retrievedVideos: [AVPlayer]) {
self.pickedVideo = pickedVideo
self.pickedImage = pickedImage
self.retrievedImages = retrievedImages
self.retrievedVideos = retrievedVideos
}
var pickedVideo : [String]
var pickedImage : [UIImage]
var retrievedImages : [UIImage]
var retrievedVideos : [AVPlayer]
func somefunc() {
}
}
View:
struct ContentView: View {
#ObservedObject var helpFuncs = DBFunctions()
#State var showPicker: Bool = false
#State var pickedImage: [UIImage] = []
#State var retrievedImages = [UIImage]()
#State var player : AVPlayer?
#State var width : CGFloat = 0
#State var retrievedVideos = [AVPlayer]()
#State var pickedVideo: [String] = []
#State var isPaused = false
var body: some View {
VStack{
Button(action: {
helpFuncs.uploadImage()
}) {
Text("Upload Image")
}
}
}
What you are doing is something like an anti-pattern... The ObservableObject should be getting their "truth" from the Model, and not from the view. So, normally speaking, or those values initialize themselves, or you get their from the Model.
To be clear, you should create an object first, from model data, then pass its instance to the View. That's what MVVM is all about.
Fell free to increase your question if something that I answered was not clear enough.
Edit1: If you need to communicate something (like a choice) from the View to the ObservableObject, you should do this via "Intents"... that is normally a func in the ObservableObject class. Those Intents (in my way to architect this is failable), if the Intent goes well, ObservableObject change the Model and Publish their new results to the View, which redraws itself. If the "Intent" not go through, or I launch an Alert (if it's critical) or I simply ignore.
As mentioned, you are not using ObservableObject as it is meant to be used.
Look at this link, it gives you some good examples of how to use ObservableObject and manage data in your app
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
Try something like this example code:
class DBFunctions : ObservableObject {
#Published var pickedVideo : [String] // <-- here #Published
#Published var pickedImage : [UIImage]
#Published var retrievedImages : [UIImage]
#Published var retrievedVideos : [AVPlayer]
init() {
self.pickedVideo = []
self.pickedImage = []
self.retrievedImages = []
self.retrievedVideos = []
}
func uploadImage() { }
}
struct ContentView: View {
#StateObject var helpFuncs = DBFunctions() // <-- here
#State var showPicker: Bool = false
#State var player : AVPlayer?
#State var width : CGFloat = 0
#State var isPaused = false
// do not use these, use the ones in your helpFuncs model directly
// like: helpFuncs.pickedImage = ...
// #State var pickedImage: [UIImage] = []
// #State var retrievedImages = [UIImage]()
// #State var retrievedVideos = [AVPlayer]()
// #State var pickedVideo: [String] = []
var body: some View {
VStack{
Button(action: {
helpFuncs.uploadImage()
}) {
Text("Upload Image")
}
}
.onAppear {
// here do some initialisation of your helpFuncs, for example
}
}
}
With your original code (and question), You can of course pass some initial values, like this:
#ObservedObject var helpFuncs = DBFunctions(pickedVideo: [], pickedImage: [], retrievedImages: [], retrievedVideos: [])

Assign value from array to a variable outside its constructor in Swift

I am trying to get variables in my view structure which will be updated from Firestore every time the user submits a rating so that a new currentRating can be determined. (currentRating is the average of all currentRatings, but I'm not storing individual ratings - I'm just trying to have my currentRating for each of the schools keep being updated after each new rating). I have created the class, FireStoreManager which creates an array which should be reading data to an array. The base of my array is from my identifiable structure RatingSubmission. I then have the first part of my final view structure where I've referenced FirestoreManager() because I'm trying to then be able to reference the different items in the array, Rating Submission, but I keep getting errors.
class FirestoreManager: ObservableObject {
#Published var ratingSubmission = [RatingSubmission]()
var currentRating: CGFloat = 0.0
func fetchRatingSubmission() {
let db = Firestore.firestore()
let docRef = db.collection("RatingInformation").document()
docRef.getDocument{(document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ??
"nil"
print("Document data: \(dataDescription)")
let data = document.data()
self.currentRating = data!["RatingInformation"]! as! CGFloat
print("after update: \(self.currentRating)")
}
}
}
init() {
fetchRatingSubmission()
}
}
struct RatingSubmission: Identifiable {
var id: String = UUID().uuidString
var location: String
var currentRating: Float
var usualRating: Float
var totalRatings: Float
}
struct NewRatingView: View {
var model = FirestoreManager()
var schools = ["North Avenue", "West Village", "Brittain"]
#State private var location = "West Village"
var currentRating: CGFloat {
didSet {
model.fetchRatingSubmission(currentRating)
}
}

Array not being seen in scope Im not sure why SwiftUI

Code:
extension Array {
// Total Together An Array
func FindTotal(_ arrayName: [Int]) -> Int {
var currentValue: Int = 0
for i in 0...Int(arrayName.count - 1) {
currentValue = currentValue + Int(arrayName[i])
}
return currentValue
}
// Number Grabber for Calculating the values
func calcItemsD(_ TargetArray: [String]) {
var placeholder: String? = nil
for i in 0...Int(TargetArray.count - 1) {
placeholder = String((TargetArray[i]).character(at: 0)!)
if (placeholder == "1") {
dealerNums.append("")
}
}
}
}
class DeckSetup : ObservableObject {
#Published public var deckOCards: [String] = []
#Published public var yourhand: [String] = []
#Published public var dealerHand: [String] = []
#Published public var dealerNums: [Int] = [7, 2]
#Published public var playerNums: [Int] = []
}
The dealerNums.append("") is throwing the error of out of scope and I am not sure why Heres the all the code that should be relevant.
dealerNums is an array encapsulated in your DeckSetup class. You can't access that from an Array extension.
What you can do, is pass in dealerNums into the function, like so:
func calcItemsD(_ targetArray: [String], dealerNums: inout [Int]) {
var placeholder: String? = nil
for i in 0 ..< targetArray.count {
placeholder = String(targetArray[i].first!)
if placeholder == "1" {
dealerNums.append("")
}
}
}
And called like so from inside your DeckSetup class:
calcItemsD(["1", "K", "A"], dealerNums: &dealerNums)
dealerNums is marked inout, since you are mutating it within the function.
I cleaned up the function a tiny bit, but I don't know if you have more to it. For example, these are more things you could change to improve it:
Iterate with for target in targetArray instead of using the index.
placeholder is not needed to be stored outside the loop as it isn't used. You can have a local let placeholder = ... if needed.
Don't force unwrap (!) the first character. Provide a suitable alternative or fallback.

Using an array within a view model

I have an array within a view model. I cannot work out how to initialise it with dummy data so that I can access any element of the array later. The number of elements in the array changes each time the view loads so I cannot initialise it in the view model with "repeating".
I tried calling a function from .onAppear to append elements to the array, but .onAppear seems to run after my view has loaded so I get an error trying to access the array.
I'm obviously doing this wrong,
My view model is:
class DemoViewModel: ObservableObject {
#Published var array = [0]
}
My view is:
#ObservedObject private var demoViewModel = DemoViewModel()
// This changes every time the view is called from it's parent
var numberOfItemsInArray = 10
var body: some View {
List(0..<numberOfItemsInArray) { index in
Text("Hello, World! - \(index)")
// demoViewModel.array[index] = 1
// causes an error
}
}
You can pass the numberOfItemsInArray variable to DemoViewModel in init, so you can use Array(repeating:count:):
class DemoViewModel: ObservableObject {
#Published var array: [Int]
init(numberOfItemsInArray: Int) {
array = Array(repeating: 0, count: numberOfItemsInArray)
}
}
and initialise DemoViewModel like this:
struct ContentView: View {
#ObservedObject private var demoViewModel: DemoViewModel
init(numberOfItemsInArray: Int = 10) {
demoViewModel = DemoViewModel(numberOfItemsInArray: numberOfItemsInArray)
}
var body: some View {
List(0 ..< demoViewModel.array.count) { index in
Text("Hello, World! - \(index)")
}
}
}
Also, consider using #StateObject instead of #ObservedObject if you're using SwiftUI 2.

How to handle data and data models in SwiftUI

I am having trouble with the structure of my data for creating a List in SwiftUI. Before SwiftUI I created my data models and held this data in separated files. For example,
In my UserModel.swift :
import SwiftUI
struct UserModel: Equateable {
var name: String
var items = [ItemModel]()
init(name: String, items: [ItemModel]? = nil) {
self.name = name
if let items = items {
self.items = items
}
}
}
In my ItemModel.swift :
import SwiftUI
struct ItemModel: Equatable {
var itemName: String
var price: Double
init(itemName: String, price: Double) {
self.itemName = itemName
self.price = price
}
}
Then I had a separate class called Data that held this data.
In my Data.swift :
import Foundation
class Data {
static var userModels = [UserModel]()
static var itemModels = [ItemModel]()
}
I would update the data by doing something like this in my views:
let user = UserModel(name: "Bob")
Data.userModels.append(user)
I'm having trouble populating a List from this framework in SwiftUI. I tried to make both of my model classes conform to Identifiable but that doesn't seem to help in updating the list. This is what I'm doing so far:
Updated UserModel :
struct UserModel: Identifiable {
var id = UUID()
var name: String
var items = [ItemModel]()
init(id: UUID, name: String, items: [ItemModel]? = nil) {
self.id = id
self.name = name
if let items = items {
self.items = items
}
}
}
Updated ItemModel:
struct ItemModel: Identifiable {
var id = UUID()
var itemName: String
var price: Double
init(id: UUID, itemName: String, price: Double) {
self.id = id
self.itemName = itemName
self.price = price
}
}
And in my ContentView.swift
List (Data.userModels) { user in
Text("\(user.name)")
}
But this does not update the List when I am running the app. (Btw I take input from TextField, append it to Data.userModels, and try to populate it in the List with no luck.)
Is there a different way I should be structuring my data and data models? Or am I just not using the proper syntax when creating the list? Sorry for the long question. Thanks for the help!

Resources