SwiftUI: Having a plist to display - ios

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

Related

Firebase is not saving the data - swiftui

I have a problem that Firebase not saving the data and it shows like that :
enter image description here
First I created a model :
struct Box: Identifiable, Hashable, Codable {
#DocumentID var id: String?
var boxName: String
var boxSize: String
enum CodingKeys: String, CodingKey {
case id
case boxName
case boxSize
}
var dictionary: [String: Any] {
let data = (try? JSONEncoder().encode(self)) ?? Data()
return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:]
}
}
Then I created ViewModel:
class BoxViewModel: ObservableObject {
#Published var box: Box
private var db = Firestore.firestore()
init(box: Box = CashBox(boxName: "", boxSize: "")) {
self.box = box
}
private func addNewBox(_ box: Box){
do {
print("the name \(box.boxName)")
let _ = db.collection("Box")
.addDocument(data: box.dictionary)
}
}
func addBox() {
self.addNewBox(box)
}
func setBoxData(boxName: String, boxSize: String){
self.box.boxName = boxName
self.box.boxSize = boxSize
}
}
Finally here is the view:
struct CashBoxView: View {
#State var boxName = ""
#State var boxSize = ""
#EnvironmentObject var boxViewModel: BoxViewModel
var body: some View {
Text("Enter box name")
TextField("", text: $boxName)
Text("Enter box size")
TextField("", text: $boxSize)
Button( action: {
boxViewModel.setBoxData(boxName: boxName, boxSize: boxSize)
boxViewModel.addBox()
}) {
Text("Done")
}
}
First I thought that the problem is the box is empty but when I tried to print the box name print("the name \(box.boxName)") and printed it
is the problem the the viewModel is #EnvironmentObject ? or what is the problem ?
Thank you,

Working with Dynamic Form and field States in SwiftUI

I'm kind of lost on how to work with states with Dynamic forms.
I surely cannot create states for each Field because I don't know how many fields are there as these Forms are basically built from a JSON Response.
Here is basically what I have right now which is what I'm looking to change.
Initially I created a state for each field back when the forms were not built dynamically and now I'm stuck on how to proceed.
I thought about using Dictionary but I ain't sure how good of a solution is that.
#State var textfieldText: String = ""
#State var SKU: String = ""
#State private var showScanner: Bool = false
var currentForm: FormModel
#State var RecordDate: Date = Date.now
#State var Formresponse: [String: Any] = [:]//This one is set to any because the value can vary from a string to [] to even a whole object
How I'm Rendering my form :
ForEach(currentForm.item, id:\.idf) { it in
if (!it.questionItem.hidden)
{
switch it.questionItem.questionType {
case .dateQuestion :
DateField(title: it.title, currentDate: $RecordDate)
case .choiceQuestion:
Text("choice question")
case .scannerQuestion:
ScannerField(title: it.title, SKU: $SKU, showScanner: $showScanner)
case .textQuestion:
TextQuestionField(title: it.title, email: currentForm.owner, text: $textfieldText)
}
}
}
I'll eventually have to submit this data in a form of dictionary which is why I thought about using a Dict ["fieldID":"FieldInput","fieldId2":"FieldInput2"..]
I think you only need one State and that is for the formResponse. You can pass that as a Binding to each input field view and within that view you can create a custom Binding to get and set the answer to the formResponse. Something like this:
struct FormFieldInputView: View {
#Binding var formResponse: [String: Any]
let field: String
var body: some View {
TextField(field, text: Binding(
get: {
formResponse[field] as? String ?? ""
},
set: { newValue in
formResponse[field] = newValue
})
)
}
}
define enum for the questions type:
enum FormItemType {
case dataQuestion
case choiceQuestion
case scannerQuestion
case textQuestion
}
define the item model for the questions type:
struct FormItemModel : Identifiable {
var type : FormItemType
var itemObject : Any
var userInput : String?
let id : UUID
}
define the form view model:
final class FormModel : ObservableObject {
#Published var items : [FormItemModel] = []
}
and the view :
struct ContentView: View {
#ObservedObject var formViewModel: FormModel
#Binding var currentInput : String
var body: some View {
List {
ForEach(formViewModel.items, id: \.id, content: { item in
switch item.type {
case .dataQuestion:
Text(item.itemObject as? String ?? "")
case .scannerQuestion:
Text("\(item.itemObject as? Int ?? 0 )")
case .choiceQuestion:
if let dic = item.itemObject as? [String:String]{
VStack{
Text(dic["Q"]!)
Text(dic["A1"]!)
Text(dic["A2"]!)
Text(dic["A3"]!)
}
}
case .textQuestion:
VStack{
Text(item.itemObject as? String ?? "")
TextEditor(text: $currentInput)
}
}
})//ForEach
}//List
}
}//View
and here's the dummy values for the form:
items = [FormItemModel(type: .textQuestion, itemObject: "Tell Me About Yourself...", id: UUID()),
FormItemModel(type: .choiceQuestion,
itemObject: ["Q":"How much is 1+1?", "A1":"1", "A2":"2", "A3":"3"],
id: UUID()),
FormItemModel(type: .scannerQuestion, itemObject: 1110111011, id: UUID())
]
and the result:

Firebase #DocumentID property wrapper not working

I'm trying to update certain values in a specific Firebase document, however, whenever the code fires, I get the error:
[FirebaseFirestore][I-FST000001] WriteStream (7fc6c510be38) Stream error: 'Not found: No document to update: projects/htg-inspection/databases/(default)/documents/Projects/365A2F53-CB38-47A6-93C3-7D6DA14038D9
Based on the response it would seem that the Id I get back from my data struct is different from the document ID that I'm trying to update. I assumed that the #DocumentID property wrapper is supposed to solve this, or am I doing/not doing something correctly/incorrectly?
These are the corresponding DocumentIDs and what I'm getting back are some random strings (assumingly from the Swift's UUID initializer)
My Projects Data Struct:
import Foundation
import FirebaseFirestoreSwift
import FirebaseFirestore
import Firebase
struct ProjectsData: Identifiable, Codable{
#DocumentID var id: String? = UUID().uuidString
var title: String
var client: String
var description: String
var inspections: [InspectionsData]
enum CodingKeys: String, CodingKey{
case id
case title
case client
case description
case inspections
}
}
Sub-Collection:
struct InspectionsData: Identifiable, Codable {
var id = UUID()
var category: Category
var trade: String
var inspectionDate: Date
var dueDate: Date
var assigned: Bool
var location: String
var images: [InspectionImages]
var checklists: [ChecklistsData]
var dateToString: String{
let formatter = DateFormatter()
formatter.dateFormat = "MMMM dd, YYYY "
return formatter.string(from: inspectionDate)
}
var dueDateToString: String{
let formatter = DateFormatter()
formatter.dateFormat = "MMMM dd, YYYY "
return formatter.string(from: dueDate)
}
}
struct ChecklistsData: Identifiable, Codable{
var id = UUID()
var question: String
var hasPassed: Bool
var hasFailed: Bool
var isnotApplicable: Bool
}
enum ChecklistCodingKeys: String, CodingKey{
case id
case question
case hasPassed
case hasFailed
case isnotApplicable
}
My View Model:
class ProjectsViewModel: ObservableObject {
#Published var projects = [ProjectsData]()
#Published var inspectionData = [InspectionsData]()
#Published var assignedProjects: [AssignedProjectsModel] = []
#Published var retrievePhotos = [UIImage]()
#Published var inspectionImages = [InspectionImages]()
#Published var showImageViewer = false
#Published var selectedImageID: String = ""
#Published var checklists = [ChecklistsData]()
#Published var hasPassed = false
#Published var hasFailed = false
#Published var isnotApplicable = false
func updateChecklist(_ checklist: ProjectsData, hasPassed: Bool, hasFailed: Bool, isnotApplicable: Bool) {
if let documentId = checklist.id {
db.collection("Projects").document(documentId).updateData(["checklists" : ["hasPassed" : hasPassed, "hasFailed" : hasFailed, "isnotApplicable" : isnotApplicable]]) {
error in
if let error = error {
print(error.localizedDescription)
} else {
print("Document has been updated")
}
}
}
}
}
The View that I'm trying to update the database from:
struct ChecklistComponent: View {
#State var hasPassed: Bool
#State var hasFailed: Bool
#State var isnotApplicable: Bool
#ObservedObject var checklistvm = ProjectsViewModel()
var documentID: ProjectsData
var checklistItem: String = ""
var question: String = ""
var body: some View {
VStack{
HeaderOne(text: question, size: 16)
Spacer()
HStack{
ChecklistButton(text: "Pass", textColor: hasPassed ? "#00AA97" : "#696969", buttonColor: hasPassed ? "#B0EFE8" : "#DBDBDB",
action: {
hasPassed.toggle()
hasFailed = false
isnotApplicable = false
checklistvm.updateChecklist(documentID, hasPassed: hasPassed, hasFailed: hasFailed, isnotApplicable: isnotApplicable)
})
ChecklistButton(text: "Fail", textColor: hasFailed ? "#AA0000" : "#696969", buttonColor: hasFailed ? "#EFB0B0" : "#DBDBDB",
action: {
hasFailed.toggle()
hasPassed = false
isnotApplicable = false
checklistvm.updateChecklist(documentID, hasPassed: hasPassed, hasFailed: hasFailed, isnotApplicable: isnotApplicable)
})
ChecklistButton(text: "N/A", textColor: isnotApplicable ? "#89AA00" : "#696969", buttonColor: isnotApplicable ? "#EDEFB0" : "#DBDBDB",
action: {
isnotApplicable.toggle()
hasFailed = false
hasPassed = false
checklistvm.updateChecklist(documentID, hasPassed: hasPassed, hasFailed: hasFailed, isnotApplicable: isnotApplicable)
})
}
}
.padding()
.frame(width: UIScreen.main.bounds.width-30 ,height: 161)
.background(.white)
.cornerRadius(31)
}
}
Of course this is a lot of code and I can't share the entire project, but hopefully this can give enough context to lend your assistance.
Using #FirestoreQuery wrapper:
import SwiftUI
import Firebase
import FirebaseFirestore
import FirebaseFirestoreSwift
struct HomeView: View {
#State var searchable = ""
#State var navtoView = false
#State var showLogoutdialog = false
#ObservedObject var homevm = HomeViewModel()
var auth = AuthService()
#Environment(\.presentationMode) var presentationmode
#State var firstname = ""
#ObservedObject var projectvm = ProjectsViewModel()
#State var selectedProject: ProjectsData?
#FirestoreQuery(collectionPath: "Projects") var projects: [ProjectsData]
var body: some View {
NavigationView{
ScrollView(.vertical){
Spacer()
.height(70)
HeaderComponent(firstname: users.firstName)
SearchBar(searchText: $searchable)
.padding(.bottom)
VStack {
ForEach(projects, id: \.id) { item in
ProjectFeedCard(projectTitle: item.title, client: item.client)
.onTapGesture {
self.selectedProject = item
navtoView.toggle()
}
}
}
//MARK: NavigationLinks
NavigationLink(destination:ProjectView(inspections: selectedProject?.inspections ?? [], projectTitle: selectedProject?.title ?? "" , client: selectedProject?.client ?? "",description: selectedProject?.description ?? "", documentID: selectedProject ?? ProjectsData(title: "", client: "", description: "", inspections: []), checklists: []) , isActive: $navtoView){
EmptyView()
}
Spacer()
CustomButton(action: {showLogoutdialog.toggle()}, buttonText: "Log Out", buttonColor: .accentColor)
.padding()
.confirmationDialog("Are you sure?", isPresented: $showLogoutdialog, titleVisibility: .visible){
Button("Sign Out", role: .destructive){
homevm.logOutApp()
presentationmode.wrappedValue.dismiss()
}
}
.onAppear{
projectvm.fetchProjects()
}
}
.background(Color(hexadecimal: "#F8F8F8"))
.ignoresSafeArea()
.navigationBarHidden(true)
}
}
}
I managed to solve the issue by changing the id variable to a String and re-did the fetch projects function. So in short I just completely scrapped the #DocumentID wrapper which didn't seem to work. The #FirestoreQuery query wrapper worked after I changed the ID variable in my data model, but I didn't need it again since my View Model did the job
Data Struct:
struct ProjectsData: Identifiable, Codable{
var id: String
var title: String?
var client: String?
var description: String?
var inspections: [InspectionsData]
enum CodingKeys: String, CodingKey{
case id
case title
case client
case description
case inspections
}
}
I refactored the function that gets documents from Firestore to this:
func fetchFireStoreData() {
db.collection("Projects").getDocuments { snapshot, error in
if error == nil {
if let snapshot = snapshot{
//Get all documents
self.projects = snapshot.documents.map({ document in
let id = document.documentID
let title = document["title"] as? String ?? ""
let client = document["client"] as? String ?? ""
let description = document["description"] as? String ?? ""
let inspections = document["inspections"] as? [String : [String : Any]]
var inspectionsArray = [InspectionsData]()
if let inspections = inspections {
for inspection in inspections {
let categoryText = inspection.value["category"] as? String ?? "error"
let category = Category(rawValue: categoryText) ?? .Electrical
let trade = inspection.value["trade"] as? String ?? "error"
let inspectionDate = (inspection.value["inspectionDate"] as? Timestamp)?.dateValue() ?? Date()
let dueDate = inspection.value["dueDate"] as? Date
let assigned = inspection.value["assigned"] as? Bool ?? false
let location = inspection.value["inspection"] as? String ?? ""
let images = inspection.value["images"] as? [String : [String : Any]]
let checklists = inspection.value["checklists"] as? [String : [String : Any]]
var imagesArray = [InspectionImages]()
var checklistsArray = [ChecklistsData]()
if let checklists = checklists{
for list in checklists {
let question = list.value["question"] as? String ?? "error"
let hasPassed = list.value["hasPassed"] as? Bool ?? false
let hasFailed = list.value["hasFailed"] as? Bool ?? false
let isnotApplicable = list.value["isnotApplicable"] as? Bool ?? false
checklistsArray.append(ChecklistsData(question: question, hasPassed: hasPassed, hasFailed: hasFailed, isnotApplicable: isnotApplicable))
}
}
inspectionsArray.append(InspectionsData(category: category, trade: trade, inspectionDate: inspectionDate , dueDate: dueDate ?? Date.now, assigned: assigned, location: location, images: imagesArray, checklists: checklistsArray))
if let images = images {
for image in images {
let url = image.value["url"] as? String ?? "error"
imagesArray.append(InspectionImages(url: url))
}
}
}
}
self.projects.append(ProjectsData(id: id ,title: title, client: client, description: description, inspections: inspectionsArray))
return ProjectsData(id: id, title: title, client: client, description: description, inspections: inspectionsArray )
})
}
}
}
}
I'm now able to get the correct Document ID and now I can use my updateChecklist function.

SwiftUI List with sections using dictionary of array

I have an array of users returned. I want to group them by created date and then display them in SwiftUI List with sections. The section title is the date. After I group the users I end up with a Dictionary of user arrays and the key is the date that group all users that have been created in the same date I want to use the key as a section and the value (user array) as List rows. It seems that List only works with array. Any clean solution to prepare the data for the List so it can display the sections and its content ?
import SwiftUI
import Combine
struct ContentView: View {
#EnvironmentObject var interactor: Interactor
var body: some View {
List(interactor.users) { user in // Error: Cannot convert value of type '[String : [User]]' to expected argument type 'Range<Int>'
}
.onAppear {
interactor.loadUsers()
}
}
}
class Interactor: ObservableObject {
#Published private(set) var users = [String: [User]]()
func loadUsers() {
let allUsers = [User(id: "1", name: "Joe", createdAt: Date()),
User(id: "2", name: "Jak", createdAt: Date())]
users = Dictionary(grouping: allUsers) { user in
let dateFormatted = DateFormatter()
dateFormatted.dateStyle = .short
return dateFormatted.string(from: user.createdAt)
}
}
}
struct User: Identifiable {
let id: String
let name: String
let createdAt: Date
}
You can use sections inside List, and make use of its header parameter to pass Dates. I have already fetched all dates as [String] by using separate function. One thing you need to figure is the way you are going to pass multiple keys to fetch data for different date keys that you will have. May be you can create that first by using [Users] objects and save all keys.
Below is the solution to your issue-:
ContentView-:
//// Created by TUSHAR SHARMA on 06/02/21.
////
//
import SwiftUI
struct ContentView: View {
#EnvironmentObject var interactor: Interactor
var body: some View {
List {
ForEach(getAllDates(),id:\.self) { dates in
Section(header: Text(dates)) {
ForEach(interactor.users[dates] ?? []) { users in
Text(users.name)
}
}
}
}
}
func getAllDates() -> [String]{
let getObjects = interactor.users[Date().stringFromDate()] ?? []
var dateArray : [String] = []
for getData in getObjects{
dateArray.append(getData.createdAt.stringFromDate())
}
let unique = Array(Set(dateArray))
return unique
}
}
extension Date{
func stringFromDate() -> String{
let dateFormatted = DateFormatter()
dateFormatted.dateStyle = .short
return dateFormatted.string(from: self)
}
}
class Interactor: ObservableObject {
#Published private(set) var users = [String: [User]]()
init() {
loadUsers()
}
func loadUsers() {
let allUsers = [User(id: "1", name: "Joe", createdAt: Date()),
User(id: "2", name: "Jak", createdAt: Date())]
users = Dictionary(grouping: allUsers) { user in
user.createdAt.stringFromDate()
}
}
}
struct User: Identifiable {
let id: String
let name: String
let createdAt: Date
}
#main view
// Created by TUSHAR SHARMA on 07/01/21.
//
import SwiftUI
#main
struct WaveViewApp: App {
let interactor = Interactor()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(interactor)
}
}
}

Cannot resolve "Type of expression is ambiguous without more context" error. Can someone check my code?

I'm relatively new to SwiftUI and time to time getting errors and solving them by searching over the internet but this time I could not find any solution to my problem and decided to ask for some help over here, stack overflow. I hope the code below helps you to find my issue.
Both my struct are Identifiable and I actually used ShoppingList struct in the same view to make a List of it with the same technique and it works without an error. But when I try to use ForEach for a variable of ShoppingList struct (which is also a struct and conforms to Identifiable protocol) I get this error "Type of expression is ambiguous without more context"
This is the view that I get my error:
struct ListDetailView: View {
#EnvironmentObject var session: SessionStore
var item: ShoppingList
#State private var isAddNewViewActive: Bool = false
var body: some View {
List {
Section(header: Text("Products")) {
ForEach(self.item.products, id: \.id) { product in <<<--- ERROR LINE
Text(product.name)
}
}
Section(header: Text("")) {
Button(action: { self.isAddNewViewActive.toggle() } ) {
Text("Click to add new product")
}
}
}
.listStyle(GroupedListStyle())
.navigationBarTitle(self.item.name)
.sheet(isPresented: $isAddNewViewActive) {
AddNewItemView(session: self.session, item: self.item, isViewActive: self.$isAddNewViewActive)
}
}
}
These are the structs that are in the code
struct ShoppingList: Identifiable, Equatable {
var id: UUID
var name: String
var coverPhoto: String
var products: [Product]
init(id: UUID = UUID(), name: String, coverPhoto: String = "cart", products: [Product] = [Product]()) {
self.id = id
self.name = name
self.coverPhoto = coverPhoto
self.products = products
}
mutating func addProduct(product: Product) {
products.append(product)
print(products)
}
}
struct Product: Identifiable, Equatable {
var id: UUID
var name: String
var brand: String
var imageURL: String
var links: [Int: String]
var description: String
init(id: UUID = UUID(), name: String, brand: String = "", imageURL: String = "", links: [Int: String] = [:], description: String = "") {
self.id = id
self.name = name
self.brand = brand
self.imageURL = imageURL
self.description = description
self.links = links
}
}
Thanks in advance to all StackOverflow Community.
i properly conform to the Equatable protocol
struct ShoppingList: Identifiable, Equatable {
static func == (lhs: ShoppingList, rhs: ShoppingList) -> Bool {
return lhs.id == rhs.id && rhs.id == lhs.id
}
var id: UUID()
...
init(name: String, brand: String = "", imageURL: String = "", links: [Int: String] = [:], description: String = "") {
...
}
}
no need to init UUID, UUID() will self generate
Apparently, there was an error in a completely unrelated part of the code snippet I posted here (sheet View that pops when I click the button on View that has error) and that was causing the error :/
The code I posted here works just fine.

Resources