Data is missing from API call to Unsplashed using Swift [closed] - swift

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
Problem:
I was following a tutorial and I cannot seem to get the data loaded when I get a API call from Unsplashed. I registered for an account and used my personal key.
I created my loadData function that should load the data from the API call:
func loadData() {
let key = "my key"
let url = "https://api.unsplash.com/photos/random/?count=30&client_id=\(key)"
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { (data, _, error) in
guard let data = data else {
print("URLSession dataTask error", error ?? "nil")
return
}
print(data)
do{
let json = try JSONDecoder().decode([Photo].self,from: data)
print(json)
for photo in json {
DispatchQueue.main.async {
self.photoArray.append(photo)
}
}
}catch{
print("In error")
print(error.localizedDescription)
}
}.resume()
}
I present my data in the content view as follows:
import SwiftUI
struct ContentView: View {
#ObservedObject var randomImages = UnsplashData()
var body: some View {
Text("Hello, world!")
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I print the data before I use the JSON decoder and there seems to be data there, but when I go to get the JSON it states this error:
The data couldn’t be read because it is missing.
So is it the way I am using the API key? Or something else?
The photo structure is as follows:
struct Photo: Identifiable, Decodable {
var id: String
var alt_description: String
var urls: [String: String]
}

It's not the data that you're receiving, it's how you're decoding it that seems to be an issue.
For example, this simple playground example works just fine...
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
struct Sources: Decodable {
let raw: String
let full: String
let regular: String
let small: String
let thumbnail: String
}
struct Photo: Decodable {
let urls: [Sources]
}
let key = "don't post this, that's a bad idea"
let url = "https://api.unsplash.com/photos/random/?count=30&client_id=\(key)"
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { (data, _, error) in
guard let data = data else {
print("URLSession dataTask error", error ?? "nil")
return
}
print(String(data: data, encoding: .utf8))
do{
let json = try JSONDecoder().decode([Photo].self,from: data)
print(json)
}catch{
print("In error")
print(error.localizedDescription)
}
}.resume()
If you change print(error.localizedDescription) to print(error) you'll generally get a more accurate description of the error

Related

How to use sink to assign to an array

Working on a demo I switched to using Combine, but just cannot seem to find a way to assign the values that I get from json decoding in sink to point to an array , below is my code , as you can see in the commented out code using URLSession it was much easier …thanks
Currently I just see the default record
struct NewsItem: Decodable {
let id: Int
let title: String
let strap: String
let url: URL
let main_image: URL
let published_date: Date
static let shared = NewsItem(id: 0, title: "", strap: "", url: URL(string: "https://www.hackingwithswift.com/articles/239/wwdc21-wrap-up-and-recommended-talks")!, main_image: URL(string: "https://www.hackingwithswift.com/resize/300/uploads/wwdc-21#2x.jpg")!, published_date: Date())
}
struct CardView: View {
#State private var news = [NewsItem]()
#State private var request = Set<AnyCancellable>()
var body: some View {
List {
ForEach(news, id:\.id) { news in
Text(news.title)
Text("\(news.published_date)")
Link("Goto Link", destination: news.url)
AsyncImage(url: news.main_image)
.frame(width: 50, height: 50)
}
}
.onAppear {
Task {
await fetchData()
}
}
}
func fetchData() async {
let url = URL(string: "https://www.hackingwithswift.com/samples/headlines.json")!
// URLSession.shared.dataTask(with: url) { data, response, error in
// if let error = error {
// print(error.localizedDescription)
// } else if let data = data {
// let json = JSONDecoder()
//
// json.dateDecodingStrategy = .iso8601
// do {
// let user = try json.decode([NewsItem].self, from: data)
// news = user
// } catch {
// print(error.localizedDescription)
// }
// }
// }.resume()
URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: [NewsItem].self, decoder: JSONDecoder())
.replaceError(with: [NewsItem.shared])
.sink(receiveValue: { item in
news = item
})
.store(in: &request)
}
}
You are seeing the default output because you are replacing all errors. Use at least print to look at the error before replacing it.
Turned out the issue here was the decoding of the Date. Applying the proper decoding strategy fixed the issue.
func fetchData() async {
//create custom decoder and apply dateDecodingStrategy
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let url = URL(string: "https://www.hackingwithswift.com/samples/headlines.json")!
URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
// use the custom decoder
.decode(type: [NewsItem].self, decoder: decoder)
// if an error occures at least print it
.mapError({ error in
print(error)
return error
})
.replaceError(with: [NewsItem.shared])
.sink(receiveValue: { item in
news = item
})
.store(in: &request)
}
If you want to use Combine you need an ObservableObject and assign the end of the pipeline to an #Published var. When the object is deinit it will cancel the pipeline automatically.
The advantatage of async/await and .task is we don't need objects anymore and the task is cancelled when the UIView (that SwiftUI manages) dissapears.

How could you access the GitHub API in Swift?

I'd like to make an update detection system in my macOS SwiftUI app by pulling the latest release from GitHub via the API and then comparing the tag. How would I go about accessing the API from Swift? I've tried using the methods from here, medium.com, here, swifttom.com and here, steveclarkapps.com but none of them accomplish what I'm trying to do.
For the first method, the code functions with the provided example API, but doesn't work with the GitHub API and it returns this error instead:
Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
Method 2 suffers the same issue.
I couldn't even get enough of method 3's code working to try it.
Here's my adapted code based off of the medium.com method:
Model.swift
import Foundation
struct TaskEntry: Codable {
let id: Int
let tag_name: String
let name: String
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State var results = [TaskEntry]()
var body: some View {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.name)
}
}.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://api.github.com/repos/NCX-Programming/RNGTool/releases/latest") 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 response = try! JSONDecoder().decode([TaskEntry].self, from: data) /*{*/
DispatchQueue.main.async {
self.results = response
}
return
/*}*/
}
}.resume()
}
}
Commented out code and variable names that seem irrelevant are just leftovers.
OS: macOS Big Sur 11.6
Xcode version: 13.0
Open this in your browser:
https://api.github.com/repos/NCX-Programming/RNGTool/releases/latest
You will notice it is not an array but an object. You should be decoding an object like this:
JSONDecoder().decode(TaskEntry.self, from: data)
Edit:
This requires you to change your view. Notice this is no longer a List because you are no longer fetching an array but a single item:
struct TaskEntry: Codable {
let id: Int
let tagName: String
let name: String
}
struct ContentView: View {
#State var entry: TaskEntry? = nil
var body: some View {
VStack(alignment: .leading) {
if let entry = entry {
Text("\(entry.id)")
Text(entry.name)
Text(entry.tagName)
} else {
ProgressView()
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://api.github.com/repos/NCX-Programming/RNGTool/releases/latest") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
// TODO: Handle data task error
return
}
guard let data = data else {
// TODO: Handle this
return
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let response = try decoder.decode(TaskEntry.self, from: data)
DispatchQueue.main.async {
self.entry = response
}
} catch {
// TODO: Handle decoding error
print(error)
}
}.resume()
}
}
NOTICE: I did some other improvements as well
Use JSONDecoder to convert from snake case to camel case
Added do catch block so your app doesn't crash
Check for errors before decoding
Added loading indicator (had to put something in the else)
However,
As our discussion you are probably calling the wrong endpoint. That endpoint is not returning an array but a single object, you can tell this because the JSON response begins with { rather than [
I've adjusted my answer to change the endpoint I believe you should be calling:
struct TaskEntry: Codable {
let id: Int
let tagName: String
let name: String
}
struct ContentView: View {
#State var results: [TaskEntry]? = nil
var body: some View {
if let results = results {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.name)
}
}
} else {
VStack(alignment: .leading) {
ProgressView()
.onAppear(perform: loadData)
}
}
}
func loadData() {
guard let url = URL(string: "https://api.github.com/repos/NCX-Programming/RNGTool/releases") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
// TODO: Handle data task error
return
}
guard let data = data else {
// TODO: Handle this
return
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let response = try decoder.decode([TaskEntry].self, from: data)
DispatchQueue.main.async {
self.results = response
}
} catch {
// TODO: Handle decoding error
print(error)
}
}.resume()
}
}

How Xcode simulator is fetching data from internet when the internet connection is OFF?

I have created a simple SwiftUI app, which should display a list of songs fetched from iTunes url link:
import SwiftUI
struct Response: Codable {
var results: [Result]
}
struct Result: Codable {
var trackId: Int
var trackName: String
var collectionName: String
}
struct ContentView: View {
#State private var results = [Result]()
var body: some View {
List(results, id: \.trackId) { item in
VStack(alignment: .leading) {
Text(item.trackName)
.font(.headline)
Text(item.collectionName)
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song") 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 ?? "Unknown error")")
}.resume()
}
}
When the internet connection on my Mac is On, I am able to fetch the data and load it to the list. I decided to turn it off, then I completely closed/deleted the app from memory then opened it app again and the data was still there. This is very confusing for me, can somebody explain me what is happening, because the code should load the data only when the Wi-Fi is ON, why I am still able to see it when it is OFF?
Short video of my simulator
My question is, why here is gone?
Just try to use .reloadIgnoringLocalCacheData. It worked for me.
URLSession.shared caches data by default if you don't change the configuration. Just edit the request to
let request = URLRequest(url: url, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData)

Fetching and decoding unkwown error in Swift [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I'm trying to fetch the data and decode it into structs, and then display that data in a list.
I used quicktype.io to parse JSON to Swift. This part seems to be okay.
struct Response: Codable {
var results: [User]
}
struct User: Codable {
let id: String
let isActive: Bool
let name: String
let age: Int
let company, email, address, about: String
let registered: Date
let tags: [String]
let friends: [Friend]
}
struct Friend: Codable {
let id, name: String
}
This is the rest of the code, where I try to decode the data(where the error occurs). Here is a link to the JSON.
struct ContentView: View {
#State private var users = [User]()
var body: some View {
List {
ForEach(users, id: \.id) { user in
Text(user.name)
}
}.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) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
print(decodedResponse.results)
users = decodedResponse.results
}
// everything is good, so we can exit
return
}
}
// if we're still here it means there was a problem
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
You have to decode on type [User] not Response. Look at the response and you'll see that the top-level JSON is an Array, not a Dictionary. Also, update your code to get meaningful error messages. Use do try catch syntax instead of if let try?.
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 {
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let users = try decoder.decode([User].self, from: data)
DispatchQueue.main.async {
print(users)
self.users = users
}
} catch {
print(error)
}
} else {
// if we're still here it means there was a problem
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}
}.resume()
}
Update: Also, need to add dateDecodingStrategy to make the decoding work correctly.

Error when parsing JSON data (Swift 4 Playground)

My Swift Playground keeps returning
Error: The data couldn't be read because it isn't in the correct
format."
and I can't figure out what I'm doing wrong. Below is my code.
JSON Sample Data:
{
"meta": {
"name":"Tour of Honor Bonus Listing",
"version":"18.1.4"
},
"bonuses": [
{
"bonusCode":"AZ1",
"category":"ToH",
"name":"Anthem Tour of Honor Memorial",
"value":1,
"city":"Anthem",
"state":"AZ",
"flavor":"Flavor Text Goes Here"
}
]
}
Playground Code:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
struct JsonFile: Codable {
struct Meta: Codable {
let name: String
let version: String
}
struct JsonBonuses: Codable {
let bonusCode: String
let category: String
let name: String
let value: Int
let city: String
let state: String
let flavor: String
}
let meta: Meta
let bonuses: [JsonBonuses]
}
let url = URL(string: "http://www.tourofhonor.com/BonusData.json")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error: \(error.localizedDescription)")
PlaygroundPage.current.finishExecution()
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print("Error: invalid HTTP response code")
PlaygroundPage.current.finishExecution()
}
guard let data = data else {
print("Error: missing data")
PlaygroundPage.current.finishExecution()
}
// feel free to uncomment this for debugging data
// print(String(data: data, encoding: .utf8))
do {
let decoder = JSONDecoder()
let posts = try decoder.decode([JsonFile].self, from: data)
print(posts.map { $0.meta.name })
PlaygroundPage.current.finishExecution()
}
catch {
print("Error: \(error.localizedDescription)")
PlaygroundPage.current.finishExecution()
}
}.resume()
I assume I have something in my Struct incorrect, but I can't figure out what it is.
(This paragraph is to make the submission tool happy because it says I have too much code and not enough other details. Apparently being direct and succinct is not compatible with the submission scanning function).
The struct is correct but the root object is not an array (remove the brackets)
let posts = try decoder.decode(JsonFile.self, from: data)
print(posts.bonuses.map{$0.name})