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

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)

Related

What is wrong with this API call for SwiftUI

I have been trying to make an API call with swiftui, but I keep running into threading errors when I run the code. This is the current program:
import SwiftUI
struct Post: Codable, Identifiable {
var id = UUID()
var title: String
var body: String
}
class Api {
func getPosts(){
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return}
URLSession.shared.dataTask(with: url) { data, _, _ in
let posts = try! JSONDecoder().decode([Post].self, from: data!)
print(posts)
}
.resume()
}
}
// Content view file
import SwiftUI
struct PostList: View {
var body: some View {
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
.onAppear{
Api().getPosts()
}
}
}
struct PostList_Previews: PreviewProvider {
static var previews: some View {
PostList()
}
}
I got this code verbatim from a swift tutorial, but I am getting errors from it. Any help would be greatly appreciated!
the problem happen because in this line:
let posts = try! JSONDecoder().decode([Post].self, from: data!)
you are making force unwrap try! and swift can't decode your data into [Post] because your model is wrong, change for this:
struct Post: Codable {
var userId: Int
var id: Int
var title: String
var body: String
}
your app will compile, and please avoid to use force unwrap.
To "fix" your error, use var id: Int in your Post model.
Also use the following code, that is more robust than the tutorial code you have been using: see this SO post: Preview Crashed when I get data from api
struct ContentView: View {
#State var posts: [Post] = []
var body: some View {
VStack {
List(posts) { post in
Text(post.title)
}
.onAppear{
Api.shared.getPosts { posts in // <-- here
self.posts = posts
}
}
}
}
}
struct Post: Codable, Identifiable {
var id: Int // <-- here
var title: String
var body: String
}
class Api {
static let shared = Api() // <-- here
func getPosts(completion: #escaping([Post]) -> ()) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return
print("bad url")
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { // <-- here
print("no data") // todo deal with no data
completion([])
return
}
do {
let posts = try JSONDecoder().decode([Post].self, from: data)
DispatchQueue.main.async {
completion(posts)
}
print(posts)
} catch {
print("\(error)")
}
}
.resume()
}
}

Google Books API GET Request returns blank screen

Good day, All
So I am learning/practicing Network calls. I came across a video by Paul Hudson where he makes a call to the Itunes API using the same code I am trying to use here. However, I am trying to make a call to the Google Books API. My call (code below) is not working, it (returns a blank screen) I am not sure why. I am of course using variables from the URL I am trying to call/make a request from.
import SwiftUI
struct Response: Codable {
var results: [Result]
}
struct Result: Codable {
var id: Int
var etag: String
}
struct ContentView: View {
#State private var results = [Result]()
var body: some View {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.etag)
}
}
.task {
await loadData()
// ????
}
}
func loadData() async {
guard let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=flowers+inauthor:keyes") else {
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
results = decodedResponse.results
}
} catch {
print("Invalid data")
}
}
}
As previously mentioned, this code was tested using the Itunes API and it worked flawlessly. I am not sure what is causing the issue or what can fix it. I will keep searching and practicing on my end.
Thank you!
the reason your are getting a blank screen, is because your Response and Result struct do not match the json data you get from
the api. Look carefully at the json data and you will see the difference. Try something like this:
struct Response: Codable {
let items: [Result] // <--- here
}
struct Result: Codable, Identifiable { // <--- here
var id: String // <--- here
var etag: String
}
struct ContentView: View {
#State private var results = [Result]()
var body: some View {
List(results) { item in // <--- here
VStack(alignment: .leading) {
Text(item.etag)
}
}
.task {
await loadData()
}
}
func loadData() async {
guard let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=flowers+inauthor:keyes") else {
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
results = decodedResponse.items // <--- here
}
} catch {
print("Invalid data")
}
}
}

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

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

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

SwiftUI textView onChange not working properly

I was making a search bar with onchange in which it recieve some data for every change. But sometime when I type fast the working is not proper. I think I need to use debounce here. I've tried and fails (beginner to swift)
Here is my code :
struct Result: Codable{
var searchResult :[Real]
}
struct Real: Codable{
var _id : String
var name : String
}
struct ContentView: View { #State private var text: String = ""
#State private var isEditing = false
#State private var results = [Result]()
#State private var real = [Real]()
var body: some View {
VStack(alignment: .leading){
HStack {
TextField("Search ...", text: $text)
.onChange(of: text) {
guard let url = URL(string: "https://'api link'")else{
print("inavlid url")
return
}
let json: [String: Any] = ["searchKey":text]
let jsonData = try? JSONSerialization.data(withJSONObject: json)
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = jsonData
URLSession.shared.dataTask(with: request){
data,response,error in
if let data = data{
do {
let decodedResponse = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
self.real = decodedResponse.searchResult
}
print("tommtoow")
} catch let jsonError as NSError {
print("JSON decode failed: \(jsonError)")
}
return
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
print($0)
}
}
}
}
}
}
}
Try to implement a general API request with a method .onAppear(perform: ) and onChange can be used after to filter results
with "onChange" you do your request every time you type something, hence your issue. What you probably want is to use "onSubmit" (ios15) or "onCommit" (will be deprecated), that way you do your request only when you are finished typing it in.