Reading single string of json in swift ui - json

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

Related

Swift - Creating a stack (Text view) from a function that returns an array

I am trying to create a list of text objects out of a function that returns an array of params. Everything seems to be working fine, getting the data, console shows the correct results, except the list itself which remains empty.
The function call:
import UIKit
import SwiftUI
struct SubdomainsList: View {
#State var SubDomains = VTData().funky(XDOMAIN: "giphy.com")
var body: some View {
VStack {
List{
Text("Subdomains")
ForEach(SubDomains, id: \.self) { SuDo in
Text(SuDo)
}
}
}
}
}
struct SubdomainsList_Previews: PreviewProvider {
static var previews: some View {
SubdomainsList()
}
}
The Json handlers:
struct VTResponse: Decodable {
let data: [VT]
}
struct VT: Decodable {
var id: String
}
The class:
class VTData {
func funky (XDOMAIN: String) -> Array<String>{
var arr = [""]
getDATA(XDOMAIN: "\(XDOMAIN)", userCompletionHandler: { (SubDomain) in
print(SubDomain)
arr.append(SubDomain)
return SubDomain
})
return arr
}
func getDATA(XDOMAIN: String, userCompletionHandler: #escaping ((String) -> String)) {
let token = "<TOKEN>"
guard let url = URL(string: "https://www.lalalla.com/subdomains") else {fatalError("Invalid URL")}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "x-apikey")
let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
guard let data = data else { return }
let decoder = JSONDecoder()
let result = try? decoder.decode(VTResponse.self, from: data)
if let result = result {
for SubDo in result.data {
let SubDomain = SubDo.id
userCompletionHandler(SubDomain)
}
}
else {
fatalError("Could not decode")
}
})
task.resume()
}
}
I'm getting no errors whatsoever, and the console output shows the correct results:
support.giphy.com
cookies.giphy.com
media3.giphy.com
i.giphy.com
api.giphy.com
developers.giphy.com
media.giphy.com
x-qa.giphy.com
media2.giphy.com
media0.giphy.com
It is also worth mentioning that when I add print(type(of: SubDomain)) to the code I'm getting a String rather than an array.
The preview:
preview
Any ideas?
try this approach, again, to extract the list of subdomain from your API, and display them in
a List using the asynchronous function getDATA(...):
class VTData {
// `func funky` is completely useless, remove it
func getDATA(XDOMAIN: String, completion: #escaping ([String]) -> Void) { // <-- here
let token = "<TOKEN>"
guard let url = URL(string: "https://www.virustotal.com/api/v3/domains/\(XDOMAIN)/subdomains") else {
print("Invalid URL")
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "x-apikey")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return } // todo return some error msg
do {
let results = try JSONDecoder().decode(VTResponse.self, from: data)
return completion(results.data.map{ $0.id }) // <-- here
} catch {
print(error) // <-- here important
}
}.resume()
}
}
struct SubdomainsList: View {
#State var subDomains: [String] = [] // <--- here
var body: some View {
VStack {
List{
Text("Subdomains")
ForEach(subDomains, id: \.self) { SuDo in
Text(SuDo)
}
}
}
.onAppear {
// async function
VTData().getDATA(XDOMAIN: "giphy.com") { subs in // <--- here
subDomains = subs
}
}
}
}

SwiftUI List and URLSession + JSONDecode

hi everyone I'm newbie in Swift
I ask for help why the data is not displayed in List? Please help me a fix it.
I make a Model Data, and take a data from URl and parse it
My model:
struct ResponseData: Codable{
var userId: String
var id: String
var title: String
var body: String
}
My code in Content view:
import SwiftUI
struct ContentView: View {
#State var responseData = [ResponseData]()
var body: some View {
List(responseData, id: \.id ) { item in
Text("\(item.body)")
}
.task {
await loadData()
}
}
func loadData () async {
let urlString = "https://jsonplaceholder.typicode.com/posts"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {return}
guard error == nil else {return}
do {
let postData = try JSONDecoder().decode(ResponseData.self, from: data)
} catch let error {
print(error)
}
}.resume()
}
}
There are many issues in the code.
The most significant ones are
In the struct userId and id are Int.
The JSON root object is an array, please note the starting [.
You don't update the model after receiving the data.
As you are using async/await anyway take full advantage of the pattern.
The JSON API returns posts, so how about to name the struct Post?
struct Post: Codable, Identifiable {
let userId: Int
let id: Int
let title: String
let body: String
}
struct ContentView : View {
#State var posts = [Post]()
var body : some View {
List(posts) { post in
Text(post.body)
}
.task {
do {
posts = try await loadData()
} catch {
print(error)
}
}
}
func loadData() async throws -> [Post] {
let urlString = "https://jsonplaceholder.typicode.com/posts"
let url = URL(string: urlString)!
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url))
return try JSONDecoder().decode([Post].self, from: data)
}
}

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

swiftUI / Xcode 12 fetch data from server

I'm very new to swiftUI and have been working through the landscapes app tutorial.
I have been trying to switch the data source from a bundled JSON file to a remote JSON source but have so far been lost on how to integrate what I've learnt about the URLSession with the tutorials load code.
Apple's code:
final class ModelData: ObservableObject {
#Published var landmarks: [Landmark] = load("landmarkData.json")
// #Published var landmarks: [Landmark] = apiCall.getLocations(locations)
}
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
What I have to load from the remote source:
struct Location: Codable, Identifiable {
let id = UUID()
let country: String
let name: String
}
class apiCall {
func getLocations(completion:#escaping ([Location]) -> ()) {
guard let url = URL(string: "https://overseer.cyou/heritage/heritageData.json") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let locations = try! JSONDecoder().decode([Location].self, from: data!)
print(locations)
DispatchQueue.main.async {
completion(locations)
}
}
.resume()
}
}
Can anyone show me how I go about doing this, ideally from a complete beginners point of view?
// framework support
import SwiftUI
import Combine
// List view setup
struct LocationsView: View {
#ObservedObject var viewModel = LocationModel()
var body: some View {
List(viewModel.locations) { location in
HStack {
VStack(alignment: .leading) {
Text(location.name)
.font(.headline)
Text(location.country)
.font(.subheadline)
}
}
}
}
}
// Location model
struct Location: Codable, Identifiable {
var id = UUID()
let country: String
let name: String
let locationId: Int = 0
enum CodingKeys: String, CodingKey {
case locationId = "id"
case country
case name
}
}
// Location view model class
class LocationModel: ObservableObject {
#Published var locations: [Location] = []
var cancellationToken: AnyCancellable?
init() {
getLocations()
}
}
extension LocationModel {
func getLocations() {
cancellationToken = self.request("https://overseer.cyou/heritage/heritageData.json")?
.mapError({ (error) -> Error in
print(error)
return error
})
.sink(receiveCompletion: { _ in },
receiveValue: {
self.locations = $0
})
}
// API request
private func request(_ path: String) -> AnyPublisher<[Location], Error>? {
guard let url = URL(string: path)
else { return nil }
let request = URLRequest(url: url)
return apiCall.run(request)
.map(\.value)
.eraseToAnyPublisher()
}
}
// API setup
struct apiCall {
struct Response<T> {
let value: T
let response: URLResponse
}
static func run<T: Decodable>(_ request: URLRequest) -> AnyPublisher<Response<T>, Error> {
return URLSession.shared
.dataTaskPublisher(for: request)
.tryMap { result -> Response<T> in
let value = try JSONDecoder().decode(T.self, from: result.data)
return Response(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}

SwiftUI: Display JSON Data with Text() instead of List

I have got a problem with displaying JSON data on a SwiftUI View.
I tried several tutorials and read articles which are related to my problem, but nothing seems appropriate enough.
For example everyone displays JSON data from an API with a fancy list or pictures, but I only want to know how you can simply display one word on a view (without List{}).
I chose the PokeAPI to figure out how to display "Hi Charmander" with the Text() instruction.
Example of a list (and without ObservableObject and #Published)
I want to get rid of the List and use sth. Text(resultsVar[0].name).onAppear(perform: loadData) like instead
import SwiftUI
struct pokeRequest:Codable {
var results: [Result]
}
struct Result:Codable {
var name:String
}
struct ViewOne: View {
#State var resultsVar = [Result]()
var body: some View {
VStack{
//unfortunately this does not work:
//Text(resultsVar[0].name).onAppear(perform: loadData)
List(resultsVar, id: \.name) { item in
VStack(alignment: .leading) {
Text("Hi \(item.name)")
}
}
.onAppear(perform: loadData)
}
}
func loadData(){
guard let url = URL(string: "https://pokeapi.co/api/v2/pokemon?offset=3&limit=3") 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(pokeRequest.self, from: data) {
DispatchQueue.main.async {
self.resultsVar = decodedResponse.results
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
struct ViewOne_Previews: PreviewProvider {
static var previews: some View {
ViewOne()
}
}
Second try with a different approach (without .onAppear())
In this approach I tried with class: Observable Object and #Published but I also didn't come to my wished UI-output.
import SwiftUI
struct pokeRequest2:Codable {
var results2: [pokeEach2]
}
struct pokeEach2:Codable {
var name2:String
}
class Webservice:ObservableObject {
#Published var pokeInfo: [pokeRequest2] = [pokeRequest2]()
func decodepokemon() {
let session = URLSession.shared
let url = URL(string: "https://pokeapi.co/api/v2/pokemon?offset=3&limit=3")!
let task = session.dataTask(with: url) { data, response, error in
if error != nil || data == nil {
print("Client error!")
return
}
guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
print("Server error!")
return
}
guard let mime = response.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}
do {
let response = try JSONDecoder().decode(pokeRequest2.self, from: data!)
print(self.pokeInfo[0].results2[0].name2)
DispatchQueue.main.async {
self.pokeInfo = [response]
}
} catch {
print("JSON error: \(error.localizedDescription)")
}
}.resume()
}
init() {
decodepokemon()
}
}
struct ViewTwo: View {
#ObservedObject var webservice: Webservice = Webservice()
var body: some View {
Text("please help")
//Does also not work: Text(self.webservice.pokeInfo2[0].results2[0].name2)//.onAppear()
//Since a few minutes somehow the debug area prints "JSON error: The data couldn’t be read because it is missing." instead of "charmander"
}
}
struct ViewTwo_Previews: PreviewProvider {
static var previews: some View {
ViewTwo()
}
}
I tried several tutorials and read articles which are related to my problem, but nothing seems appropriate enough.
I would highly appreciate any help :-)
Thanks in advance!
I may be misunderstanding the question but in SwiftUI text can be displayed as follows:
Text("Hi" + item.name)
But as I say I'm not sure if that's the question.
As you will dynamically change the list items, you have to use .id() modifier. And in order to use .id(), the result must conforms to Hashable. The following code can help you solve the problem.
struct Result:Codable, Hashable {
var name:String
}
struct ViewOne: View {
#State var resultsVar = [Result]() // [Result(name: "firsy"),Result(name: "second"), ]//[Result]()
var body: some View {
VStack{
Spacer()
//unfortunately this does not work:
//Text(resultsVar[0].name).onAppear(perform: loadData)
List(resultsVar, id: \.name) { item in
VStack(alignment: .leading) {
Text("Hi \(item.name)")
}
}.id(resultsVar)
}.onAppear(perform: loadData)
I am by no means an expert (started using Swift less than three days ago) but I think I might have what you are looking for. This enables you to call a simple REST API that contains only a simple dictionary (in my case the dictionary contains "Differential": 5.22) and then display that number as text when a button is pressed. Hope the following is helpful!
struct DifferentialData: Decodable {
var Differential: Double
}
struct ContentView: View {
#State var data = DifferentialData(Differential: 0.00)
func getData() {
guard let url = URL(string: "Enter your URL here")
else { return } //Invalid URL
URLSession.shared.dataTask(with: url) { data, response, error in
let differentials = try! JSONDecoder().decode(DifferentialData.self, from: data!)
print(differentials)
DispatchQueue.main.async {
self.data = differentials
}
}
.resume()
}
var body: some View {
VStack{
Text("\(data.Differential)")
Button("Refresh data") {self.getData()}
}
}
}