Why is my JSON code in Swift not parsing? - json

Disclaimer: 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 {
let title, author: String
let lines: [String]
let linecount: String
}
class FetchPoem: ObservableObject {
// 1.
#Published var poems = [Poem]()
init() {
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: View {
#ObservedObject var fetch = FetchPoem()
let joined = fetch.poem.lines.joined(separator: "\n")
var body: some View {
Text(fetch.poem.title)
.padding()
Text( \(joined) )
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The build currently fails. It's throwing me the following errors:
Initializer 'init(_:)' requires that 'Binding<Subject>' conform to 'StringProtocol'
Referencing subscript 'subscript(dynamicMember:)' requires wrapper 'ObservedObject<FetchPoem>.Wrapper'
Value of type 'FetchPoem' has no dynamic member 'poem' using key path from root type 'FetchPoem'
Moreover, I am attempting to append the array "Lines" into one main String variable "Joined". However, I am not sure this works... The error is "String interpolation can only appear inside a string literal". Would love some help if anyone knows...
Any ideas? All help is appreciated.
** Edited Code - Q2
import SwiftUI
struct Poem: Codable, Hashable {
let title, author: String
let lines: [String]
let linecount: String
}
class FetchPoem: ObservableObject {
#Published var poems = [Poem]()
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: View {
#ObservedObject var fetch = FetchPoem()
var body: some View {
VStack {
if let poem = fetch.poems.first {
Button("Refresh") {getPoem}
Text("\(poem.author): \(poem.title)").bold()
Divider()
ScrollView {
VStack {
ForEach(poem.lines, id: \.self) {
Text($0)
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Most probably you wanted this (because poems is an array) - tested with Xcode 12.1 / iOS 14.1
struct Poem: Codable, Hashable {
let title, author: String
let lines: [String]
let linecount: String
}
// ...
struct ContentView: View {
#ObservedObject var fetch = FetchPoem()
var body: some View {
List {
ForEach(fetch.poems, id: \.self) {
Text("\($0.author): \($0.title)")
}
}
}
}
... , and next might be wrapping that into NavigationView/NavigationLink with shown each poem in desination view.
Of showing lines as follows:
var body: some View {
VStack {
if let poem = fetch.poems.first {
Text("\(poem.author): \(poem.title)").bold()
Divider()
ScrollView {
VStack {
ForEach(poem.lines, id: \.self) {
Text($0)
}
}
}
}
}
}

Related

Parse a Json from a url with entry values and list them in SwiftUI

I'm stuck on this.
I have a json that I'm parsing and that Json has an entry value and I want to do a swift list based in user input that will trigger from the external json different responses.
I have the following code so far
import SwiftUI
import Combine
import Foundation
var lokas : String = "ABCY"
struct ContentView: View {
#State var name: String = ""
#ObservedObject var fetcher = Fetcher()
var body: some View {
VStack {
TextField("Enter Loka id", text: $name)
List(fetcher.allLokas) { vb in
VStack (alignment: .leading) {
Text(movie.device)
Text(String(vb.seqNumber))
.font(.system(size: 11))
.foregroundColor(Color.gray)
}
}
}
}
}
public class Fetcher: ObservableObject {
#Published var allLokas = [AllLoka_Data]()
init(){
load(devices:lokas)
}
func load(devices:String) {
let url = URL(string: "https://xxx.php?device=\(devices)&hours=6")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([AllLoka_Data].self, from: d)
DispatchQueue.main.async {
self.allLokas = decodedLists
}
}else {
print("No Data")
}
} catch {
print ("Error")
}
}.resume()
}
}
struct AllLoka_Data: Codable {
var date: String
var time: String
var unix_time: Int
var seqNumber : Int
}
// Now conform to Identifiable
extension AllLoka_Data: Identifiable {
var id: Int { return unix_time }
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
But I just added a Global variable to test it, but how do I pass the variable name to make the function work ?
Thank you

Decode JSON using Codable and then populate my SwiftUI

I am new to Swift and SwiftUI. I am currently teaching myself how to code (loving it!) through hackingwithswift.com I am currently on Day 60 and I am stuck and not sure what to do from here.
The challenge is to decode some information using Codable and populate SwiftUI.
I created a struct to match the JSON, but when I go to run the app, I keep getting my error "Fetch Failed: Unknown Error" and therefore my UI won't update.
Would someone glance at my code and provide any pointers on where I am going wrong and possibly why? Thank you so much for any suggestions and help, it is much appreciated! Code is posted below.
Cody
import SwiftUI
struct Response: Codable {
var results: [User]
}
struct User: Codable, Identifiable {
let id: String
let isActive: Bool
let name: String
let age: Int
let company: String
let email: String
let address: String
let about: String
let registered: String
let tags: [String]
struct FriendRole: Codable {
let id: String
let name: String
}
let friend: [FriendRole]
}
struct ContentView: View {
#State private var results = [User]()
var body: some View {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.address)
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async {
self.results = decodedResponse.results
}
return
}
}
print("Fetch Failed: \(error?.localizedDescription ?? "Unkown Error").")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm also new to swiftUI this is how I managed to make it work by using Observable Objects
Here's the structures Since in the json file there is a [ in the very beginning you do not have to create a struct that has a User array you could create a struct and then create a variable of that struct type as an array
here's how I did it
I created separate files for instance for the structures i had a different file for them
here's what I have for my structure file
import Foundation
struct User : Codable, Identifiable {
let id : String
let isActive : Bool
let name : String
let age : Int
let company : String
let email : String
let address : String
let about : String
let registered : String
let tags = [String]()
let friends = [Friends]()
}
struct Friends : Codable {
let id : String
let name : String
}
I created another file for the observable object class
class JsonChannel : ObservableObject {
#Published var retVal = [User]()
func getInfo () {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {return}
URLSession.shared.dataTask(with: url) { (data, resp, err) in
if let data = data {
DispatchQueue.main.async {
do {
self.retVal = try JSONDecoder().decode([User].self, from: data)
}
catch {
print(error)
}
}
}
}.resume()
}
}
and here's what i have for my contentView file
import SwiftUI
struct ContentView : View {
#ObservedObject var info = JsonChannel()
var body: some View {
VStack {
Button(action: {
self.info.getInfo()
}) {
Text("click here to get info")
}
List {
ForEach (self.info.retVal) { item in
VStack {
Text("\(item.name)")
Text("\(item.address)")
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwifUI : Is my downloaded JSON saved to coreData?

I've been messing around with CoreData and this time I'm trying to save my downloaded JSON to core data. I don't get any error until I try to display the list and it would return empty.
Any suggestions would be much appreciated!
Model
struct ArticleData: Identifiable, Decodable {
let id : String
let type : String
let attributes: ArticleAttributee
struct ArticleAttributee: Codable {
let name: String
let card_artwork_url: String
...
}
AttributeArticle+CoreDataProperties.swift
extension AttributeArticle {
#nonobjc public class func fetchRequest() -> NSFetchRequest<AttributeArticle> {
return NSFetchRequest<AttributeArticle>(entityName: "AttributeArticle")
}
#NSManaged public var card_artwork_url: String?
#NSManaged public var content_type: String?
...
public var wrapperCard_artwork_url : String {
card_artwork_url ?? "Unknown"
}
...
}
Download JSOn and load to Core Data
class Article {
static func loadDataFromJSON(completion: #escaping ([ArticleData]) -> ()) {
let stringURL = "https://api.jsonbin.io/b/5ed679357741ef56a566a67f"
guard let url = URL(string: stringURL) else { return }
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {
data, response, error in
guard let data = data else {
print("No data in response \(error?.localizedDescription ?? "No data response")")
return
}
if let decoderLoadedUser = try? JSONDecoder().decode([ArticleData].self, from: data) {
completion(decoderLoadedUser)
}
}.resume()
}
static func loadDataToCD(moc: NSManagedObjectContext) {
loadDataFromJSON { (articles) in
DispatchQueue.main.async {
var tempArticles = [AttributeArticle]()
for article in articles {
let newArticle = AttributeArticle(context: moc)
newArticle.name = article.attributes.name
newArticle.card_artwork_url = article.attributes.card_artwork_url
... so on
...
tempArticles.append(newArticle)
}
do {
try moc.save()
} catch let error {
print("Could not save data: \(error.localizedDescription)")
}
}
}
}
}
my list :
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: AttributeArticle.entity(), sortDescriptors: []) var articles: FetchedResults<AttributeArticle>
VStack {
List() {
ForEach(articles, id: \.id) { article in
NavigationLink(destination: ArticleDetailView(articles: article)){
ArticleRowView(articles: article)
}
}
}
}.onAppear {
if self.articles.isEmpty {
print("Articles is empty \(self.articles)")
Article.loadDataToCD(moc: self.moc)
}
Sorry for the long post and thank you for your helps!

JSON Image parsing in SwiftUI

I've tried to parse JSON from https://jsonplaceholder.typicode.com/photos
Everything works great, but instead of getting a picture I get only text with the URL of this particular picture.
Here is my code. What did I do wrong? Thank you in advance!
.
.
.
.
.
.
import SwiftUI
struct ContentView: View {
#State var posts = [Post]()
var body: some View {
NavigationView{List(posts, id: \.albumId) { post in
NavigationLink(destination: DetailView(post: post)) {
HStack() {
Text(String(describing: post.albumId))
.font(.headline)
}
}.navigationBarTitle("Albums")
}.onAppear(perform: loadData)
}
}
}
struct DetailView: View {
var post: Post
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(post.title)
.font(.headline)
Text(post.url)
}
}
}
struct Post: Decodable {
var albumId: Int
var title: String
var url: String
}
extension ContentView
{
func loadData() {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/photos") else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response_obj = try? JSONDecoder().decode([Post].self, from: data) {
DispatchQueue.main.async {
self.posts = response_obj
}
}
}
}.resume()
}
}
Use https://github.com/SDWebImage/SDWebImageSwiftUI
var body: some View {
WebImage(url: URL(string: "http://...."))
}

SwiftUI: Data in list appears sometimes and sometimes not

I have a problem. In my application, I have CoreData with devices and the IP of the devices. I want to make an API request to fetch JSON data from a selected device and show them in a list. My problem is that sometimes it works and sometimes it does not and the list does not update when I change the data. I hope someone can help me.
BouquetAPIModel.swift
struct BouquetAPIModel: Codable {
let success: String
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let bouquets: [Bouquet]
}
// MARK: - Bouquet
struct Bouquet: Codable {
var number, name: String
}
Device.swift
public class Device: NSManagedObject, Identifiable {
#Published var bouquets : [Bouquet] = [Bouquet]()
func fetchBouquetList() {
fetchAPIRequest(apiPath: "/control/getbouquets?format=json") { (res: Result<BouquetAPIModel, Error>) in
switch res {
case .success(let bouquets):
self.bouquets = bouquets.data.bouquets
case .failure(let err):
print("Fail to fetch bouquets: ", err)
}
}
}
fileprivate func fetchAPIRequest<T: Decodable>(apiPath: String, completion: #escaping (Result<T, Error>) -> ()) {
let urlString = getApiUrl(apiPath: apiPath)
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data,resp, err) in
if let err = err {
completion(.failure(err))
return
}
do {
let welcome = try JSONDecoder().decode(T.self, from: data!)
completion(.success(welcome))
} catch let jsonError {
completion(.failure(jsonError))
}
}.resume()
}
BouquetView.swift
import SwiftUI
struct BouquetView: View {
#Binding var device : Device?
#State var bouquets: [Bouquet] = [Bouquet]()
var body: some View {
VStack {
Text(String(device?.deviceName ?? "fehler")).onTapGesture {
self.device?.bouquets.removeFirst()
print("Touch")
}
List (self.bouquets, id: \Bouquet.name) { bouquet in
VStack(alignment: .leading) {
Text(bouquet.name)
}
}
}.onAppear(perform: loadBouquets)
}
func loadBouquets() {
if device == nil {
//TODO jumo to settings
}
else {
device?.fetchBouquetList()
self.bouquets = device!.bouquets
}
}
}
struct BouquetView_Previews: PreviewProvider {
static var previews: some View {
BouquetView(device: .constant(nil))
}
}
Can you update your Device like below?
public class Device: ObservableObject, Identifiable {
...
}
And make sure you declare your device with annotation #ObservedObject before passing to BouquetView
Fetching might be long, so you've got into racing. To avoid it the followings approach might be used
1) remove the following line as data might not be ready yet at this point
self.bouquets = device!.bouquets
2) add explicit listener for data set
if device == nil {
//TODO jumo to settings
device!.$bouquets
.assign(to: \.bouquets, on: self)
.store(in: &cancellables) // << you will need to create this store var
}