Swift Network URLSession
Swift Network URLSession
Sending and receiving Codable data with URLSession and SwiftUI
Loading an image from a remote server
import SwiftUI
struct InternetNetwork: View {
@StateObject var vm = ViewModel()
var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
contentOfMusic
}
} else {
NavigationView {
contentOfMusic
}
}
}
var contentOfMusic: some View {
Form {
TextField("Enter singer", text: $vm.inputForSearching)
.textFieldStyle(.roundedBorder)
Text(vm.url)
// We want that to be run as soon as our List is shown,
// but we can’t just use onAppear() here
// because that doesn’t know how to handle sleeping functions –
// it expects its function to be synchronous.
//
// task() - This can call functions
// that might go to sleep for a while;
// all Swift asks us to do is mark those functions
// with a second keyword, await,
// so we’re explicitly acknowledging that a sleep might happen.
List(vm.results, id: \.trackId) { result in
HStack {
// Music Info Display
VStack(alignment: .leading) {
Text(result.trackName)
.font(.headline)
Text(result.collectionName)
}
Spacer()
// Image View
AsyncImage(url: URL(string: result.artworkUrl100)) { image in
image
} placeholder: {
Color.gray
}
.frame(width: 100, height: 100)
}
}
.onChange(of: vm.inputForSearching) { _ in
Task {
// It's like do try which is made for error catching.
await vm.loadData(from: vm.url)
}
}
}
}
}
struct InternetNetwork_Previews: PreviewProvider {
static var previews: some View {
InternetNetwork()
}
}
// [SwiftUI] Publishing changes from background threads is not allowed;
// make sure to publish values from the main thread
// (via operators like receive(on:)) on model updates.
// see Extended Reading Materials
// SOLVED: Expression requiring global actor 'MainActor' cannot appear in default-value expression of property '_vm'; this is an error in Swift 6
// https://www.hackingwithswift.com/forums/swiftui/expression-requiring-global-actor-mainactor-cannot-appear-in-default-value-expression-of-property-vm-this-is-an-error-in-swift-6/13695
@MainActor
class ViewModel: ObservableObject {
@Published var results: [Result] = [Result]()
@Published var inputForSearching: String = "" {
// See Entended Reading Materials
// How to take action when a property changes
// https://www.hackingwithswift.com/quick-start/beginners/how-to-take-action-when-a-property-changes
didSet {
self.url = self.searchLinkGenerator(for: self.inputForSearching)
}
}
@Published var url: String = ""
static var shared = ViewModel()
// Rather than forcing our entire progress to stop
// while the networking happens,
// Swift gives us the ability to say
// “this work will take some time,
// so please wait for it to complete
// while the rest of the app carries on running as usual.”
//
// Notice the new async keyword in there –
// we’re telling Swift this function might want to go to sleep
// in order to complete its work.
func loadData(from url: String) async {
// 1. Creating the URL we want to read.
// This needs to have a precise format:
// “itunes.apple.com” followed by a series of parameters
// you can find the full set of parameters
// if you do a web search for “iTunes Search API”.
// In our case we’ll be using the search term
// “Taylor Swift” and the entity “song”.
// https://itunes.apple.com/search?term=taylor+swift&entity=song
guard let url = URL(string: url) else {
print("ERROR: guard let url error")
return
}
// 2. Fetching the data for that URL.
// Just as importantly, an error might also be thrown here
// – maybe the user isn’t currently connected to the internet.
do {
// public func data(from url: URL, delegate: URLSessionTaskDelegate? = nil) async throws -> (Data, URLResponse)
let (data, _) = try await URLSession.shared.data(from: url)
// 3. Decoding the result of that data into a Response struct.
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
results = decodedResponse.results
} else {
print("ERROR: decode fail")
}
} catch {
print("Invalid data")
}
}
func searchLinkGenerator(for name: String) -> String {
// https://itunes.apple.com/search?term=taylor+swift&entity=song
let linkBefore = "https://itunes.apple.com/search?term="
let linkAfter = "&entity=song"
return linkBefore + name.replacingOccurrences(of: " ", with: "+") + linkAfter
}
}
struct Response: Codable {
var results: [Result]
}
// To sum that up, a hashable is a type that has hashValue
// in the form of an integer that can be compared across different types.
struct Result: Hashable, Codable {
var trackId: Int
var collectionName: String
var trackName: String
var artworkUrl100: String
}
Extended Reading Materials
Hashable Protocols in Swift
SOLVED: Expression requiring global actor ‘MainActor’ cannot appear in default-value expression of property ‘_vm’; this is an error in Swift 6
URLSession.shared.data(from: url) fails in Swift Playgrounds for Mac
How to take action when a property changes