I am trying to load data from an ObservableObject class when I present a SwiftUI view but I get the following error code: "Cannot use instance member 'documentID' within property initializer; property initializers run before 'self' is available"
Class with the data querying:
class ItemDataDelegate: ObservableObject {
#Published var serviceContentLoaded = Bool()
#Published var SelectedCategory: String?
#Published var SelectedDocumentID: String?
init(SelectedCategory: String, SelectedDocumentID: String){
self.getSelectedContentData(Category: SelectedCategory, DocumentID: SelectedDocumentID) {
self.getSelectedCompanyData(CUID: ContentData[indexPath.row].CompanyID) {
print("data query completed")
}
}
}
}
Here is the View that I want to present when the data from the previous code is called and completed:
The error I mentioned above is shown on the #StateObject var itemDataDelegate = ItemDataDelegate(SelectedCategory: selectedCategory, SelectedDocumentID: documentID) line of code.
struct DetailView: View {
#Binding var documentID: String
#Binding var selectedCategory: String
#StateObject var itemDataDelegate = ItemDataDelegate(SelectedCategory: selectedCategory, SelectedDocumentID: documentID)
#State private var showSheet = false
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
var image: UIImage
var companyName: String
var cartActive: Bool
var body: some View {
return ZStack {
switch itemDataDelegate.serviceContentLoaded {
case true:
ContentView()
case false:
LoadingView()
}
}
.navigationBarHidden(true)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
}
}
Since you need to use one of the passed-in parameters in the initialization of the #StateObject, you'll have to write a custom init for your View. Using your current code, it would look something like this:
struct DetailView: View {
#Binding var documentID: String
#Binding var selectedCategory: String
#StateObject var itemDataDelegate : ItemDataDelegate
#State private var showSheet = false
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
var image: UIImage
var companyName: String
var cartActive: Bool
init(documentID: Binding<String>, selectedCategory: Binding<String>, image: UIImage, companyName: String, cartActive: Bool) {
_documentID = documentID
_selectedCategory = selectedCategory
self.image = image
self.companyName = companyName
self.cartActive = cartActive
_itemDataDelegate = StateObject(wrappedValue: ItemDataDelegate(SelectedCategory: selectedCategory.wrappedValue, SelectedDocumentID: documentID.wrappedValue))
}
var body: some View {
return ZStack {
switch itemDataDelegate.serviceContentLoaded {
case true:
ContentView()
case false:
LoadingView()
}
}
.navigationBarHidden(true)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
}
}
Related
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
}
}
}
having a problem with updating data in ObservedObject
here's the data MRE:
struct Property: Identifiable, Codable, Hashable {
var id = UUID()
var name : String = ""
var meters : [Meter] = [Meter(name: "electricity"), Meter(name: "water")]
}
struct Meter: Identifiable, Hashable, Codable {
var id = UUID()
var name : String = ""
}
class PropertyData: ObservableObject {
#Published var properties: [Property]
func save() {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(properties) {
UserDefaults.standard.set(encoded, forKey: "PropertyData")
}
}
init() {
if let properties = UserDefaults.standard.data(forKey: "PropertyData") {
let decoder = JSONDecoder()
if let decoded = try? decoder.decode([Property].self, from: properties) {
self.properties = decoded
return
}
}
self.properties = [
Property(name: "Saks /1", meters: [Meter(name: "electricity")]),
Property(name: "Saks /2", meters: [Meter(name: "electricity"), Meter(name: "water")]),
]
}
}
import SwiftUI
struct ContentView: View {
#ObservedObject var data = PropertyData()
var body: some View {
NavigationView {
List {
ForEach(data.properties, id:\.self) {property in
NavigationLink(destination: NavigationLinkView(data: data, property: property)) {
Text(property.name)
}
}
}
}
}
}
struct NavigationLinkView: View {
#ObservedObject var data : PropertyData
var property:Property
var body: some View {
TabView {
MetersView(data: data, property: property)
.tabItem{
VStack {
Image(systemName: "scroll")
Text("Utitity")
}
}
}
}
}
struct MetersView: View {
#ObservedObject var data: PropertyData
#State private var metersPage = 0
#State var property : Property
#State private var addMeters = false
var body: some View {
VStack {
HStack {
Picker("Meters", selection: $metersPage) {
ForEach (0 ..< property.meters.count, id:\.self) {index in
Text(property.meters[index].name)
}
}
.pickerStyle(SegmentedPickerStyle())
Button{
print(property.meters)
addMeters.toggle()
} label: {
Image(systemName: "gear")
}
}.padding()
Spacer()
}.sheet(isPresented: $addMeters){AddMetersView(data: data, property: $property)}
}
}
struct AddMetersView: View {
#ObservedObject var data : PropertyData
#Binding var property : Property
#State var newMeter: String = ""
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Form{
Section {
TextField("Add another meter", text: $newMeter)
.autocapitalization(.none)
Button{
if newMeter != "" {
property.meters.append(Meter(name: newMeter))
data.save()
print(property.meters.count)
} } label: {
Text("Add a meter")
}
}
ForEach(0..<property.meters.count, id:\ .self) {index in
Text(property.meters[index].name)
}
Section() {
Button("That's enough"){
print(property.meters)
presentationMode.wrappedValue.dismiss()}
}
}
}
}
}
I cannot understand, why the meters, that I add on AddMetersView, do update the Meters page, but then just goes away as soon as I go to the ContentView.
Plus, in my app, if I add a property, it does stay, and it persists, but not the Meters
This is similar to your previous question. Change #State var property : Property to
It is all about the connections. You break them in a few places along the way I made some changes and put comments along the line.
import SwiftUI
struct MetersParentView: View {
//Change to StateObject
#StateObject var data = PropertyData()
var body: some View {
NavigationView {
List {
ForEach($data.properties, id:\.id) {$property in
NavigationLink(destination: NavigationLinkView(data: data, property: $property)) {
Text(property.name)
}
}
}
}
}
}
struct NavigationLinkView: View {
#ObservedObject var data : PropertyData
//Make Binding there is no connection without it
#Binding var property : Property
var body: some View {
TabView {
MetersView(data: data, property: $property)
.tabItem{
VStack {
Image(systemName: "scroll")
Text("Utitity")
}
}
}
}
}
struct MetersView: View {
#ObservedObject var data: PropertyData
#State var selectedMeter: Meter = .init()
//Change to Binding
//State is a source of truth, it breaks the connection
#Binding var property : Property
#State private var addMeters = false
var body: some View {
VStack {
HStack {
Picker("Meters", selection: $selectedMeter) {
//Use object not index
ForEach(property.meters, id:\.id) {meter in
//Tag adjustment
Text(meter.name).tag(meter as Meter)
}
}.onAppear(perform: {
selectedMeter = property.meters.first ?? Meter()
})
.pickerStyle(SegmentedPickerStyle())
Button{
print(property.meters)
addMeters.toggle()
} label: {
Image(systemName: "gear")
}
}.padding()
Spacer()
}.sheet(isPresented: $addMeters){
AddMetersView(data: data, property: $property)
}
}
}
struct AddMetersView: View {
#ObservedObject var data : PropertyData
#Binding var property : Property
#State var newMeter: String = ""
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Form{
Section {
TextField("Add another meter", text: $newMeter)
.autocapitalization(.none)
Button{
if newMeter != "" {
print(property.meters.count)
property.meters.append(Meter(name: newMeter))
print(property.meters.count)
data.save()
print(property.meters.count)
}
} label: {
Text("Add a meter")
}
}
//Dont use index
ForEach(property.meters, id:\ .id) {meter in
Text(meter.name)
}
Section() {
Button("That's enough"){
print(property.meters)
presentationMode.wrappedValue.dismiss()}
}
}
}
}
}
struct MetersParentView_Previews: PreviewProvider {
static var previews: some View {
MetersParentView()
}
}
I have an EnvironmentObject that I want to use as a datasource for my button title:
struct ContentView: View {
#State var showDetailsView = false
#EnvironmentObject var storage: Storage
var body: some View {
NavigationView {
ZStack {
Button(action: {
self.doSomethingAsync()
}) {
Text($storage.buttonTitle) // won't compile here
Here is my storage object:
class Storage: ObservableObject {
#Published var buttonTitle: String
#Published var dataObject: DataObject
init(dataObject: DataObject = DataObject(name: "Test")) {
self.dataObject = dataObject
buttonTitle = "Try"
}
}
Text takes in a String not a Binding<String>. Replace the line you pointed out with the following:
Text(storage.buttonTitle)
I am currently having trouble modifying a String value using a TextField. Here is my (simplified) code so far:
class GradeItem: ObservableObject {
#Published var name: String
#Published var scoredPoints: Double
#Published var totalPoints: Double
let isUserCreated: Bool
init(name: String, scoredPoints: Double, totalPoints: Double, isUserCreated: Bool) {
self.name = name
self.scoredPoints = scoredPoints
self.totalPoints = totalPoints
self.isUserCreated = isUserCreated
}
}
var courses: [Course] {
// initialization code...
}
struct GradeCalculatorView: View {
#State var selectedCourseIndex: Int = 0
var body: some View {
VStack {
// allows user to select a course:
ForEach(0 ..< courses.count) { i in
Button(action: {
self.selectedCourseIndex = i
}, label: {
Text(courses[i].name)
})
}
CourseView(course: courses[selectedCourseIndex])
}
}
}
struct CourseView: View {
#ObservedObject var course: Course // passed in from GradeCalculatorView
var body: some View {
VStack(alignment: .leading) {
Text(course.name)
ForEach(course.categories, id: \.name) { category in
GradeCategoryView(category: category)
}
}.padding(.leading).frame(alignment: .leading)
}
}
struct GradeCategoryView: View {
#ObservedObject var category: GradeCategory // passed in from CourseView
var body: some View {
VStack(alignment: HorizontalAlignment.leading) {
HStack {
Text(category.name)
Spacer()
}
ForEach(category.items, id:\.name) { item in
GradeItemRow(item: item)
}
}
}
}
struct GradeItemRow: View {
#ObservedObject var item: GradeItem // passed in from GradeCategoryView
var body: some View {
TextField("Item Name", text: $item.name)
}
}
I cannot seem to modify the GradeItem object's name using the TextField. When the TextField is edited, its text changes temporarily. However, when the GradeItemRow View is reloaded, it displays the GradeItem object's original name, rather than its updated name.
Would somebody please be able to help?
Thanks in advance
UPDATE: As per your requests, I have added more context to this sample code.
I know that this does not work, as when I attempt to modify a GradeItem's name with a TextField, it changes temporarily. However, when I select a different course and then the course I was initially on, the TextField displays the unmodified name value.
The following test works.
class GradeItem: ObservableObject {
#Published var name: String
#Published var scoredPoints: Double
#Published var totalPoints: Double
let isUserCreated: Bool
init(name: String, scoredPoints: Double, totalPoints: Double, isUserCreated: Bool) {
self.name = name
self.scoredPoints = scoredPoints
self.totalPoints = totalPoints
self.isUserCreated = isUserCreated
}
init() {
self.name = "gradeItem" + UUID().uuidString
self.scoredPoints = 0.0
self.totalPoints = 0.0
self.isUserCreated = false
}
}
class Course: ObservableObject {
#Published var name: String
#Published var categories: [GradeCategory]
init(name: String, categories: [GradeCategory]) {
self.name = name
self.categories = categories
}
init() {
self.name = "course_" + UUID().uuidString
self.categories = [GradeCategory]()
self.categories.append(GradeCategory())
}
}
class GradeCategory: ObservableObject {
#Published var name: String
#Published var items: [GradeItem]
init(name: String, items: [GradeItem]) {
self.name = name
self.items = items
}
init() {
self.name = "category_" + UUID().uuidString
self.items = [GradeItem]()
self.items.append(GradeItem())
}
}
struct GradeItemRow: View {
#ObservedObject var item: GradeItem // passed in from GradeCategoryView
var body: some View {
TextField("Item Name", text: $item.name).textFieldStyle(RoundedBorderTextFieldStyle())
}
}
struct GradeCategoryView: View {
#ObservedObject var category: GradeCategory // passed in from CourseView
var body: some View {
VStack(alignment: .leading) {
HStack {
Text(category.name)
Spacer()
}
ForEach(category.items, id: \.name) { item in
GradeItemRow(item: item)
}
}
}
}
struct CourseView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#ObservedObject var course: Course // passed in from ContentView
var body: some View {
VStack(alignment: .leading) {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("done")
})
Spacer()
Text(course.name)
ForEach(course.categories, id: \.name) { category in
GradeCategoryView(category: category)
}
Spacer()
}.padding(.leading).frame(alignment: .leading)
}
}
struct ContentView: View {
#State var courses: [Course] = [Course(), Course()]
#State var selectedCourseIndex: Int = 0
#State var showCourse = false
var body: some View {
VStack {
ForEach(0 ..< courses.count) { i in
Button(action: {
self.selectedCourseIndex = i
self.showCourse = true
}, label: {
Text(self.courses[i].name)
})
}
}.sheet(isPresented: self.$showCourse) {
CourseView(course: self.courses[self.selectedCourseIndex])
}
}
}
I can't implement var on struct View to class ObservableObject in swiftui
struct History: View {
var busId : String
#ObservedObject var historyData = getHistory()
var body: some View {
ZStack{
ScrollView(.vertical, showsIndicators: false){
VStack(alignment: .leading){
ForEach(historyDataSatu.data){i in
Text("Something")
}
}
}
}
}
class getHistory: ObservableObject {
#Published var data = [history]()
init() {
let db = Firestore.firestore().collection("Bus").document(busId)
// An error occurs when I implement busId into document(busId). Please help me.
}
}
}
struct history: Identifiable {
var id : String
var day : String
}
You can initiate your ObservedObject object historyData in init method like. hope it helps you
struct History: View {
var busId : String
#ObservedObject var historyData = getHistory(busId: "")
init() {
self.historyData = getHistory(busId: busId)
}
var body: some View {
ZStack{
ScrollView(.vertical, showsIndicators: false){
VStack(alignment: .leading){
ForEach(historyData.data) { i in
Text("Something")
}
}
}
}
}
}
class getHistory: ObservableObject {
#Published var data = [history]()
init(busId: String) {
let db = Firestore.firestore().collection("Bus").document(busId)
}
}