Give #State private var a variable Array as initial value - arrays

I have this #State private var which is an Array of strings and changes values when buttons are tapped. But as an initial value, I want it to take an array of strings that is variable.
#ObservedObject var News = getNews()
#State private var newssitearray : Array<String>
init() {
_newssitearray = State(initialValue: News.data.map {$0.newsSite})
}
What I did above gives the error:
self was used before all stored properties are initialized.

Do everything in init, like
#ObservedObject var News: YourTypeOfNewsContainer // << declare only
#State private var newssitearray : Array<String>
init() {
let news = getNews() // << assuming this is synchronous
// use local var for both properties initialization
self.News = news
self._newssitearray = State(initialValue: news.data.map {$0.newsSite})
}
Important: if you getNews is asynchronous, then it cannot be used either in property initialisation or init itself - think to do this in .onAppear

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

How to create subarrays without duplication Swift

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

Object array cannot display all variables using SwiftUI?

import UIKit
import SwiftUI
var str = "Hello, playground"
struct FianceItem: Identifiable,Codable {
public var id: UUID = UUID()
public var name: String
public var childs:[FianceItem] = []
}
var root=FianceItem(name: "root")
var childA=FianceItem(name: "childA")
root.childs.append(childA)
var childB=FianceItem(name: "childB")
childA.childs.append(childB)
print(root)
struct is a value type. When you assign it to another variable, its value is copied.
So order matters in this case.
This line is creating a copy of childA and putting it into the array of root.
root.childs.append(childA)
When you edit the original, the copy does not have childB.
Either reorder the code like Raja Kishan's answer or use class instead of struct.
Change your sequence
let contentView = DeveloperView()
var root=FianceItem(name: "root")
var childA=FianceItem(name: "childA")
var childB=FianceItem(name: "childB")
childA.childs.append(childB)
root.childs.append(childA)
print(root)

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.

Resources