How to fetch data from nested array from API in SwiftUI? - arrays

this is my Model
public struct Welcome: Decodable{
public let userslist: [Userslist]
}
public struct Userslist: Decodable, Hashable{
public let full_name: String
public let partner_media: [PartnerMedia]
public init( partner_media: [PartnerMedia]) {
self.partner_media = partner_media
}
}
public struct PartnerMedia: Decodable , Hashable{
public var id = UUID()
public let url: String
public init( url: String) {
self.url = url
}
}
This is View Model I follow the MVVM pattern for accessing the data from API.
class PublisherModelVM: ObservableObject {
#Published var datas = [PartnerMedia]()
let url = "APIUrl"
init() {
getData(url: url)
}
func getData(url: String) {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
do {
let results = try JSONDecoder().decode(Welcome.self, from: data)
DispatchQueue.main.async {
self.datas = results.userslist `//Cannot assign value of type '[Userslist]' to type '[PartnerMedia]' what should I write for getting proper response`
}
}
catch {
print(error)
}
}
}.resume()
}
}
I want to fetch the url and full_name And set to the View
struct PublisherListView: View{
#StateObject var list = PublisherModelVM()
var body: some View{
ScrollView(.horizontal,showsIndicators: false){
ForEach(list.datas, id: \.id){ item in
Text(item.full_name)
AsyncImage(url: URL(string: item.url)){image in
image
.resizable()
.frame(width: 235, height: 125).cornerRadius(8)
}placeholder: {
Image(systemName: "eye") .resizable()
.frame(width: 235, height: 125).cornerRadius(8)
}
}
}
}
}
this Error show in my Xcode Cannot assign value of type '[Userslist]'
to type '[PartnerMedia]'
Please help me.
can anyone help me for recommending for API related full detailed
courses and thank you in advance

As I said before (in the questions you have deleted) pay attention to the details of your models to match the json data. Try this approach, works very well for me:
struct ContentView: View {
var body: some View {
PublisherListView()
}
}
struct ServerResponse: Decodable {
let userslist: [User]
}
struct User: Decodable, Identifiable {
let id: Int
let totalBooks: Int
let totalfollowers: Int
let fullAddress: String?
let partnerMedia: [PartnerMedia]
enum CodingKeys: String, CodingKey {
case id, totalBooks,totalfollowers
case partnerMedia = "partner_media"
case fullAddress = "full_address"
}
}
struct PartnerMedia: Decodable, Identifiable {
let id: Int
let url: String
}
struct PublisherListView: View{
#StateObject var list = PublisherModelVM()
var body: some View{
ScrollView(.horizontal,showsIndicators: false){
HStack(spacing:15) {
ForEach(list.datas, id: \.id){ item in
AsyncImage(url: URL(string: item.url)){ image in
image
.resizable()
.frame(width: 235, height: 125).cornerRadius(8)
} placeholder: {
Image(systemName: "eye") .resizable()
.frame(width: 235, height: 125).cornerRadius(8)
}
}
}
}
}
}
class PublisherModelVM: ObservableObject {
#Published var datas = [PartnerMedia]()
let url = "https://alibrary.in/api/publisherList"
init() {
getData(url: url)
}
func getData(url: String) {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
do {
let results = try JSONDecoder().decode(ServerResponse.self, from: data)
DispatchQueue.main.async {
for user in results.userslist {
self.datas.append(contentsOf: user.partnerMedia)
}
}
}
catch {
print(error)
}
}
}.resume()
}
}

Related

How to fix these Core Data "one-to-many" relationship errors?

I am making a simple macOS Notes app where a user can create new folders and then create new notes for those folders. I am very new to Core Data and the "one-to-many" relationship amongst entities. I am trying to do this relationship with folders and notes. I want each folder to have their own separate notes. I keep getting errors like "Value of optional type 'String?' must be unwrapped to a value of type 'String'", "Coalesce using '??' to provide a default when the optional value contains 'nil'", and "Force-unwrap using '!' to abort execution if the optional value contains 'nil'".
I know I must have one specific problem somewhere in my code but I have no idea where. I have no idea if my code is remotely correct or not.
I have a folder view (Sidebar) and a note view (NoteView). I passed in the selected Folder as an ObservedObject in my NoteView (#ObservedObject var rootFolder: Folder) and tried to create a notesArray. In my NotesView I did rootFolder.notesArray.
Here is part of my Sidebar (folders) view:
struct Sidebar: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var isPresented: Bool = false
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Folder.folderName, ascending: true)])
var folders: FetchedResults<Folder>
#State private var selectedFoldersIds: Set<Folder.ID> = []
private var selectedFolder: Folder?{
guard let selectedFolderId = selectedFoldersIds.first,
let selectedFolder = folders.filter ({$0.id == selectedFolderId}).first else{
return nil
}
return selectedFolder
}
var body: some View {
Button(){
isPresented = true
} label: {
HStack{
Image(systemName: "folder.badge.plus")
Text("Add Folder")
}
.cornerRadius(50)
}
.sheet(isPresented: $isPresented){
AddFolderView{ name in
saveNewFolder(name: name)
}
}
.padding()
Divider()
Text("Folders")
.frame(maxWidth: .infinity, alignment: .leading)
.font(.caption)
.foregroundColor(.secondary)
List(folders, selection: $selectedFoldersIds){folder in
HStack{
Image(systemName: "folder")
NavigationLink(folder.folderName, destination:NoteView(rootFolder: folder)) //where error occurs. Is the problem something to do with folderName?
}
}
Part of NoteView:
struct NoteView: View {
#ObservedObject var rootFolder: Folder
#Environment(\.managedObjectContext) private var viewContext
#State private var isPresented: Bool = false
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Note.name, ascending: true)])
var notes: FetchedResults<Note>
#State private var selectedNotesIds: Set<Note.ID> = []
private var selectedNote: Note?{
guard let selectedNoteId = selectedNotesIds.first,
let selectedNote = notes.filter ({$0.id == selectedNoteId}).first else{
return nil
}
return selectedNote
}
var body: some View {
List {
ForEach(rootFolder.notesArray) { note in
NavigationLink(note.name, destination: NoteEditor(note: note)) // where error occurs once again. I think the problem has to do with note.name. Why doesn't this work?
}
}
Folder and Core Data Properties:
extension Folder {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Folder> {
return NSFetchRequest<Folder>(entityName: "Folder")
}
#NSManaged public var folderId: UUID?
#NSManaged public var folderName: String?
#NSManaged public var notes: NSSet?
public var notesArray: [Note] {
let set = notes as? Set<Note> ?? []
return set.sorted {
$0.wrappedName > $1.wrappedName // is something wrong with this?
}
}
}
extension Folder{
#objc(addNotesObject:)
#NSManaged public func addToNotes(_ value: Note)
#objc(removeNotesObject:)
#NSManaged public func removeFromNotes(_ value: Note)
#objc(addNotes:)
#NSManaged public func addToNotes(_ values: NSSet)
#objc(removeNotes:)
#NSManaged public func removeFromNotes(_ values: NSSet)
}
extension Folder : Identifiable {
}
Note and Core Data Properties:
extension Note {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Note> {
return NSFetchRequest<Note>(entityName: "Note")
}
#NSManaged public var id: UUID?
#NSManaged public var name: String?
#NSManaged public var text: String? // text attribute is used to pass into a TextEditor. Once again I get errors with this in my TextEditor view.
#NSManaged public var folder: Folder?
public var wrappedName: String { // is something wrong with this?
name ?? "Unknown"
}
}
extension Note : Identifiable {
}
Complete Sidebar code that probably isn't relevant:
struct Sidebar: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var isPresented: Bool = false
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Folder.folderName, ascending: true)])
var folders: FetchedResults<Folder>
#State private var selectedFoldersIds: Set<Folder.ID> = []
private var selectedFolder: Folder?{
guard let selectedFolderId = selectedFoldersIds.first,
let selectedFolder = folders.filter ({$0.id == selectedFolderId}).first else{
return nil
}
return selectedFolder
}
var body: some View {
Button(){
isPresented = true
} label: {
HStack{
Image(systemName: "folder.badge.plus")
Text("Add Folder")
}
.cornerRadius(50)
}
.sheet(isPresented: $isPresented){
AddFolderView{ name in
saveNewFolder(name: name)
}
}
.padding()
Divider()
Text("Folders")
.frame(maxWidth: .infinity, alignment: .leading)
.font(.caption)
.foregroundColor(.secondary)
List(folders, selection: $selectedFoldersIds){folder in
HStack{
Image(systemName: "folder")
NavigationLink(folder.folderName, destination:NoteView(rootFolder: folder))
}
}
.onDeleteCommand(perform: deleteSelectedFolders)
.toolbar{
ToolbarItem(placement: .primaryAction){
Button(action: toggleSidebar) {
Image(systemName: "sidebar.left")
}
}
ToolbarItem(placement: .primaryAction){
Button(action: deleteSelectedFolders){
Label("Delete note", systemImage: "trash")
}
}
}
}
private func toggleSidebar() {
NSApp.keyWindow?.firstResponder?
.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
func saveNewFolder(name: String){
let folder = Folder(context: viewContext)
folder.folderId = UUID()
folder.folderName = name
do{
try viewContext.save()
} catch{
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
private func deleteSelectedFolders(){
withAnimation{
let selectedFolders = folders.filter {selectedFoldersIds.contains($0.id)}
deleteFolders(folders: selectedFolders)
}
}
private func deleteFolders(folders: [Folder]){
viewContext.perform{ folders.forEach(viewContext.delete)}
}
}
Complete NoteView code that probably isn't relevant:
struct NoteView: View {
#ObservedObject var rootFolder: Folder
#Environment(\.managedObjectContext) private var viewContext
#State private var isPresented: Bool = false
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Note.name, ascending: true)])
var notes: FetchedResults<Note>
#State private var selectedNotesIds: Set<Note.ID> = []
private var selectedNote: Note?{
guard let selectedNoteId = selectedNotesIds.first,
let selectedNote = notes.filter ({$0.id == selectedNoteId}).first else{
return nil
}
return selectedNote
}
var body: some View {
List {
ForEach(rootFolder.notesArray) { note in
NavigationLink(note.name, destination: NoteEditor(note: note))
}
}
.toolbar{
ToolbarItem(placement: .primaryAction){
Button(){
isPresented.toggle()
} label: {
Image(systemName: "square.and.pencil")
}
.sheet(isPresented: $isPresented){
AddNoteView{ name in
saveNewNote(name: name, text: "")
}
}
}
ToolbarItem(placement: .primaryAction){
Button(action: deleteSelectedNotes){
Label("Delete note", systemImage: "trash")
}
}
}
}
func saveNewNote(name: String, text: String){
let note = Note(context: viewContext)
note.id = UUID()
note.name = name
note.text = text
do{
try viewContext.save()
} catch{
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
private func deleteSelectedNotes(){
withAnimation{
let selectedNotes = notes.filter {selectedNotesIds.contains($0.id)}
deleteNotes(notes: selectedNotes)
}
}
private func deleteNotes(notes: [Note]){
viewContext.perform{ notes.forEach(viewContext.delete)}
}
}

Persistent data when editing an existing array in swiftui

I am trying to figure out how to make two things work together. The first is persistent data using userdefaults, which I have figured out by using #Published and #Observable, then using JSONencoder/decoder to get that data to save even when the app is closed and reopened. The second I have also recently learned via this previous question < https://stackoverflow.com/questions/62106227/best-way-to-update-edit-an-array-element-in-swiftui/67752060#67752060>
My issue is combining the two. I can’t seem to figure out how to take the below code with predefined array of data and make the changes persist using UserDefaults. Any help would be greatly appreciated. Maybe I’m approaching this incorrectly.
Code for persistent data via UserDefaults
import SwiftUI
struct CharacterModel: Identifiable, Codable {
var id = UUID()
var name: String
var level: Int
}
class CharacterViewModel: ObservableObject {
#Published var characters = [CharacterModel]() {
// Write data back to Model
didSet {
let encoder = JSONEncoder()
if let encoded = try?
encoder.encode(characters) {
UserDefaults.standard.set(encoded, forKey: "Characters")
}
}
}
init() {
if let characters = UserDefaults.standard.data(forKey: "Characters") {
let decoder = JSONDecoder()
if let decoded = try?
decoder.decode([CharacterModel].self, from: characters) {
self.characters = decoded
return
}
}
self.characters = []
}
}
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var characterVM: CharacterViewModel
#State private var name = ""
#State private var level = ""
var body: some View {
NavigationView {
Form {
TextField("Name", text: $name)
TextField("Level", text: $level)
.keyboardType(.numberPad)
}
.navigationBarTitle("Add Level")
.navigationBarItems(trailing: Button("Save") {
if let actualLevel = Int(self.level) {
let character = CharacterModel(name: self.name, level: actualLevel)
self.characterVM.characters.append(character)
self.presentationMode.wrappedValue.dismiss()
}
})
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(characterVM: CharacterViewModel())
}
}
struct Home: View {
#State private var showingAddCharacter = false
#State var selectedCharacter : CharacterModel!
#ObservedObject var characterVM = CharacterViewModel()
var body: some View {
NavigationView {
List {
ForEach(characterVM.characters) { char in
HStack {
VStack(alignment: .leading) {
Text(char.name)
.font(.headline)
}
Spacer()
Text("\(char.level)")
}
}
.onDelete(perform: removeItems)
}
.navigationBarTitle("Characters")
.navigationBarItems(trailing:
Button(action: {
self.showingAddCharacter = true
}) {
Image(systemName: "plus")
}
)
.sheet(isPresented: $showingAddCharacter) {
DetailView(characterVM: self.characterVM)
}
}
}
func removeItems(at offsets: IndexSet) {
characterVM.characters.remove(atOffsets: offsets)
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
}
}
Editing a predefined array
class Training: ObservableObject, Identifiable {
let id: String
#Published var trainingName: String
#Published var isRequired: Bool
init(id: String, trainingName: String, isRequired: Bool) {
self.id = id
self.trainingName = trainingName
self.isRequired = isRequired
}
}
class GetTrainings: ObservableObject {
#Published var items = [Training]()
init() {
self.items = [
Training(id: "ttt1", trainingName: "Safety", isRequired: true),
Training(id: "ttt2", trainingName: "Administrative", isRequired: false),
Training(id: "ttt3", trainingName: "Computer", isRequired: true),
Training(id: "ttt4", trainingName: "People", isRequired: true),
Training(id: "ttt5", trainingName: "Managerial", isRequired: true),
]
}
}
struct TrainingList: View {
#ObservedObject var trainings = GetTrainings()
var body: some View {
NavigationView {
VStack {
List {
ForEach(trainings.items) { training in
HStack {
NavigationLink(destination: TrainingView(training: training)) {
Text("\(training.trainingName)")
}
}
}
}
}.navigationBarTitle("Training List")
.onAppear {
self.trainings.objectWillChange.send() // refresh
}
}
}
}
struct TrainingView: View {
#ObservedObject var training: Training
var body: some View {
VStack {
Text("\(training.trainingName)").font(.body)
Text("\(training.isRequired == true ? "Required Training" : "Training Not Required")")
HStack {
NavigationLink(destination: EditTraining(training: training)) {
Text("Edit Training Details")
}
}
}.navigationBarTitle("\(training.trainingName) Page", displayMode: .inline)
}
}
struct EditTraining: View {
#ObservedObject var training: Training
#State private var newName: String
#State private var isRequiredTraining: Bool
init(training: Training) {
self.training = training
self._newName = State(initialValue: training.trainingName)
self._isRequiredTraining = State(initialValue: training.isRequired)
}
private func submitData() {
let newName = self.newName
let newBoolVal = self.isRequiredTraining
print("Firebase Sync Id is :\(training.id) Text: \(newName) and Bool: \(newBoolVal)")
self.training.trainingName = newName
self.training.isRequired = newBoolVal
}
var body: some View {
VStack {
Form {
Section (header: Text("Edit")) {
Text("\(training.trainingName)")
/* TextField should Populate With passed In Training Name Here*/
TextField("New Name", text: self.$newName)
Toggle(isOn: self.$isRequiredTraining) {
Text("Is Required")
}
}
Section {
Button(action: {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
to:nil, from:nil, for:nil)
self.submitData()
}) {
Text("Submit")
}
}
}
}.navigationBarTitle("Edit Training Page", displayMode: .inline)
}
}

Problems with switching array elements

I have a class that has a calculated property, which is an array consisting of instances of the structure.
struct Team: Identifiable, Codable, Hashable {
var id = UUID()
var name : String
}
class TeamRow : ObservableObject {
#Published var teamsArray : [Team] = [] {
didSet {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(teamsArray) {
UserDefaults.standard.setValue(encoded, forKey: "Teams")
}
}
}
init() {
if let teams = UserDefaults.standard.data(forKey: "Teams") {
let decoder = JSONDecoder()
if let decoded = try? decoder.decode([Team].self, from: teams) {
self.teamsArray = decoded
return
}
}
}
}
Also, I have a view, with the ability to add elements(teams) to this array using a sheet.
struct PlayersRow: View {
#ObservedObject var teams = TeamRow()
#State private var team = ""
#State private var showTeamAddSheet = false
var body: some View {
Form {
ForEach(teams.teamsArray) { team in
Text(team.name)
.font(.system(size: 20))
.padding(.horizontal, 110)
.padding(.vertical, 10)
}
}
.navigationBarTitle("Teams")
.navigationBarItems(trailing: Button(action: {
self.showTeamAddSheet = true
}) {
Image(systemName: "plus")
.foregroundColor(.black)
.font(.system(size: 30))
})
.sheet(isPresented: $showTeamAddSheet) {
AddPlayerView(teams: self.teams)
}
}
}
This is a sheet view.
struct AddPlayerView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var teams : TeamRow
#State private var team = ""
var body: some View {
NavigationView {
Form {
TextField("Add new team", text: $team)
}
.navigationBarItems(trailing: Button(action: {
let newTeam = Team(name: self.team)
self.teams.teamsArray.append(newTeam)
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Сохранить")
.font(.custom("coolJazz", size: 20))
.foregroundColor(.black)
}))
.navigationBarTitle("Add Team")
}
}
}
And I have a view where I need to output the array elements one by one, using a button, clicked on the button, the view screen displayed 0 element, clicked on the button, displayed first element, etc.
struct GameView: View {
#ObservedObject var teams = TeamRow()
//#State var index = 0
var body: some View {
VStack(spacing: 40) {
//Text(teams.teamsArray[index]) this isn't worked, return an error: Initializer 'init(_:)' requires that 'Team' conform to 'StringProtocol'
Button(action: {
}) {
Text("press it")
}
}
}
}
struct GameView_Previews: PreviewProvider {
static var previews: some View {
GameView().environmentObject(TeamRow())
}
}
if the array is set initially in the class, I have no problem displaying the elements on the screen by increasing the index, but how to solve this problem I do not know...
Can some one explain newbie?
Change your GameView Code to following:
struct GameView: View {
#ObservedObject var teams = TeamRow()
#State var index = 0
var body: some View {
VStack(spacing: 40) {
if teams.teamsArray.count > index {
Text(teams.teamsArray[index].name)
}
Button(action: {
index += 1
}) {
Text("press it")
}
}
}
}

Swiftui append to a struct array doesn't work

Im learning swiftui at this moment. But now i have come across a problem.
Im trying to append data to an array that is an struct.
struct outfit:Identifiable {
var id = UUID()
var user: String
var amount: Double
var rating: Double
}
and the other file
import SwiftUI
import Firebase
import FirebaseStorage
import FirebaseFirestore
struct rate: View {
private var db = Firestore.firestore()
#State var user = Auth.auth().currentUser
#State private var outfitcards = [outfit]()
#State private var cards = [1, 2, 3]
#State private var offset = [CGSize.zero, CGSize.zero]
init () {
loadcards()
}
var body: some View {
GeometryReader { frame in
ZStack{
VStack {
Text("outfit")
.font(.largeTitle)
.padding()
ZStack {
Text("No cards to show")
.frame(width: frame.size.width * 0.6, height: frame.size.width * 1.6)
HStack {
Image(systemName: "record.circle.fill")
.foregroundColor(.red)
Spacer()
Image(systemName: "record.circle.fill")
.foregroundColor(.green)
}
ForEach(outfitcards) { index in
Text(index.user)
}
}
}
}
}
}
func loadcards () {
db.collection("rating").whereField("user", isNotEqualTo: user?.uid ?? "Error")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err.localizedDescription)")
} else {
for document in querySnapshot!.documents {
let cuser = document.get("user") as! String
let camount = document.get("amount") as! Double
let crating = document.get("rating") as! Double
print("user=\(cuser) amount=\(camount) crating=\(crating)")
outfitcards.append(outfit(user: cuser, amount: camount, rating: crating))
}
print(outfitcards)
}
}
}
}
It does print the username, the amount and the rating but then when i print the array itself it is giving me a []. So it doens't append. Also the for each loop is empty so that also means that the array is empty
and nothing is appended
Does anyone know what I do wrong?
Loading data in a View like that is a dangerous practice and it'll get reset/reloaded any time that view is re-rendered (or even re-inited in this case).
Instead, move your code that loads the data to an ObservableObject:
struct Outfit:Identifiable {
var id = UUID()
var user: String
var amount: Double
var rating: Double
}
class Loader : ObservableObject {
private var db = Firestore.firestore()
private var user = Auth.auth().currentUser
#Published var outfitcards = [Outfit]()
func loadcards () {
db.collection("rating").whereField("user", isNotEqualTo: user?.uid ?? "Error")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err.localizedDescription)")
} else {
for document in querySnapshot!.documents {
let cuser = document.get("user") as! String
let camount = document.get("amount") as! Double
let crating = document.get("rating") as! Double
print("user=\(cuser) amount=\(camount) crating=\(crating)")
self.outfitcards.append(Outfit(user: cuser, amount: camount, rating: crating))
}
print(self.outfitcards)
}
}
}
}
struct Rate: View {
#StateObject var loader = Loader()
#State private var cards = [1, 2, 3]
#State private var offset = [CGSize.zero, CGSize.zero]
var body: some View {
GeometryReader { frame in
ZStack{
VStack {
Text("outfit")
.font(.largeTitle)
.padding()
ZStack {
Text("No cards to show")
.frame(width: frame.size.width * 0.6, height: frame.size.width * 1.6)
HStack {
Image(systemName: "record.circle.fill")
.foregroundColor(.red)
Spacer()
Image(systemName: "record.circle.fill")
.foregroundColor(.green)
}
ForEach(loader.outfitcards) { index in
Text(index.user)
}
}
}
}
}.onAppear {
loader.loadcards()
}
}
}
Now, the loader is responsible for making the database call. It updates a #Published property, which the View observes.
Note that I changed the capitalization of a few things (Rate, Outfit). In Swift, the common practice is to capitalize types (classes/structs/enums) and start variable names with lowercase letters.
Also probably worth noting that the way that you're casting the data from firebase (with as!) is dangerous and can crash your program if the data isn't in the format you expect. Better to use optional binding (let user = document.get("user") as? String)
Your issue is there you are updating #State value in initializing View, which you are doing in Wrong way, do like this onAppear:
struct rate: View {
private var db = Firestore.firestore()
#State var user = Auth.auth().currentUser
#State private var outfitcards = [outfit]()
#State private var cards = [1, 2, 3]
#State private var offset = [CGSize.zero, CGSize.zero]
var body: some View {
GeometryReader { frame in
ZStack{
VStack {
Text("outfit")
.font(.largeTitle)
.padding()
ZStack {
Text("No cards to show")
.frame(width: frame.size.width * 0.6, height: frame.size.width * 1.6)
HStack {
Image(systemName: "record.circle.fill")
.foregroundColor(.red)
Spacer()
Image(systemName: "record.circle.fill")
.foregroundColor(.green)
}
ForEach(outfitcards) { index in
Text(index.user)
}
}
}
}
}
.onAppear() { loadcards() } // <<: Here
}
func loadcards () {
db.collection("rating").whereField("user", isNotEqualTo: user?.uid ?? "Error")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err.localizedDescription)")
} else {
for document in querySnapshot!.documents {
let cuser = document.get("user") as! String
let camount = document.get("amount") as! Double
let crating = document.get("rating") as! Double
print("user=\(cuser) amount=\(camount) crating=\(crating)")
outfitcards.append(outfit(user: cuser, amount: camount, rating: crating))
}
print(outfitcards)
}
}
}
}

Why is my SwiftUI JSON not parsing properly?

Disclaimer: Another very basic question below. I am trying to learn the basics of IOS development.
I'm currently trying to parse data from an API to a SwiftUI project and am not able to successfully do so.
The code goes as follows:
import SwiftUI
struct Poem: Codable, Hashable {
let title, author: String
let lines: [String]
let linecount: String
}
struct ContentView: View {
var poems = [Poem]()
var body: some View {
VStack {
if let poem = poems.first {
Button("Refresh") {getPoem()}
Text("\(poem.author): \(poem.title)").bold()
Divider()
ScrollView {
VStack {
ForEach(poem.lines, id: \.self) {
Text($0)
}
}
}
}
}
}
}
func getPoem() {
let url = URL(string: "https://poetrydb.org/random/1")!
// 2.
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let poemData = data {
// 3.
let decodedData = try JSONDecoder().decode([Poem].self, from: poemData)
DispatchQueue.main.async {
self.poems = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The code does not build. The error thrown happens in the Func getPoem where "Cannot find 'self' in scope".
Any ideas? All help is appreciated.
I meant something like
class FetchPoem: ObservableObject {
// 1.
#Published var poems = [Poem]()
init() {
getPoem()
}
func getPoem() {
let url = URL(string: "https://poetrydb.org/random/1")!
// 2.
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let poemData = data {
// 3.
let decodedData = try JSONDecoder().decode([Poem].self, from: poemData)
DispatchQueue.main.async {
self.poems = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
struct PoemContentView: View {
#ObservedObject var fetch = FetchPoem()
var body: some View {
VStack {
Button("Get Next Poem") { fetch.getPoem() }
if let poem = fetch.poems.first {
Text("\(poem.author): \(poem.title)").bold()
Divider()
ScrollView {
VStack {
ForEach(poem.lines, id: \.self) {
Text($0)
}
}
}
} else {
Spacer()
}
}
}
}

Resources