JSON Image parsing in SwiftUI - json

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://...."))
}

Related

SwiftUI NavigationLink cannot find 'json' in scope

I'm new to SwiftUI and have worked through the server requests and JSON. I now need to programmatically transition to a new view which is where I get stuck with a "Cannot find 'json' in scope" error on the NavigationLink in ContentView.swift. I've watched videos and read articles but nothing quite matches, and everything I try just seems to make things worse.
JSON response from server
{"status":{"errno":0,"errstr":""},
"data":[
{"home_id":1,"name":"Dave's House","timezone":"Australia\/Brisbane"},
{"home_id":2,"name":"Mick's House","timezone":"Australia\/Perth"},
{"home_id":3,"name":"Jim's House","timezone":"Australia\/Melbourne"}
]}
JSON Struct file
import Foundation
struct JSONStructure: Codable {
struct Status: Codable {
let errno: Int
let errstr: String
}
struct Home: Codable, Identifiable {
var id = UUID()
let home_id: Int
let name: String
let timezone: String
}
let status: Status
let data: [Home]
}
ContentView file
import SwiftUI
struct ContentView: View {
#State private var PushViewAfterAction = false
var body: some View {
NavigationLink(destination: ListView(json: json.data), isActive: $PushViewAfterAction) {
EmptyView()
}.hidden()
Button(action: {
Task {
await performAnAction()
}
}, label: {
Text("TEST")
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue.cornerRadius(10))
.foregroundColor(.white)
.font(.headline)
})
}
func performAnAction() {
PushViewAfterAction = true
return
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ListView file
import SwiftUI
struct ListView: View {
#State var json: JSONStructure
var body: some View {
VStack {
List (self.json.data) { (home) in
HStack {
Text(home.name).bold()
Text(home.timezone)
}
}
}.onAppear(perform: {
guard let url: URL = URL(string: "https://... ***removed*** ") else {
print("invalid URL")
return
}
var urlRequest: URLRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
URLSession.shared.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
// check if response is okay
guard let data = data, error == nil else { // check for fundamental networking error
print((error?.localizedDescription)!)
return
}
let httpResponse = (response as? HTTPURLResponse)!
if httpResponse.statusCode != 200 { // check for http errors
print("httpResponse Error: \(httpResponse.statusCode)")
return
}
// convert JSON response
do {
self.json = try JSONDecoder().decode(JSONStructure.self, from: data)
} catch {
print(error.localizedDescription)
print(String(data: data, encoding: String.Encoding.utf8)!)
}
print(json)
if (json.status.errno != 0) {
print(json.status.errstr)
}
print("1. \(json.data[0].name)), \(json.data[0].timezone)")
print("2. \(json.data[1].name)), \(json.data[1].timezone)")
}).resume()
})
}
}
struct ListView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
I've tried to keep the code to a minimum for clarity.
It's because there is no "json" in ContentView, you need to pass json object to ListView, but since you load json in ListView, then you need to initialize json in ListView like:
struct ListView: View {
#State var json: JSONStructure = JSONStructure(status: JSONStructure.Status(errno: 0, errstr: ""), data: [JSONStructure.Home(home_id: 0, name: "", timezone: "")])
var body: some View {
and remove it form NavigationLink in ContentView like:
NavigationLink(destination: ListView(), isActive: $PushViewAfterAction) {
or you could build your JSONStructure to accept optional like:
import Foundation
struct JSONStructure: Codable {
struct Status: Codable {
let errno: Int?
let errstr: String?
init() {
errno = nil
errstr = nil
}
}
struct Home: Codable, Identifiable {
var id = UUID()
let home_id: Int?
let name: String?
let timezone: String?
init() {
home_id = nil
name = nil
timezone = nil
}
}
let status: Status?
let data: [Home]
init() {
status = nil
data = []
}
}
but then you need to check for optionals or provide default value like:
struct ListView: View {
#State var json: JSONStructure = JSONStructure()
var body: some View {
VStack {
List (self.json.data) { (home) in
HStack {
Text(home.name ?? "Could not get name").bold()
Text(home.timezone ?? "Could not get timeZone")
}
}
}.onAppear(perform: {
guard let url: URL = URL(string: "https://... ***removed*** ") else {
print("invalid URL")
return
}
var urlRequest: URLRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
URLSession.shared.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
// check if response is okay
guard let data = data, error == nil else { // check for fundamental networking error
print((error?.localizedDescription)!)
return
}
let httpResponse = (response as? HTTPURLResponse)!
if httpResponse.statusCode != 200 { // check for http errors
print("httpResponse Error: \(httpResponse.statusCode)")
return
}
// convert JSON response
do {
self.json = try JSONDecoder().decode(JSONStructure.self, from: data)
} catch {
print(error.localizedDescription)
print(String(data: data, encoding: String.Encoding.utf8)!)
}
print(json)
if (json.status?.errno != 0) {
print(json.status?.errstr)
}
print("1. \(json.data[0].name)), \(json.data[0].timezone)")
print("2. \(json.data[1].name)), \(json.data[1].timezone)")
}).resume()
})
}
}

SwiftUI Fetching JSON API Data from api.city.bik

I am having issues trying to get this data. I heard there is a trick. Can anyone create a simple call to view the data from this api? Would truly appreciate it. Been trying for a week. I cant for the life of me get this simple api call to work.
http://api.citybik.es/v2/networks
Model.swift
import Foundation
// MARK: - Welcome
struct Dataset: Codable {
let networks: [Network]
}
// MARK: - Network
struct Network: Codable {
let company: [String]
let href, id: String
let location: Location
let name: String
}
// MARK: - Location
struct Location: Codable {
let city, country: String
let latitude, longitude: Double
}
Contentview.swift
import SwiftUI
struct ContentView: View {
#State var results = [Network]()
func loadData() {
guard let url = URL(string: "http://api.citybik.es/v2/networks") else {
print("Your API end point is Invalid")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response = try? JSONDecoder().decode([Network].self, from: data) {
DispatchQueue.main.async {
self.results = response
}
return
}
}
}.resume()
}
var body: some View {
List(results, id: \.name) { item in
VStack(alignment: .leading) {
Text("\(item.name)")
}
}.onAppear(perform: loadData)
}
}
copy the whole of the json from : "https://api.citybik.es/v2/networks"
into "https://app.quicktype.io/" and get the (correct) swift data structures from that.
Rename "Welcome" to "Response" and use that in your code.
use: "https://api.citybik.es/v2/networks" note the https.
EDIT: In your code:
struct ContentView: View {
#State var networks = [Network]()
var body: some View {
List(networks, id: \.id) { network in
VStack {
Text(network.name)
Text(network.location.city)
}
}.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://api.citybik.es/v2/networks") else {
print("Your API end point is Invalid")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async {
self.networks = response.networks
}
return
}
}
}.resume()
}
}
Once you have all the data structures,
and if you are using Swift 5.5 for ios 15 or macos 12, you can use something like this:
struct ContentView: View {
#State var networks = [Network]()
var body: some View {
List(networks, id: \.id) { network in
VStack {
Text(network.name)
Text(network.location.city)
}
}
.task {
let response: Response? = await fetchNetworks()
if let resp = response {
networks = resp.networks
}
}
}
func fetchNetworks<T: Decodable>() async -> T? {
let url = URL(string: "https://api.citybik.es/v2/networks")!
let request = URLRequest(url: url)
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
// throw URLError(.badServerResponse) // todo
print(URLError(.badServerResponse))
return nil
}
let results = try JSONDecoder().decode(T.self, from: data)
return results
}
catch {
return nil
}
}
}

Why is my JSON code in Swift not parsing?

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)
}
}
}
}
}
}

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!

Reading single string of json in swift ui

I'm using this API http://shibe.online/api/shibes? although when i load it, all i see is a single link which is what i want to use but there is no variable identifier associated with it.
["https://cdn.shibe.online/shibes/be12be31e2c880b4ac6992b3bb02751211b6ee68.jpg"]
import Foundation
import SwiftUI
import Combine
import SDWebImageSwiftUI
struct ShibeView: View {
#ObservedObject var fetcher = ShibeFetcher()
var body: some View {
AnimatedImage(url: URL(string: self.fetcher.shibe?.url ?? "")).resizable().scaledToFit()
}
}
public class ShibeFetcher: ObservableObject {
#Published var shibe: ShibeRecievers?
init(){
load()
}
func load() {
let url = URL(string: "http://shibe.online/api/shibes?")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let webData = try JSONDecoder().decode(ShibeRecievers.self, from: d)
DispatchQueue.main.async {
self.shibe = webData
}
}else {
print("No Data")
}
} catch {
print ("Error here")
}
}.resume()
}
}
struct ShibeRecievers: Codable {
var url: String //Not sure how to store the link from the api
}
The problem i have is storing the link from the api into my app and I can't find any json parsing documentation without variable names.
Your json response is just an array of String. So you can decode it like this:
try! JSONDecoder().decode([String].self, from: d)
Completed version based on your current implementation:
struct ShibeView: View {
#ObservedObject var fetcher = ShibeFetcher()
var body: some View {
Image(uiImage: UIImage(data: try! Data(contentsOf: URL(string: fetcher.shibe?.first ?? "https://i.imgur.com/epMSRQH.png")!)) ?? UIImage())
}
}
public class ShibeFetcher: ObservableObject {
#Published var shibe: [String]?
init(){
load()
}
func load() {
let url = URL(string: "http://shibe.online/api/shibes?")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let webData = try JSONDecoder().decode([String].self, from: d)
DispatchQueue.main.async {
self.shibe = webData
}
}else {
print("No Data")
}
} catch {
print ("Error here")
}
}.resume()
}
}