I think it's a simple problem but a head scratcher.
I'm getting a ....has no initializers error when there actually are initializers.
Here is my code:
(Edit: I've updated the PatientData class to include all the additional variables. I didn't think they made a difference in figuring out the problem, so for the sake of brevity I left them out.)
Data Structures
class PatientData: Identifiable, ObservableObject
{
let id = UUID()
#Published var patientName: String = "Name"
#Published var patientAge: String = "Age"
#Published var patientDOB: String = "DOB"
#Published var patientPhone: String = "Phone"
#Published var patientAddress: PatientAddress
struct PatientAddress
{
var patientStreetAddress: String = "Street"
var patientCity: String = "City"
var patientState: String = "State"
var patientZip: String = "Zip"
init(patientStreetAddress: String, patientCity: String, patientState: String, patientZip: String)
{
self.patientStreetAddress = patientStreetAddress
self.patientCity = patientCity
self.patientState = patientState
self.patientZip = patientZip
}
}
#Published var facilityName: String = "Facility"
#Published var facilityRoom: String = "Room Number"
#Published var facilityFloor: String = "Floor"
#Published var facilityPhoneNumber: String = "Phone Number"
init(patientName: String, patientAge: String, patientDOB: String, patientPhone: String, patientAddress: PatientAddress, facilityName: String, facilityRoom: String, facilityFloor: String, facilityPhoneNumber: String)
{
self.patientName = patientName
self.patientAge = patientAge
self.patientDOB = patientDOB
self.patientPhone = patientPhone
self.patientAddress = patientAddress
self.facilityName = facilityName
self.facilityRoom = facilityRoom
self.facilityFloor = facilityFloor
self.facilityPhoneNumber = facilityPhoneNumber
}
init() {}
}
Content View
struct ContentView: View
{
#ObservedObject var patient = PatientData()
...
}
Note that:
PatientData()
is an equivalent of:
PatientData.init()
Which means that if you want to create your PatientData this way:
#ObservedObject var patient = PatientData()
you need to provide a matching init method (it can be empty as all your #Published properties have already a default value):
init() { }
EDIT
Looking at your full code, it seems like one of your properties has no initial value:
#Published var patientAddress: PatientAddress
An empty init will work only when all your properties are already initialised, which means you need to assign some value to your patientAddress:
#Published var patientAddress = PatientAddress()
However, for this to work, you'd need to add an empty init in PatientAddress as well:
struct PatientAddress {
...
init() {}
}
NOTE
All your code could be much simpler without all these initialisers. If you only use empty init methods you don't have to declare them (they are auto-generated for structs if there are no other initialisers):
class PatientData: Identifiable, ObservableObject {
let id = UUID()
#Published var patientName: String = "Name"
#Published var patientAge: String = "Age"
#Published var patientDOB: String = "DOB"
#Published var patientPhone: String = "Phone"
#Published var patientAddress: PatientAddress = PatientAddress()
#Published var facilityName: String = "Facility"
#Published var facilityRoom: String = "Room Number"
#Published var facilityFloor: String = "Floor"
#Published var facilityPhoneNumber: String = "Phone Number"
}
extension PatientData {
struct PatientAddress {
var patientStreetAddress: String = "Street"
var patientCity: String = "City"
var patientState: String = "State"
var patientZip: String = "Zip"
}
}
Also, Swift can infer types automatically. You don't usually have to declare types explicitly:
#Published var patientPhone = "Phone"
#Published var patientAddress = PatientAddress()
Related
I keep getting an error "Missing argument for parameter #1 in call" when i try to call a ForEach on a list of Workouts, and I don't know what Swift is confused about. I believe it has something to do with which ViewModel it is referencing, but im not sure how to explicitly tell it.
Here's my code of my View:
struct WorkoutProgramView: View {
#ObservedObject var workoutViewModel: WorkoutViewModel
#StateObject var vm: WorkoutListViewModel = .init()
#State var currentWeek: [Date] = []
#State var currentDay: Date = Date()
#State var selection: String? = nil
let screenSize: CGRect = UIScreen.main.bounds
let columns = [GridItem(.adaptive(minimum: 150, maximum: 400))]
var body: some View {
ScrollView{
LazyVGrid(columns: columns){
ForEach(vm.workoutDays, id:\.id) { day in
if isSameDay(date1: currentDay, date2: day.date!){
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width: .infinity).padding().foregroundColor(Color.black).opacity(0.1)
}
I get the error on the ForEach row.
Here's my workoutViewModel and vm:
import SwiftUI
#MainActor
class WorkoutListViewModel: ObservableObject{
#ObservedObject var workoutViewModel = WorkoutViewModel()
#Published var workoutDays: [WorkoutDays] = []
func loadWorkoutData() async throws{
let svc = NestedFirestoreService(workoutViewModel: workoutViewModel)
self.workoutDays = try await svc.retrieveWorkoutDays()
}
}
class WorkoutViewModel: ObservableObject {
let auth = Auth.auth()
let user = Auth.auth().currentUser
#Published var name: String = ""
#Published var date: Date = Date()
#Published var exercise: Double = 0.0
#Published var workoutProgramList: [WorkoutProgramModel] = []
#Published var todaysFoodList: [FoodItem] = []
#Published var workoutProgramSelected: String = ""
#Published var workoutDayData: [WorkoutDays] = []
#Published var workoutDaySelected: String = ""
#Published var exerciseData: [ExerciseModel] = []
#Published var ExerciseSelected: String = ""
#Published var loggedSet: Int = 0
#Published var loggedReps: Int = 0
#Published var loggedWeight: Int = 0
#Published var notes: String = ""
#Published var dayAndExerciseList: [newStrengthModel] = []
}
And here's my WorkoutDays model":
struct WorkoutDays: Encodable, Decodable, Identifiable {
#DocumentID var id: String?
var name: String?
var date: Date?
var description: String?
var timeToCompleteWarmup: Double?
var timeToCompleteStrength: Double?
var timeToCompleteMetcon: Double?
var timeToCompleteAccessories: Double?
var exercises: [ExerciseModel]?
}
struct ExerciseModel: Encodable, Decodable, Hashable, Identifiable {
#DocumentID var id: String?
var workoutDayID: String?
var name: String?
var reps: [Int]?
var percent: [Double]?
var linkToVideo: String?
var isCompleted: Bool?
var description: String?
var timeToComplete: Double?
var type: String?
}
Can anyone help out? Been stuck on this for days and its definitely bugging me lol.
I am trying to read a plist and having it displayed as a List on SwiftUI. It compiles with no errors or warnings, but nothing gets displayed. I am not sure what I am doing wrong or the mistake I am making here. I've tried multiple things, but I still get a blank display.
import Foundation
struct PresidentModel: Decodable{
var Name: String
var Number: Int
var StartDate: String
var EndDate: String
var Nickname: String
var PoliticalParty: String
enum CodingKeys: String, CodingKey{
case Name = "Name"
case Number = "Number"
case StartDate = "Start Date"
case EndDate = "End Date"
case Nickname = "Nickname"
case PoliticalParty = "Political Party"
}
}
class PresidentViewModel: ObservableObject{
#Published var PresArray: [PresidentModel] = []
#Published var name: String = ""
#Published var number: Int = 0
#Published var startDate: String = ""
#Published var endDate: String = ""
#Published var nickname: String = ""
#Published var politicalParty: String = ""
func loadProperityListData(){
guard let path = Bundle.main.path(forResource: "presidents", ofType: "plist"), let xml = FileManager.default.contents(atPath: path) else {
fatalError("Unable to access property list states.plist")
}
do{
PresArray = try PropertyListDecoder().decode([PresidentModel].self, from: xml)
name = PresArray[0].Name
number = PresArray[0].Number
startDate = PresArray[0].StartDate
endDate = PresArray[0].EndDate
nickname = PresArray[0].Nickname
politicalParty = PresArray[0].PoliticalParty
}
catch {
fatalError("Unable to decode property list states.plist")
}
}//func
}//class
Where the plist will be displayed as a List :
import SwiftUI
struct ContentView: View {
let presidents = PresidentViewModel()
var body: some View {
List(presidents.PresArray.indices, id: \.self){ president in
Text(presidents.PresArray[].Name)
}
}//Body
}//View
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
First of all please conform to the naming convention and declare the struct member names with starting lowercase letters and add an id property
struct PresidentModel: Decodable, Identifiable {
let id = UUID()
let name: String
let number: Int
let startDate: String
let endDate: String
let nickname: String
let politicalParty: String
private enum CodingKeys: String, CodingKey {
case name = "Name"
case number = "Number"
case startDate = "Start Date"
case endDate = "End Date"
case nickname = "Nickname"
case politicalParty = "Political Party"
}
}
The main problem is that you don't load the data, a good place is the init method of the observable class. The properties are not needed because the array contains all data
class PresidentViewModel: ObservableObject{
#Published var presArray: [PresidentModel] = []
init() {
loadProperityListData()
}
func loadProperityListData(){
guard let url = Bundle.main.url(forResource: "presidents", withExtension: "plist"),
let data = try? Data(contentsOf: url) else {
fatalError("Unable to access property list states.plist")
}
do {
presArray = try PropertyListDecoder().decode([PresidentModel].self, from: data)
} catch {
print(error)
presArray = []
}
}//func
}//class
The second main problem is that PresidentViewModel is not declared as #StateObject. And due to the added id property you get rid of dealing with indices and specifying id: \.self
import SwiftUI
struct ContentView: View {
#StateObject var model = PresidentViewModel()
var body: some View {
List(model.presArray) { president in
Text(president.name)
}
}//Body
}//View
I've been trying to save a UserData object so that content is "permanent". All of the methods online show classes with primitive types like String, Int, Double, etc. I am aware that you can store Data types but I couldn't find much
This is the class I want to save to disk. It errors out with : "Type 'UserData' does not conform to protocol 'Decodable'" and "Type 'UserData' does not conform to protocol 'Encodable'"
Obviously, skillBag is causing the issue; but I can't find any resources on how to conform Skill to Codable.
class UserData : ObservableObject, Codable{
private var userName:String;
#Published private var skillBag:[Skill] = [];
init(Username user:String, skillBag skills:[Skill]) {
userName = user;
skillBag = skills;
}
}
Skill Class
class Skill : Identifiable, ObservableObject, Equatable{
#Published var skillName:String = "";
#Published var level:Int = 1;
#Published var prestiege:Int = 0;
var curXP:Double = 0.0;
var maxXP:Double = 50.0;
#Published var skillDesc:String;
#Published public var skillColor:Color = Color.purple;
#Published public var buttons: [SkillButton] = [];
#Published private var prestCap:Int;
//EXTRA HIDDEN CODE
}
For Reference, here is the SkillButton Class
class SkillButton : Identifiable, ObservableObject, Equatable{
#Published public var xp:Double = 0;
#Published private var buttonName:String = "";
#Published private var uses:Int = 0;
#Published public var buttonColor:Color = Color.pink;
#Published private var description:String;
enum CodingKeys: String, CodingKey {
case buttonName, xp, uses, buttonColor, description
}
init(ButtonName buttonName:String, XpGained xp:Double, Uses uses:Int = 0, Description desc:String = "") {
self.xp = xp;
self.buttonName = buttonName;
self.uses = uses;
self.description = desc;
}
}
I know that each of these classes need to conform to Codable, but I just couldn't find a resource to make these conform to Codable.
Other than not conforming the Skill and SkillButton classes to Codable, the underlying problem you have is down to the #published property wrapper, as this stops the auto-synthesis of Codable conformance.
Rather than writing out the solution, can I refer you to this Hacking with Swift link: https://www.hackingwithswift.com/books/ios-swiftui/adding-codable-conformance-for-published-properties
You will also have to conform Color to Codable yourself as (unless it's changed in recent updates?) it isn't Codable out of the box.
So as of now, I have been able to use Environment objects for relatively simple things, such as User Profile and User Goals, where we only have one set of values for these Environment objects.
However, I am fairly confused on how I could implement an Array of Environment Objects. For example I want to create a Environment Objects called AddedFoods, which has 4 attributes (takes 4 paramaters):
name
totalCals
totalProtein
totalFat
totalCarns
And I want this environment to be able to take a large quantity of these objects, so obviously it must be some sort of array, but I have no clue how to implement this in Swift as I am relatively new. Any help would be greatly appreciated!
UPDATE: This is what I have for my other environment objects which aren't an array:
import Foundation
class UserInfoModel: ObservableObject {
struct UserInfo: Identifiable {
var id = UUID()
var firstName: String
var height: Double
var weight: Double
var gender: String
var age: Double
var activityLevel: String
var BMR: Double
}
struct DailyCalorieGoals: Identifiable{
var id = UUID()
var calorieGoal: Double
var fatGoal: Double
var proteinGoal: Double
var carbGoal: Double
}
struct CurrentCalorieProgress: Identifiable{
var id = UUID()
var calorieProgress: Int
var fatProgress: Int
var carbProgress: Int
}
#Published var personUserInfo = UserInfo.init(firstName: "", height: 0, weight: 0, gender: "", age: 0, activityLevel: "", BMR: 0)
#Published var personDailyCalorieGoals = DailyCalorieGoals.init(calorieGoal: 2400, fatGoal: 40, proteinGoal: 0, carbGoal: 0)
#Published var personCurrentCalorieProgress = CurrentCalorieProgress.init(calorieProgress: 0, fatProgress: 0, carbProgress: 0)
}
.environmentObject takes in ObservableObject as parameter, so what you could do is create a variable in your observable class that holds array of your AddedFoods type objects. You don't need Array of EnvironmentObjects.
import SwiftUI
struct AddedFoods:Identifiable{
var name : String = ""
var totalCals:Int = 0
var id = UUID().uuidString
//Your other properties
}
class UserInfoModel: ObservableObject,Identifiable {
#Published var foods : [AddedFoods]?
var id = UUID().uuidString
init() {
dummyData()
}
func dummyData() {
var obj:[AddedFoods] = []
obj.append(AddedFoods(name: "foo", totalCals: 2))
obj.append(AddedFoods(name: "foo1", totalCals: 2))
obj.append(AddedFoods(name: "foo2", totalCals: 2))
obj.append(AddedFoods(name: "foo3", totalCals: 2))
foods = obj
}
}
struct myView:View{
#EnvironmentObject var getFood:UserInfoModel
var unwrappedFoods:[AddedFoods]{
getFood.foods ?? []
}
var body: some View{
ForEach(unwrappedFoods) {obj in
Text(obj.name)
}
}
}
#Main
import SwiftUI
#main
struct WaveViewApp: App {
#StateObject var model : UserInfoModel
init() {
_model = StateObject(wrappedValue: UserInfoModel())
}
var body: some Scene {
WindowGroup {
myView()
.environmentObject(model)
}
}
}
I'm struggling to do a simple append in SwiftUI. Here's my code:
// This is defined in my custom view
var newClass = Class()
// This is inside a List container (I hid the Button's content because it doesn't matter)
Button(action: {
self.newClass.students.append(Student())
print(self.newClass.students) // This prints an Array with only one Student() instance - the one defined in the struct's init
})
// These are the custom structs used
struct Class: Identifiable {
var id = UUID()
#State var name = ""
#State var students: [Student] = [Student()] // Right here
}
struct Student: Identifiable {
var id = UUID()
#State var name: String = ""
}
I think it might be somehow related to the new #Struct thing, but I'm new to iOS (and Swift) development, so I'm not sure.
Let's modify model a bit...
struct Class: Identifiable {
var id = UUID()
var name = ""
var students: [Student] = [Student()]
}
struct Student: Identifiable {
var id = UUID()
var name: String = ""
}
... and instead of using #State in not intended place (because it is designed to be inside View, instead of model), let's introduce View Model layer as
class ClassViewModel: ObservableObject {
#Published var newClass = Class()
}
and now we can declare related view that behaves as expected
struct ClassView: View {
#ObservedObject var vm = ClassViewModel()
var body: some View {
Button("Add Student") {
self.vm.newClass.students.append(Student())
print(self.vm.newClass.students)
}
}
}
Output:
Test[4298:344875] [Agent] Received display message [Test.Student(id:
D1410829-F039-4D15-8440-69DEF0D55A26, name: ""), Test.Student(id:
50D45CC7-8144-49CC-88BE-598C890F2D4D, name: "")]