so, having problems updating my String array into the firebase database. I'm able to update my strings, my Int values, but the one string array variable won't change when I have the user pick from the MultiSelectPickerView.
I also check if the user's selection isn't changed then nothing should be updated into the database.
My code:
#Binding var teleClubGenres: String
#State var teleClubGenres2 = [String]()
#State var genres: [String] = ["Action", "Adventure", "Animated", "Children/Family", "Comedy", "Crime" , "Drama", "Historical", "Fantasy", "Horror", "Musical", "Mystery", "Science Fiction", "Romance", "Thriller", "Western"]
NavigationLink(destination: {
MultiSelectPickerView(genres: genres, teleClubGenres: $teleClubGenres2)
.navigationTitle("Change the Genres:")
}, label: {
HStack {
Text("Change the Genres:").font(Font.headline.weight(.semibold))
Spacer()
Image(systemName: "\($teleClubGenres2.count).circle").font(.title2)
}
})
Text("Genres Selected:").font(Font.headline.weight(.semibold))
Text(teleClubGenres2.joined(separator: "\n"))
Button(action: {
model.updateClub(clubCreator: (String(describing: uidxs)), clubName: clubName , clubName2: clubName2, description: des, platform: streamingPlatforms2, teleClubGenres: teleClubGenres2, streamingType: streamingType2, membersLimit: membersLimit2)
}, label: {
Text("Submit change").frame(maxWidth: .infinity, alignment: .center).font(Font.headline.weight(.semibold))
})
func updateClub(clubCreator: String, clubName: String, clubName2: String, description: String , platform: Int, teleClubGenres: [String], streamingType: Int, membersLimit: Int) {
let db = Firestore.firestore()
db.collection("clubs").whereField("clubName",isEqualTo: clubName).getDocuments{
(snap, err) in
if err != nil {
print("Error")
return
}
for i in snap!.documents {
DispatchQueue.main.async {
if teleClubGenres.count != 0 {
i.reference.updateData(["TeleClubGenres": teleClubGenres])
}
}
}
}
}
struct ProfileInfo: Identifiable, Codable {
var id: String = UUID().uuidString
var userID: String
var userName: String
var exists: Bool
}
Related
Here is the desired result (a List from a viewModel derived using functions to generate yearly pay raises and show user's age from year to year). When the user alters their birthdate and hiredate in the ProfileFormView, this List should update the Age and Year group accordingly:
Unfortunately I have not been able to successfully create this view due to errors in attempts to incorporated functions within the viewModel's array.
Here is my viewModel:
class WorkerViewModel:ObservableObject {
#Published var userData: Worker =
Worker(name: "Robert", birthdate: Date(timeIntervalSince1970: 10000), hiredate: Date(timeIntervalSince1970: 30000), department: "HR")
func calcAge(birthdate: String) -> Int {
let dateFormater = DateFormatter()
dateFormater.dateFormat = "MM/dd/yyyy"
let birthdayDate = dateFormater.date(from: birthdate)
let calendar: NSCalendar! = NSCalendar(calendarIdentifier: .gregorian)
let now = Date()
let calcAge = calendar.components(.year, from: birthdayDate!, to: now, options: [])
let age = calcAge.year
return age!
}
func calcYearGroup(hiredate: String) -> Int {
let dateFormater = DateFormatter()
dateFormater.dateFormat = "MM/dd/yyyy"
let newHireDateFormatted = dateFormater.date(from: hiredate)
let calendar: NSCalendar! = NSCalendar(calendarIdentifier: .gregorian)
let now = Date()
let calcYearGroup = calendar.components(.year, from: newHireDateFormatted!, to: now, options: [])
let yearGroup = calcYearGroup.year! + 1
return yearGroup
}
func calcAnnualEarnings(hourlyRate: Double, monthlyHours: Double) -> Double {
return (hourlyRate * monthlyHours) * 12
}
#Published var userDataList: [WorkerInfo] = [
WorkerInfo(age: calcAge(birthdate: $WorkerInfo.birthdate), yearGroup: calcYearGroup(hiredate: $WorkerInfo.hiredate), salary: calcAnnualEarnings(hourlyRate: PayRates.hR[1], monthlyHours: 72), dept: "\($WorkerInfo.department)"),
WorkerInfo(age: calcAge(birthdate: $WorkerInfo.birthdate) + 1, yearGroup: calcYearGroup(hiredate: $WorkerInfo.hiredate) + 1, salary: calcAnnualEarnings(hourlyRate: PayRates.hR[2], monthlyHours: 72), dept: "\($WorkerInfo.department)"),
WorkerInfo(age: calcAge(birthdate: $WorkerInfo.birthdate) + 2, yearGroup: calcYearGroup(hiredate: $WorkerInfo.hiredate) + 2, salary: calcAnnualEarnings(hourlyRate: PayRates.hR[3], monthlyHours: 72), dept: "\($WorkerInfo.department)"),
WorkerInfo(age: calcAge(birthdate: $WorkerInfo.birthdate) + 3, yearGroup: calcYearGroup(hiredate: $WorkerInfo.hiredate) + 3, salary: calcAnnualEarnings(hourlyRate: PayRates.hR[4], monthlyHours: 72), dept: "\($WorkerInfo.department)"),
WorkerInfo(age: calcAge(birthdate: $WorkerInfo.birthdate) + 4, yearGroup: calcYearGroup(hiredate: $WorkerInfo.hiredate) + 4, salary: calcAnnualEarnings(hourlyRate: PayRates.hR[5], monthlyHours: 72), dept: "\($WorkerInfo.department)"),
]
}
Here is my model data:
struct Worker: Codable, Identifiable {
var id = UUID()
var name: String
var birthdate: Date
var hiredate: Date
var department: String
}
struct WorkerInfo: Codable, Identifiable {
var id = UUID()
var age: Int
var yearGroup: Int
var salary: Double
var dept: String
}
struct MockData {
static let sampleUser = WorkerInfo(age: 41, yearGroup: 1, salary: 80000, dept: "HR")
}
struct PayRates {
let hR: [Double] = [0,92,128,150,154,158,162,166,170,172,174,177,180]
let management: [Double] = [0,235,236,238,240,242,244,246,248,250,252,254,256]
}
Here is my Content View:
struct ContentView: View {
#StateObject var vm = WorkerViewModel()
var body: some View {
TabView {
ProfileFormView()
.tabItem {
Image(systemName: "square.and.pencil")
Text("Profile")
}
WorkerView()
.tabItem {
Image(systemName: "house")
Text("Home")
}
.padding()
}.environmentObject(vm)
}
}
Here is my Worker View:
struct WorkerView: View {
#EnvironmentObject var vm: WorkerViewModel
var body: some View {
ZStack {
List($vm.userDataList) { $worker in
WorkerCardView(workerInfo: $worker)
}
}
}
}
Here is my ProfileForm View:
struct ProfileFormView: View {
#EnvironmentObject var vm: WorkerViewModel
#State var depts = ["HR","Management","Marketing","Development"]
var body: some View {
Form {
Section(header: Text("Personal Information")) {
TextField("Name", text: $vm.userData.name)
DatePicker("Birthdate", selection: $vm.userData.birthdate, displayedComponents: .date)
DatePicker("New Hire Date", selection: $vm.userData.hiredate, displayedComponents: .date)
Picker("Department", selection: $vm.userData.department) {
ForEach(depts, id: \.self) {
Text ($0)
}
}
}
}
}
}
And lastly, here is my WorkerCard View:
struct WorkerCardView: View {
#Binding var workerInfo: WorkerInfo
var body: some View {
HStack{
VStack(alignment: .leading) {
Text("Year \(workerInfo.yearGroup)")
.font(.headline)
Label("Age \(workerInfo.age)", systemImage: "person")
.foregroundStyle(.blue)
.font(.subheadline)
}
Spacer()
VStack(alignment: .leading) {
Text("$ \(workerInfo.salary, specifier: "%.0f") salary")
.font(.headline)
Label("\(workerInfo.dept) Dept", systemImage: "building")
.foregroundStyle(.blue)
.font(.subheadline)
}
}
}
}
Thank you in advance for your help!!
//UPDATE 1//
I have converted my functions to computed properties seemingly error free. This also has an added benefit of making the code cleaner which is nice.
I have boiled the problem down now to an initialization issue. Each of my three computed properties trigger compiler errors when placed in the userDataList array:
"Cannot use instance member 'computedProperty' within property initializer; property initializers run before 'self' is available"
//UPDATE 2//
I converted the userDataArray into a computed property, but the resulting compiler error is related to the #Published property wrapper. Naturally, computed properties cannot receive property wrappers, but I need the userDataArray to be visible to the WorkerView in order for the List to iterate over the userDataArray.
Removing the #Published causes this complier error in the WorkerView:
"Cannot assign to property: 'userDataList' is a get-only property"
I have two frames, one which displays the employees and another that adds employees, which then updates the first frame using #Binding. In the ListFrame struct where I display the list of employees, I am attempting to read in the data from Firebase (getData func) and append it to the array. I am successful in reading the data, as the employeesTemp array which I initialize in the getData function gets populated correctly. However, when I try to append the values in the correctly populated employeesTemp array to the employees global array, the global array stays empty, which means my first frame doesn't display anything. I have read other posts that talk about the fact that Firebase's operations are async, but after going through dozens of stackoverflows I have not been able to find a solution. Any help is appreciated.
import SwiftUI
import Firebase
struct Employee: Identifiable {
let id = UUID()
var firstName: String
var lastName: String
var emailID: String
var education: String
// combo drop down box
var department: String
var address: String
var salary: String
}
struct ListFrame: View {
#State var employees = [Employee]()
init() {
self.getData()
}
func getData() {
// get reference to data base
let db = Firestore.firestore()
// temp employee list (gets populated correctly)
var employeesTemp = [Employee]()
// read in the employees from employeelist data base
db.collection("EmployeeList").getDocuments { snapshot, error in
// checking for errors
if error == nil {
// no errors
if let snapshot = snapshot {
// get all documents and create employees
DispatchQueue.main.async {
print("adding employee")
employeesTemp = [Employee]()
employeesTemp = snapshot.documents.map { document in
let new_employee:Employee = Employee(firstName: document["firstName"] as? String ?? "", lastName: document["lastName"] as? String ?? "" , emailID: document["emailID"] as? String ?? "", education: document["education"] as? String ?? "", department: document["department"] as? String ?? "", address: document["address"] as? String ?? "", salary: document["salary"] as? String ?? "")
print(new_employee)
employeesTemp.append(new_employee)
print(employeesTemp)
print("appending to global employees")
employees.append(contentsOf: employeesTemp)
print(employees)
//self.employees = employeesTemp
return new_employee
}
}
}
}
else {
// handle error
print("error")
}
}
}
var body: some View {
NavigationView {
VStack {
Spacer()
List {
ForEach(employees) { employee in
Text(employee.firstName)
}
}
.navigationTitle("List of Employees")
Spacer()
HStack {
Button(action: {
}) {
NavigationLink(destination: AddEmployeeFrame(employees: $employees)) {
Text("Add Employee")
}
}
}
}
}
}
}
struct AddEmployeeFrame: View {
#State private var firstName: String = ""
#State private var lastName: String = ""
#State private var emailID: String = ""
#State private var education: String = ""
// combo drop down box
#State private var department: String = ""
#State private var address: String = ""
#State private var salaryString: String = ""
#Binding var employees: [Employee]
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
func addData(firstName: String, lastName: String, emailID: String, education: String, department: String, address: String, salary: String) {
// get reference to database
let db = Firestore.firestore()
// add a document to collection
db.collection("EmployeeList").addDocument(data: ["firstName": firstName, "lastName": lastName, "emailID": emailID,
"education": education, "department": department, "address": address,
"salary": salary]) { error in
if error == nil {
// no error
//self.getData()
}
else {
// handle error
}
}
}
var body: some View {
NavigationView {
// text fields
VStack(spacing: 50) {
Group {
TextField("First Name", text: $firstName).multilineTextAlignment(.center)
TextField("Last Name", text: $lastName).multilineTextAlignment(.center)
TextField("Email ID", text: $emailID).multilineTextAlignment(.center)
TextField("Education", text: $education).multilineTextAlignment(.center)
}
.navigationTitle("Add Employee")
Menu("Select Department") {
Button("Other") {
department = "Other"
print(department)
}
Button("IT Department") {
department = "IT Department"
print(department)
}
Button("Storage Department") {
department = "Storage Department"
print(department)
}
Button("HR Department") {
department = "HR Department"
print(department)
}
Button("Testing Department") {
department = "Testing Department"
print(department)
}
}
TextField("Address", text: $address).multilineTextAlignment(.center)
TextField("Salary", text: $salaryString).multilineTextAlignment(.center)
// cancel and save buttons
HStack() {
Spacer()
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Cancel")
})
Spacer()
Button(action: {
let new_employee = Employee(firstName: firstName, lastName: lastName, emailID: emailID, education: education, department: department, address: address, salary: salaryString)
// adding data to server
self.addData(firstName: new_employee.firstName, lastName: new_employee.lastName, emailID: new_employee.emailID, education: new_employee.education, department: new_employee.department, address: new_employee.address, salary: new_employee.salary)
employees.append(new_employee)
print(employees)
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Save")
})
Spacer()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ListFrame()
}
}
Model:
enum TaskType: Int, Codable {
case upcoming = 0
case inProgress
case testing
case completed
var title: String {
switch self {
case .upcoming:
return "Upcoming"
case .inProgress:
return "In Progress"
case .testing:
return "Testing"
case .completed:
return "Completed"
}
}
}
struct TasksModel: Encodable, Decodable {
var upcomingArray: [TaskInfo]
var inProgressArray: [TaskInfo]
var testingArray: [TaskInfo]
var completedArray: [TaskInfo]
}
struct TaskInfo: Codable, Equatable, Identifiable {
var id: String
var title: String
var description: String
var taskStatus: TaskType
var taskDate = Date()
}
VM:
class HomeVM: ObservableObject {
#Published var tasksArray: TasksModel
self.tasksArray = TasksModel.init(upcomingArray: [], inProgressArray: [], testingArray: [], completedArray: [])
}
So now that I could locate the record with received taskID and change the taskStatus, I need also to move the record from upcomingArray to inProgressArray. This is what I’m trying:
func inProgressSetTask(taskID: String) {
#StateObject var viewModel = HomeVM()
if let index = viewModel.tasksArray.upcomingArray.firstIndex(where: {$0.id == taskID}) {
// Update task status
viewModel.tasksArray.upcomingArray[index].taskStatus = .inProgress
// Need to remove from upcomingArray and append into inProgressArray
viewModel.tasksArray.upcomingArray.remove(at: index)
var lastIndex = viewModel.tasksArray.inProgressArray.last
viewModel.tasksArray.inProgressArray[lastIndex].append()
viewModel.save()
// End
} else {
…
Updating taskStatus above working fine but remove from one array into another is not.
This code above will repeat for each array after else. Appreciate any help.
you could try the following example code to achieve what you want:
(note, you should have #StateObject var viewModel = HomeVM() outside of the func inProgressSetTask(taskID: String) {...}
or pass it in as a parameter)
EDIT-1: moving the function with all arrays into HomeVM and assuming id are unique.
func inProgressSetTask(taskID: String) {
print("InProgress Set ID# \(taskID)")
// with index, using `firstIndex`
if let index = viewModel.tasksArray.inProgressArray.firstIndex(where: {$0.id == taskID}) {
// do something with the index
viewModel.tasksArray.inProgressArray[index].title = "xxx"
}
// with TaskInfo, using `first`
if var taskInfo = viewModel.tasksArray.inProgressArray.first(where: {$0.id == taskID}) {
// do something with the taskInfo
taskInfo.title = "xxx"
}
}
With all arrays of TaskInfo, use the function setTaskFromAll(...) in HomeVM. For example: viewModel.setTaskFromAll(taskID: "1")
class HomeVM: ObservableObject {
#Published var tasksArray: TasksModel = TasksModel.init(upcomingArray: [], inProgressArray: [], testingArray: [], completedArray: [])
func setTaskFromAll(taskID: String) {
if let index = tasksArray.inProgressArray.firstIndex(where: {$0.id == taskID}) {
tasksArray.inProgressArray[index].title = "inProgress"
} else {
if let index = tasksArray.completedArray.firstIndex(where: {$0.id == taskID}) {
tasksArray.completedArray[index].title = "completed"
} else {
if let index = tasksArray.testingArray.firstIndex(where: {$0.id == taskID}) {
tasksArray.testingArray[index].title = "testing"
} else {
if let index = tasksArray.upcomingArray.firstIndex(where: {$0.id == taskID}) {
tasksArray.upcomingArray[index].title = "upcoming"
}
}
}
}
}
}
EDIT-2:
However, since you already have the "TaskType" of each array in the TaskInfo struct, why not remove TasksModel
and use a single array of TaskInfo in your HomeVM. Like this:
class HomeVM: ObservableObject {
#Published var tasksArray: [TaskInfo] = [
TaskInfo(id: "1", title: "title1", description: "description1", taskStatus: .upcoming),
TaskInfo(id: "2", title: "title2", description: "description2", taskStatus: .inProgress)
// ....
]
func setTask(taskID: String, to taskType: TaskType) {
if let index = tasksArray.firstIndex(where: {$0.id == taskID}) {
tasksArray[index].taskStatus = taskType
}
}
func getAllTaskInfo(_ oftype: TaskType) -> [TaskInfo] {
return tasksArray.filter{$0.taskStatus == oftype}
}
}
and use it like this: viewModel.setTask(taskID: "1", to: .testing) and viewModel.getAllTaskInfo(.inProgress)
EDIT-3: to remove from one array and append to another, using your TasksModel scheme, use this:
class HomeVM: ObservableObject {
#Published var tasksArray: TasksModel = TasksModel(upcomingArray: [
TaskInfo(id: "1", title: "title1", description: "description1", taskStatus: .upcoming),
TaskInfo(id: "2", title: "title2", description: "description2", taskStatus: .upcoming)
], inProgressArray: [
TaskInfo(id: "3", title: "title3", description: "description3", taskStatus: .inProgress),
TaskInfo(id: "4", title: "title4", description: "description4", taskStatus: .inProgress)
], testingArray: [], completedArray: [])
func inProgressSetTask(taskID: String) {
if let index = tasksArray.upcomingArray.firstIndex(where: {$0.id == taskID}) {
// update task status
tasksArray.upcomingArray[index].taskStatus = .inProgress
// get the upcomingArray taskInfo
let taskInfo = tasksArray.upcomingArray[index]
// remove from upcomingArray
tasksArray.upcomingArray.remove(at: index)
// append to inProgressArray
tasksArray.inProgressArray.append(taskInfo)
} else {
// ...
}
}
}
Use it like this: viewModel.inProgressSetTask(taskID: "1")
As you can plainly see, you are much better-off with the EDIT-2, you are repeating/duplicating things in EDIT-3 for no reason. There is no need for separate arrays for the different TaskType, you already have this info in the TaskInfo var taskStatus: TaskType. With EDIT-2, use viewModel.getAllTaskInfo(.inProgress) to get all TaskInfo of a particular type, just like it would be if you used a separate array.
You are attempting to compare a String to a TaskInfo, because the elements of an inProgressArray are of type TaskInfo. What you need to do is drill into the array and get to the .id. That is simple to do. In the .first(where:), you simply pass a closure of $0.id == taskID.
if let index = viewModel.tasksArray.inProgressArray.first(where: { $0.id == taskID } ) {
I'm building a feed, where I want to unite two different structures and sort the feed by date, here's how I've tried. It shows mistakes in View. It writes either "compiler unable to type check in reasonable time", either something else that connects to the ForEach loop.
Maybe you have an idea what can be an issue? Or, if there's other way to build and sort the feed?
EDIT: The following error: "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"
class FeedViewModel: ObservableObject {
#Published var feed: [FeedCommonContent] = []
// download(ed) articles
self.feed.append(Article(id: doc.document.documentID, title: title, pic: pic, time: time.dateValue(), user: user))
// download(ed) tutorials
self.feed.append(Tutorial(id: doc.document.documentID, title: title, steps: [steps], notes: notes, pic: pic, time: time.dateValue(), user: user))
// sort the feed
self.feed.sort { (p1, p2) -> Bool in
return p1.time > p2.time
}
}
protocol FeedCommonContent {
// Define anything in common between objects
var time: Date { get set }
var id: String { get set }
var feedIdentity: FeedIdentity.RawValue { get } // Article, or Tutorial; or - { get set }
}
enum FeedIdentity: String {
// case Article, Tutorial
case Article = "Article"
case Tutorial = "Tutorial"
}
struct Article: Identifiable, FeedCommonContent {
var feedIdentity = FeedIdentity.Article.rawValue
var id: String = UUID().uuidString
var title: String
var pic: String
var time: Date
var user: User
}
struct Tutorial: Identifiable, FeedCommonContent {
var feedIdentity = FeedIdentity.Tutorial.rawValue
var id: String = UUID().uuidString
var titleImage: String
var name: String
var user: User
var inventory: [String]
var steps: [String]
var notes: String
var time: Date
var warnings: String
}
struct FeedView: View {
#StateObject var feedData = FeedViewModel()
var body: some View {
ScrollView {
ForEach(feedData.feed, id: \.self.id) { item in
// also tried type checking:
// if item is Article // or, if let article = item as? Article
if item.feedIdentity == FeedIdentity.Article.rawValue {
NavigationLink(destination: ArticleView(article: item, articleData: feedData)) {
ArticleUnitView(article: item, feedData: feedData)
}
} else if item.feedIdentity == FeedIdentity.Tutorial.rawValue { // if item is Tutorial
NavigationLink(destination: TutorialView(tutorial: item, feedData: feedData)) {
TutorialUnitView(tutorial: item, feedData: feedData)
}
}
}
}
When I did an array only of one of data structures, all worked. (i.e. either var articles: [Article] = [], or var tutorials: [Tutorial] = [] etc..)
this is the (test) code I used to show how to "sort" your feed by date, and display it in a View:
EDIT, using "Joakim Danielson" enum suggestion:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
FeedView()
}
}
}
class FeedViewModel: ObservableObject {
#Published var feed: [FeedCommonContent] = []
// for testing
init() {
self.feed.append(Article(title: "article title", pic: "pic1", time: Date(), user: User()))
self.feed.append(Tutorial(titleImage: "title1", name: "tut1", user: User(), inventory: [], steps: [], notes: "note1", time: Date(), warnings: ""))
sortFeed()
}
func sortFeed() {
feed.sort { $0.time > $1.time }
}
}
protocol FeedCommonContent {
// Define anything in common between objects
var time: Date { get set }
var id: String { get set }
var feedIdentity: FeedIdentity { get } // Article, or Tutorial; or - { get set }
}
enum FeedIdentity: String {
// case Article, Tutorial
case Article
case Tutorial
}
struct User: Identifiable {
var id: String = UUID().uuidString
var name: String = "user"
}
struct Article: Identifiable, FeedCommonContent {
let feedIdentity = FeedIdentity.Article
var id: String = UUID().uuidString
var title: String
var pic: String
var time: Date
var user: User
}
struct Tutorial: Identifiable, FeedCommonContent {
let feedIdentity = FeedIdentity.Tutorial
var id: String = UUID().uuidString
var titleImage: String
var name: String
var user: User
var inventory: [String]
var steps: [String]
var notes: String
var time: Date
var warnings: String
}
struct FeedView: View {
#StateObject var feedData = FeedViewModel()
var body: some View {
ScrollView {
ForEach(feedData.feed, id: \.id) { item in
// switch item.feedIdentity {
// case FeedIdentity.Article:
// NavigationLink(destination: ArticleView(article: item, articleData: feedData)) {
// ArticleUnitView(article: item, feedData: feedData)
// }
// case FeedIdentity.Tutorial:
// NavigationLink(destination: TutorialView(tutorial: item, feedData: feedData)) {
// TutorialUnitView(tutorial: item, feedData: feedData)
// }
// }
// for testing
switch item.feedIdentity {
case FeedIdentity.Article:
NavigationLink(destination: Text(item.feedIdentity.rawValue + " " + item.id)) {
Text(item.feedIdentity.rawValue)
}
case FeedIdentity.Tutorial:
NavigationLink(destination: Text(item.feedIdentity.rawValue + " " + item.id)) {
Text(item.feedIdentity.rawValue)
}
}
}
}
}
Having a slight trouble in saving my decoded JSON data into the struct, not sure what I'm doing wrong here.
What I'm trying to do is call the sneaker database and retrieve the results and display them in a view, i can see the JSON call is working with the print but now stuck on how to get this in a way I can display it into a view.
JSON
{
"count": 1,
"results": [
{
"id": "029ddc1d-a64b-469b-b9df-bd21c84a608e",
"brand": "Jordan",
"colorway": "Deep Ocean/Sail-Cement Grey-Fire Red",
"gender": "men",
"name": "SE Sashiko",
"releaseDate": "2020-12-05",
"retailPrice": 190,
"shoe": "Jordan 4 Retro",
"styleId": "CW0898-400",
"title": "Jordan 4 Retro SE Sashiko",
"year": 2020,
"media": {
"imageUrl": "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=700&h=500&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370",
"smallImageUrl": "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=300&h=214&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370",
"thumbUrl": "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=140&h=100&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370"
}
}
]
}
Model:
struct APIResponse: Decodable {
enum CodingKeys: String, CodingKey {
case shoeResults = "results"
}
let shoeResults: [Shoe]
}
struct Shoe: Decodable {
public var id: String
public var brand: String
public var colorway: String
public var gender: String
public var name: String
public var releaseDate: String
public var retailPrice: Int
public var shoe: String
public var styleId: String
public var title: String
public var year: Int
public var media: mediaData
enum CodingKeys: String, CodingKey {
case id = "id"
case brand = "brand"
case colorway = "colorway"
case gender = "gender"
case name = "name"
case releaseDate = "releaseDate"
case retailPrice = "retailPrice"
case shoe = "shoe"
case styleId = "styleId"
case title = "title"
case year = "year"
case media = "media"
}
}
struct mediaData: Decodable {
var imageUrl: String
var smallImageUrl: String
var thumbUrl: String
}
Shoefetcher class:
public class ShoeFetcher: ObservableObject {
init(){
load()
}
func load() {
let url = URL(string: "https://api.thesneakerdatabase.com/v1/sneakers?limit=10&styleId=cw0898-400")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode(APIResponse.self, from: d)
DispatchQueue.main.async {
print(decodedLists)
}
}else {
print("No Data")
}
}catch DecodingError.keyNotFound(let key, let context) {
Swift.print("could not find key \(key) in JSON: \(context.debugDescription)")
} catch DecodingError.valueNotFound(let type, let context) {
Swift.print("could not find type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.typeMismatch(let type, let context) {
Swift.print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.dataCorrupted(let context) {
Swift.print("data found to be corrupted in JSON: \(context.debugDescription)")
} catch let error as NSError {
NSLog("Error in read(from:ofType:) domain= \(error.domain), description= \(error.localizedDescription)")
}
}.resume()
}
}
output of decodedlists:
APIResponse(shoeResults: [Sneakers.Shoe(id: "029ddc1d-a64b-469b-b9df-bd21c84a608e", brand: "Jordan", colorway: "Deep Ocean/Sail-Cement Grey-Fire Red", gender: "men", name: "SE Sashiko", releaseDate: "2020-12-05", retailPrice: 190, shoe: "Jordan 4 Retro", styleId: "CW0898-400", title: "Jordan 4 Retro SE Sashiko", year: 2020, media: SneakerNews.mediaData(imageUrl: "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=700&h=500&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370", smallImageUrl: "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=300&h=214&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370", thumbUrl: "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=140&h=100&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370"))])
Edit:
updated the shoefetcher class:
public class ShoeFetcher: ObservableObject {
#Published var shoeResults: [Shoe] = [] //added
init(){
load()
}
func load() {
let url = URL(string: "https://api.thesneakerdatabase.com/v1/sneakers?limit=10&styleId=cw0898-400")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode(APIResponse.self, from: d)
DispatchQueue.main.async {
self.shoeResults = decodedLists.shoeResults //added
print(self.shoeResults)
}
}else {
print("No Data")
}
}catch DecodingError.keyNotFound(let key, let context) {
Swift.print("could not find key \(key) in JSON: \(context.debugDescription)")
} catch DecodingError.valueNotFound(let type, let context) {
Swift.print("could not find type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.typeMismatch(let type, let context) {
Swift.print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.dataCorrupted(let context) {
Swift.print("data found to be corrupted in JSON: \(context.debugDescription)")
} catch let error as NSError {
NSLog("Error in read(from:ofType:) domain= \(error.domain), description= \(error.localizedDescription)")
}
}.resume()
}
}
added a ContentView to just see if I can display something off the JSON file.
struct ContentView: View {
#ObservedObject var fetcher = ShoeFetcher()
#State var Shoes: Shoe
var body: some View {
VStack {
Text("Shoes")
Text(Shoes.brand)
}
}
}
errors I'm getting is Missing argument for a parameter 'Shoes' in call in my SneakersApp.swift file
import SwiftUI
#main
struct SneakersApp: App {
var body: some Scene {
WindowGroup {
ContentView() //error here
}
}
}
I have a feeling I need to initialise the JSON variables but cant seem to workout where/how I do that
You've done all of the hard work of getting the data from the API and decoding it into structs.
To display it in a view, you could do something like this:
struct ContentView : View {
#StateObject var fetcher = ShoeFetcher()
var body: some View {
VStack {
if let response = fetcher.apiResponse {
ForEach(response.shoeResults, id: \.id) { shoe in
Text(shoe.name)
Text(shoe.brand)
Text(shoe.title)
Text("\(shoe.year)")
Text("\(shoe.retailPrice)")
}
}
}
}
}
Your current API call is only returning one Shoe in the array, so the ForEach call isn't doing much, but it would certainly be important if you had more results.