Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to SwiftUI V2

Introduction to SwiftUI V2

Jussi Pohjolainen

April 23, 2024
Tweet

More Decks by Jussi Pohjolainen

Other Decks in Technology

Transcript

  1. Different Approaches Swift / Objective-C UIKit + Storyboards / XIB

    UIKit + Just Code (Snapkit/Purelayout) SwiftUI Swift Learn the language first!
  2. UIKit + Storyboards / XIB • Autolayout can be frustrating

    • Version control can be cumbersome • Debug can be hard • Use several storyboards! Split your app. • Good approach for small apps • Number of developers... 1?
  3. UIKit + Code Everything • "Full control of everything" •

    Use libraries like snapkit or purelayout for easier coding • Good approach if you want control every aspect • But this will require lot of coding!
  4. SwiftUI • The future of Apple Devices development • It's

    cross platform! The UI code should be reusable on all devices. • No backward compatibility (only iOS 13 +) • Hot Reload can be frustrating • Much less documentation available • Good approach to new apps • Way easier than storyboards
  5. SceneFile import SwiftUI @main struct SwiftUiAppApp: App { var body:

    some Scene { WindowGroup { ContentView() } } } Starting point of our app App protocol forces body property
  6. Lifecycle management • Scene phases • SwiftUI introduces the concept

    of scene phases which you can use to perform actions at specific points in the lifecycle of your app's UI • Scene? Like Appllication window • If you need to integrate with UIKit for handling more complex app lifecycle events or legacy codebases, you can use the @UIApplicationDelegateAdaptor property
  7. Scene • Each scene has its own lifecycle, meaning it

    can be created, enter the foreground, move to the background, and be destroyed independently of other scenes. This is similar to how individual windows in desktop applications can be opened, minimized, or closed independently. • The scene concept is especially powerful on iPadOS, where users can utilize the Split View and Slide Over features to work with multiple instances of the same app • Scenes also facilitate extending an app's UI to external displays.
  8. SceneFile @main struct MyScene: App { var body: some Scene

    { #if os(macOS) WindowGroup { Text("Mac") } #else WindowGroup { Text("iOS") } #endif } }
  9. Only two files! import SwiftUI @main struct SwiftUIAppLifecycleApp: App {

    var body: some Scene { WindowGroup { ContentView() } } } import SwiftUI struct ContentView: View { var body: some View { Text("Hello, world!") .padding() } }
  10. SwiftUI App Protocol import SwiftUI @main struct SwiftUIAppLifecycleApp: App {

    var body: some Scene { WindowGroup { ContentView() } } } Starting point of our app
  11. SwiftUI App Protocol import SwiftUI @main struct SwiftUIAppLifecycleApp: App {

    var body: some Scene { WindowGroup { ContentView() } } } App protocol requires that struct has body type of "some Scene"
  12. App Delegate import SwiftUI class AppDelegate: NSObject, UIApplicationDelegate { func

    application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { NSLog("Ready to go!") return true } } @main struct SwiftUIAppLifecycleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } }
  13. SwiftUI App Protocol import SwiftUI @main struct SwiftUIAppLifecycleApp: App {

    var body: some Scene { WindowGroup { ContentView() } } } WindowGroup is container scene that wraps SwiftUI Views. Running this on iPad you can create multiple WindowGroup scenes
  14. .\variable? struct Address { var city: String } struct Person

    { var address: Address } let jack = Person(address: Address(city: "New York")) let cityKeyPath : KeyPath<Person, String> = \Person.address.city let city = jack[keyPath: cityKeyPath] // Accesses 'city' property of 'address' of 'person'
  15. Listening to Scene changes @main struct tuhoaApp: App { @Environment(\.scenePhase)

    private var scenePhase var body: some Scene { WindowGroup { ContentView() .onChange(of: scenePhase) { switch scenePhase { case .active: print("App is active") case .inactive: print("App is inactive") case .background: print("App is in the background") default: break } } } } } EnvironmentValues.scenePhase
  16. Content View import SwiftUI struct ContentView: View { var body:

    some View { Text("Hello") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } Your UI For Live Preview
  17. Struct vs Classes? • Notice that the Content View uses

    struct over classes • Struct is value type • Struct does not have inheritance • Can conform to a protocol • UIKit • Every ui component (class) inherites UIView • UIView is a mammoth class containing several properties • For example UIStackView inherites UIView which contains background color that UIStackView does not use! • SwiftUI • Trivial structs -> speed gain
  18. import SwiftUI struct ContentView: View { var body = ...

    } Conforms to protocol View, This forces to have the var body!
  19. import SwiftUI struct ContentView: View { var body = VStack(content:

    { Text("moi") Text("Hei") }) } Using closures
  20. import SwiftUI struct ContentView: View { var body = VStack

    { Text("moi") Text("Hei") } } Using trailing lambda
  21. VStack init init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil,

    content: () -> Content) You can pass a function that returns Content
  22. Content? • Content is "Swift Magic" • It is a

    generic type that SwiftUI magically determines for you • Notice that VStack has init • struct VStack<Content> where Content : View • So when you create VStack it has generics • For example • VStack<TupleView<(Text, Text)>> • Now VStack type is determined so that it may hold two Text objects
  23. Without Closures, without "magic" import SwiftUI func f() -> TupleView<(Text,

    Text)> { let content : TupleView<(Text, Text)> = TupleView<(Text,Text)>((Text("a"), Text("b"))) return content } struct ContentView: View { var body : HStack<TupleView<(Text, Text)>> = HStack(content: f) }
  24. Using closure struct ContentView: View { var body : HStack<TupleView<(Text,

    Text)>> = HStack(content: { let content : TupleView<(Text, Text)> = TupleView<(Text,Text)>((Text("a"), Text("b"))) return content }) }
  25. Using trailing closure struct ContentView: View { var body :

    HStack<TupleView<(Text, Text)>> = HStack() { let content : TupleView<(Text, Text)> = TupleView<(Text,Text)>((Text("moi"), Text("hei"))) return content } }
  26. Omit Type struct ContentView: View { var body : HStack<TupleView<(Text,

    Text)>> = HStack() { let content = TupleView<(Text,Text)>((Text("moi"), Text("hei"))) return content } }
  27. Default return struct ContentView: View { var body : HStack<TupleView<(Text,

    Text)>> = HStack() { TupleView<(Text,Text)>((Text("moi"), Text("hei"))) } }
  28. ViewBuilder struct ContentView: View { var body : HStack<TupleView<(Text, Text)>>

    = HStack() { ViewBuilder.buildBlock(Text("moi"), Text("hei")) } }
  29. ViewBuilder struct ContentView: View { var body : HStack<TupleView<(Text, Text)>>

    = HStack { ViewBuilder.buildBlock(Text("moi"), Text("hei")) } }
  30. This works also, what?? struct ContentView: View { var body

    : HStack<TupleView<(Text, Text)>> = HStack { //TupleView<(Text,Text)>((Text("moi"), Text("hei"))) Text("moi") Text("hei") } }
  31. "function builder" feature struct ContentView: View { var body :

    HStack<TupleView<(Text, Text)>> = HStack() { //Text("moi") //Text("hei") let view : TupleView<(Text,Text)> = ViewBuilder.buildBlock(Text("moi"), Text("hei")) return view } } By using ViewBuilder it will build the generic type for you
  32. Anonymous Function Call var x : Int { return 5

    } Invokes anonymous function and return value will be in 5
  33. Anonymous Function Calls struct ContentView: View { var body :

    HStack<TupleView<(Text, Text)>> { let hstack = HStack { Text("a") Text("b") } return hstack } } Anonymous function Uses function builder
  34. Anonymous Function Calls struct ContentView: View { var body :

    HStack<TupleView<(Text, Text)>> { HStack { Text("a") Text("b") } } } Default return
  35. Anonymous Function Calls struct ContentView: View { var body :

    HStack<TupleView<(Text, Text, Button<Text>)>> { HStack { Text("a") Text("b") Button("click") { print(type(of: self.body)) } } } } We will have to change the type!
  36. Using trailing lambda in Constructor struct ContentView: View { var

    body : some View { HStack { Text("a") Text("b") Button("click") { print(type(of: self.body)) } } } } It will automatically determinate the type! "one specific type that conforms to the View protocol, but we don’t want to say what."
  37. Simple Button struct ContentView: View { var body: some View

    { Button("Click", action: { print("clicked") }) } }
  38. Mutating value struct ContentView: View { var counter = 0

    var body: some View { Text("\(counter)") Button("Click", action: { counter = counter + 1 }) } } By default structs are immutable! Error!
  39. State • SwiftUI manages the storage of any property you

    declare as a state • @State private var counter = 0 • When the state value changes, view refreshes • Access state properties only within the view; declare them as private • It is safe to mutate state properties from any thread
  40. Fix struct ContentView: View { @State private var counter =

    0 var body: some View { Text("\(counter)") Button("Click", action: { counter = counter + 1 }) } }
  41. Button Example struct ContentView: View { @State private var counter

    = 0 var body: some View { Text("\(counter)") .fontWeight(.bold) .font(.title) .padding() Button(action: { counter += 1 }, label: { Text("Click Me!") .fontWeight(.bold) .font(.title) .padding() .background(Color.purple) .cornerRadius(40) .foregroundColor(.white) }) } }
  42. Trailing Closure struct ContentView: View { @State private var counter

    = 0 var body: some View { Text("\(counter)") .fontWeight(.bold) .font(.title) .padding() Button(action: { counter += 1 }) { Text("Click Me!") .fontWeight(.bold) .font(.title) .padding() .background(Color.purple) .cornerRadius(40) .foregroundColor(.white) } } }
  43. TextField struct ContentView: View { @State private var name =

    "" var body: some View { let binding : Binding<String> = Binding( get: { self.name }, set: { self.name = $0 } ) TextField("Enter username...", text: binding) .padding() Text("Your name is \(name)") .padding() } } State variable here must be wrapped inside of Binding<String>
  44. TextField struct ContentView: View { @State private var name =

    "" var body: some View { TextField("Enter username...", text: $name) .padding() Text("Your name is \(name)") .padding() } } Automatically creates the Binding!
  45. SwiftUI Layouts • HStack – horizontal stacking • VStack –

    vertical stacking • ZStack – views on top of each other • ScrollView – horizontal and vertical scrolling • ..
  46. HStack Example struct ContentView: View { @State private var name

    = "" var body: some View { HStack(alignment: .center) { Text("Username:") .font(.callout) .bold() TextField("Enter username...", text: $name) .textFieldStyle(RoundedBorderTextFieldStyle()) }.padding() } }
  47. HStack Example Button(action: { print("Delete tapped!") }) { HStack {

    Image(systemName: "trash") .font(.title) Text("Delete") .fontWeight(.semibold) .font(.title) } .padding() .foregroundColor(.white) .background(Color.red) .cornerRadius(40) }
  48. TabView struct ContentView: View { var body: some View {

    TabView { Text("The content of the first view") .tabItem { Image(systemName: "phone.fill") Text("First Tab") } Text("The content of the second view") .tabItem { Image(systemName: "tv.fill") Text("Second Tab") } } } }
  49. Creating own views struct ContentView: View { var body: some

    View { TabView { RedView() .tabItem { Image(systemName: "phone.fill") Text("Hello") } BlueView() .tabItem { Image(systemName: "tv.fill") Text("World") } } } }
  50. Own Views struct RedView: View { var body: some View

    { ZStack() { Color.red Text("Hello") .foregroundColor(Color.white) } } } struct BlueView: View { var body: some View { ZStack() { Color.blue Text("World") .foregroundColor(Color.white) } } }
  51. Navigation struct ContentView: View { var body: some View {

    NavigationView { NavigationLink(destination: Text("Second View")) { Text("Hello, World!") }.navigationBarTitle("Navigation") } } }
  52. ForEach • ForEach in SwiftUI is a view • Can

    return it directly from your view body. • Provide it an array of items • You may also need to tell SwiftUI how it can identify each of your items uniquely so it knows how to update them when values change
  53. Example struct ContentView: View { var body: some View {

    VStack() { ForEach((1..<10), content: { value in Text("\(value)") }) } } } Range (not ClosedRange)
  54. Example: Using Trailing Closure struct ContentView: View { var body:

    some View { VStack() { ForEach((1..<10)) { Text("\($0)") } } } } init(Range<Int>, content: (Int) -> Content)
  55. ForEach: Another Example struct ContentView: View { let colors: [Color]

    = [.red, .green, .blue] var body: some View { VStack() { ForEach(colors) { Text("\($0.description)") .padding() .background($0) .cornerRadius(20) } } } } Referencing initializer 'init(_:content:)' on 'ForEach' requires that 'Color' conform to 'Identifiable'
  56. ForEach: Solution struct MyColor: Identifiable { var color : Color

    var id = UUID() } struct ContentView: View { let colors: [MyColor] = [MyColor(color: .red), MyColor(color: .blue), MyColor(color: .green)] var body: some View { VStack() { ForEach(colors, id: \.id) { Text("\($0.color.description)") .padding() .background($0.color) .cornerRadius(20) } } } } Forces to have id, UUID() will create unique value.
  57. ID can be anything struct MyColor: Identifiable { var color

    : Color var id : String } struct ContentView: View { let colors: [MyColor] = [MyColor(color: .red, id: "what"), MyColor(color: .blue, id: "is"), MyColor(color: .green, id: "this")] var body: some View { VStack() { ForEach(colors, id: \.id) { Text("\($0.color.description)") .padding() .background($0.color) .cornerRadius(20) } } } } id can be anything, should be unique
  58. ForEach: Solution struct MyColor: Hashable { var color : Color

    } struct ContentView: View { let colors: [MyColor] = [MyColor(color: .red), MyColor(color: .blue), MyColor(color: .green)] var body: some View { VStack() { ForEach(colors, id: \.id) { Text("\($0.color.description)") .padding() .background($0.color) .cornerRadius(20) } } } } Creates id that is the same if properties are same
  59. Using \.self struct ContentView: View { let names = ["jack",

    "tina", "paul"] var body: some View { VStack() { ForEach(names, id: \.self) { Text("\($0)") .padding() .cornerRadius(20) } } } } Using "jack", "tina" and "paul" as ids
  60. And now this works also... struct ContentView: View { let

    colors: [Color] = [.red, .green, .blue] var body: some View { VStack() { ForEach(colors, id: \.self) { Text("\($0.description)") .padding() .background($0) .cornerRadius(20) } } } }
  61. Picker and ForEach struct ContentView: View { let languages =

    ["Swift", "Kotlin", "Java"] @State var selectedLanguage : Int = 0 var body: some View { VStack() { Picker("Select your favorite language", selection: $selectedLanguage) { ForEach(0 ..< languages.count, id: \.self) { Text(self.languages[$0]) } } Text("Your favorite language \(languages[selectedLanguage])") } } }
  62. List and ForEach struct ContentView: View { let colors =

    ["red", "green", "blue"] var body: some View { List { ForEach(colors, id: \.self) { color in Text("\(color)") } } } }
  63. Navigation with List struct ContentView: View { let colors =

    ["red", "green", "blue"] var body: some View { NavigationStack { List { ForEach(colors, id: \.self) { color in Text("\(color)") } }.navigationBarTitle("Colors") } } } Notice that this is given to the inner component!
  64. Toggle and NavigationStack struct ContentView: View { @State private var

    toggle = true let colors = ["red", "green", "blue"] let cars = ["audi", "bmw", "mercedes"] var body: some View { VStack { NavigationView { if(toggle) { List { ForEach(colors, id: \.self) { color in Text("\(color)") } }.navigationBarTitle("Colors") } else { List { ForEach(cars, id: \.self) { car in Text("\(car)") } }.navigationBarTitle("Cars") } } Toggle(isOn: $toggle) { Text("Colors") }.padding() } } }
  65. Sections struct ContentView: View { let premiumCars = ["mercedes", "bmw",

    "audi"] let cars = ["skoda", "ford", "kia"] var body: some View { VStack { NavigationStack { List { Section(header: Text("Premium")) { ForEach(premiumCars, id: \.self) { car in Text("\(car)") } } Section(header: Text("Other")) { ForEach(cars, id: \.self) { car in Text("\(car)") } } }.navigationBarTitle("Cars") } } } }
  66. NavigationView vs NavigationStack • If your app has a minimum

    deployment target of iOS 16, iPadOS 16, macOS 13, tvOS 16, or watchOS 9, or later, transition away from using NavigationView. In its place, use NavigationStack and NavigationSplitView instances.
  67. Old vs New // Old NavigationView { // This is

    deprecated. /* content */ } .navigationViewStyle(.stack) // New NavigationStack { /* content */ }
  68. Basic Example struct ContentView: View { var body: some View

    { NavigationStack { Text("Hello, World!") .navigationBarTitle("Navigation") } } }
  69. struct ContentView: View { var body: some View { NavigationStack

    { VStack { Spacer() Text("First View") Spacer() NavigationLink(destination: Text("Second View")) { Text("Move to Second View") }.padding() } .navigationBarTitle("Navigation") } } }
  70. Passing Data A -> B struct SecondView: View { var

    name = "" var body: some View { Text("you gave: \(name)") } } struct ContentView: View { @State var name = "" var body: some View { NavigationStack { VStack { Spacer() TextField("Enter name", text: $name) .padding() .textFieldStyle(RoundedBorderTextFieldStyle()) Spacer() NavigationLink(destination: SecondView(name: name)) { Text("To the Second View") }.padding() } .navigationBarTitle("Navigation") } } }
  71. Passing Data B -> A struct SecondView: View { @Binding

    var name : String; var body: some View { TextField("Enter name", text: $name) .padding() .textFieldStyle(RoundedBorderTextFieldStyle()) } } struct ContentView: View { @State var name = "" var body: some View { NavigationStack { VStack { NavigationLink(destination: SecondView(name: $name)) { Text("To the Second View") }.padding() Text("\(name)") } .navigationBarTitle("Navigation") } } } Passing binding to the second view Accepting the binding
  72. Several Navigation Links struct ContentView: View { var body: some

    View { NavigationStack { NavigationLink("Mint", value: 1) NavigationLink("Pink", value: 2) NavigationLink("Teal", value: 3) .navigationDestination(for: Int.self) { value in Text("hello = \(value)") } .navigationTitle("Colors") } } }
  73. Using List struct ContentView: View { var body: some View

    { NavigationStack { List { NavigationLink("Mint", value: 1) NavigationLink("Pink", value: 2) NavigationLink("Teal", value: 3) } .navigationDestination(for: Int.self) { value in Text("hello = \(value)") } .navigationTitle("Colors") } } }
  74. And using color.. struct ContentView: View { var body: some

    View { NavigationStack { List { NavigationLink("Mint", value: Color.mint) NavigationLink("Pink", value: Color.pink) NavigationLink("Teal", value: Color.teal) } .navigationDestination(for: Color.self) { value in Text(value.description) .padding() .background(value) .cornerRadius(8) } .navigationTitle("Colors") } } }
  75. Programmically trigger navigation link struct ContentView: View { @State private

    var isShowingDetailView = false var body: some View { NavigationStack { VStack { Button("Tap to show detail") { self.isShowingDetailView = true } } .navigationBarTitle("Navigation") .navigationDestination(isPresented: $isShowingDetailView) { Text("second view") } } } }
  76. Local State View: Value type struct ContentView: View { @State

    private var counter = 0 var body: some View { VStack() { Text("\(self.counter)") .padding() Button("click") { counter += 1 } } } }
  77. Passed in from the outside: Value Type struct MyButton: View

    { @Binding var counter : Int // Binding<Int> var body: some View { Button("click") { counter += 1 } } } struct ContentView: View { @State private var counter = 0 var body: some View { VStack() { Text("\(counter)") .padding() MyButton(counter: $counter) } } }
  78. View creates, object type struct ContentView: View { @StateObject private

    var counterObject = Counter() var body: some View { VStack() { Text("\(counterObject.counter)") .padding() Button("start") { counterObject.start() }.padding() Button("stop") { counterObject.stop() }.padding() } } } class Counter: ObservableObject { @Published var counter = 0 var timer = Timer() func start() { timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in self.counter += 1 } } func stop() { timer.invalidate() self.counter = 0 } } Must be class When changes, refresh happens Class Counter is a singleton, only one instance even if view is recreated
  79. Passing object to child view struct ContentView: View { @StateObject

    private var counterObject = Counter() var body: some View { VStack() { Text("\(counterObject.counter)") .padding() Buttons(counter: counterObject) } } } struct Buttons : View { @ObservedObject var counter : Counter var body: some View { Button("start") { counter.start() }.padding() Button("stop") { counter.stop() }.padding() } } Object is received as argument
  80. Create the object in "main" import SwiftUI @main struct MyEnvApp:

    App { var body: some Scene { WindowGroup { ContentView().environmentObject(Counter()) } } }
  81. struct Buttons : View { @EnvironmentObject var counterObject: Counter var

    body: some View { Button("start") { counterObject.start() }.padding() Button("stop") { counterObject.stop() }.padding() } } class Counter: ObservableObject { @Published var counter = 0 var timer = Timer() func start() { timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in self.counter += 1 } } func stop() { timer.invalidate() self.counter = 0 } } struct ContentView: View { @EnvironmentObject var counterObject: Counter var body: some View { VStack() { Text("\(counterObject.counter)") .padding() Buttons() } } }
  82. Rules • @State • The view itself creates (and owns)

    the instance you want to wrap. • You need to respond to changes that occur within the wrapped property. • You're wrapping a value type (struct or enum) • @Binding • You need read- and write access to a property that's owned by a parent view. • The wrapped property is a value type (struct or enum). • You don't own the wrapped property (it's provided by a parent view).
  83. Rules • @StateObject • You want to respond to changes

    or updates in an ObservableObject. • The view you're using @StateObject in creates the instance of the ObservableObject itself. • @ObservedObject • You want to respond to changes or updates in an ObservedObject. • The view does not create the instance of the ObservedObject itself. (if it does, you need a @StateObject) • @EnvironmentObject • You would normally use an @ObservedObject but you would have to pass the ObservableObject through several view's initializers before it reaches the view where it's needed.
  84. Lifecycle Methods • In UIKit you have View Controllers with

    multiple lifecycle methods • In SwiftUI it is a little simpler • .onAppear() • .onDisappear()
  85. Example struct ContentView: View { @State private var shouldShowView =

    true var body: some View { VStack() { Toggle(isOn: $shouldShowView) { Text("Display") }.padding() if(shouldShowView) { VStack() { Text("Hello") Text("World") }.onAppear() { print("appear") }.onDisappear() { print("disappear") } } } } }
  86. URLSession • For simple requests, use URLSession.shared object • When

    receiving stuff, you will get NSData object • It is asynchronous • The URLSession.shared has method func dataTask(with: URL, completionHandler: (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
  87. Example let myURL = URL(string: "https://my-restful-api.onrender.com/locations")! let httpTask = URLSession.shared.dataTask(with:

    myURL) { (optionalData, response, error) in print("done fetching") } httpTask.resume()
  88. Example let myURL = URL(string: " https://my-restful-api.onrender.com/locations")! let httpTask =

    URLSession.shared.dataTask(with: myURL) { (optionalData, response, error) in print("done fetching") } httpTask.resume() NSData? URLResponse? Error?
  89. Example: Using Response let httpTask = URLSession.shared.dataTask(with: myURL) {(optionalData, response,

    error) in if let httpResponse = response as? HTTPURLResponse { print("statusCode: \(httpResponse.statusCode)") } } HTTPURLResponse is a subclass of URLResponse
  90. Example: Data -> String let httpTask = URLSession.shared.dataTask(with: myURL) {(optionalData,

    response, error) in let data : String = String(data: optionalData!, encoding: .utf8)! print(data) }
  91. JSON Parsing • Protocols • Encodable – convert struct to

    JSON • Decodable – convert JSON into struct • Codable – both • A lot of built types are already Codable, like Int, String, Bool, Dictionaries, Arrays • Arrays => JS Array, Dictionary => JS Object
  92. Location struct Location: Decodable { let id: Int let lat:

    Double let lon: Double } Decodable: bytes (string) -> Object Encodable: Object -> bytes (string) Codable: Decodable and Encodable. These protocols will add methods to the struct that provide implementations for the parsing
  93. Example using JSONDecoder let url : URL = URL(string: "https://my-restful-api.onrender.com/locations")!

    let httpTask = URLSession.shared.dataTask(with: url) { (optionalData : Data?, response: URLResponse?, error: Error?) in let jsonDecoder = JSONDecoder() do { let locations = try jsonDecoder.decode(Array<Location>.self, from: optionalData!) print(locations[0].lat) print(locations[0].lon) } catch { print(error) } } // Start the task httpTask.resume()
  94. Content View struct ContentView: View { @StateObject var httpConnection =

    HttpConnection() var body: some View { NavigationStack { if(httpConnection.isFetched) { List { let locations = httpConnection.result! ForEach(locations, id: \.id) { location in Text("\(location.lat) - \(location.lon)") } }.navigationBarTitle("Location API") } else { ProgressView() } }.onAppear() { httpConnection.connect(url: "https://my-restful-api.onrender.com/locations") } } }
  95. HTTP Class struct Location: Decodable { let id: Int let

    lat: Double let lon: Double } class HttpConnection : ObservableObject { @Published var result : Array<Location>? = nil @Published var isFetched = false func connect(url: String) { let myURL = URL(string: url)! let httpTask = URLSession.shared.dataTask(with: myURL) {(optionalData, response, error) in let jsonDecoder = JSONDecoder() DispatchQueue.main.async() { do { self.result = try jsonDecoder.decode(Array<Location>.self, from: optionalData!) self.isFetched = true } catch let error { print(error) } } } httpTask.resume() } } Accessing UI thread from worker thread is forbidden
  96. Async + Await • Introduction of async/await: • Introduced in

    Swift 5.5 as part of the Structured Concurrency model, it offers a more straightforward way to write asynchronous code compared to callbacks and promises. • Asynchronous Functions (async): • An async function is a function that performs an operation asynchronously. You mark a function as async to indicate it can perform work asynchronously and might pause (or "await") its execution to let other work be performed. • Awaiting on Tasks: • The await keyword is used to call async functions.
  97. Task • Task in SwiftUI: • A Task in SwiftUI

    is used to run asynchronous work that can perform operations in parallel to the main UI thread. It is particularly useful for initiating asynchronous operations from the SwiftUI view's lifecycle events or from user interactions. • Automatic Cancellation: • When a SwiftUI view disappears from the screen, any ongoing tasks associated with that view are automatically canceled. This automatic task cancellation helps in managing memory and processing resources more efficiently.
  98. Async + await example struct ContentView: View { func fetchData()

    async -> String { // Simulate a network delay try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 seconds return "Data fetched from network" } @State private var data: String = "Loading..." var body: some View { VStack { Text(data) .padding() } .task { // Execute the fetchData function asynchronously data = await fetchData() } } }
  99. try? • try? converts the result of a throwing function

    into an optional. • It simplifies error handling when you don't need to know the specific error. • The use of try? necessitates further unwrapping of the resulting optional to access the value.
  100. Async + await example do { try await Task.sleep(nanoseconds: 2_000_000_000)

    // Sleeps for 2 seconds } catch { // Handle the cancellation error or other errors print("Task was cancelled or another error occurred.") } // Ignore any errors try? await Task.sleep(nanoseconds: 2_000_000_000)
  101. enum DataError: Error { case networkFailure case invalidResponse } func

    fetchData() throws -> String { // Simulated network request let success = false // Change to true to simulate success if success { return "Fetched data successfully" } else { throw DataError.networkFailure } } // Using try? let result: String? = try? fetchData() if let data = result { print(data) } else { print("Failed to fetch data.") }
  102. struct ContentView: View { @State private var count = 0

    var body: some View { VStack { Text("Count: \(count)") .padding() Button("Start Counting") { // Reset the count to 0 each time the button is tapped before starting count = 0 Task { await startCounting() } } .padding() } } private func startCounting() async { for i in 1...10 { await MainActor.run { // Update the count on the main thread self.count = i } await sleepWithDelay(seconds: 1) } } func sleepWithDelay(seconds duration: UInt64) async -> Void { do { try await Task.sleep(nanoseconds: duration * 1_000_000_000) // Sleep succeeded without interruption } catch { // Handle the error (e.g., task cancellation) print("Sleep was interrupted: \(error.localizedDescription)") } } }
  103. import SwiftUI import Alamofire struct Joke: Decodable { let value:

    String } struct ContentView: View { @State private var joke = "Tap 'Get Joke' to fetch a random Chuck Norris joke." var body: some View { VStack(spacing: 20) { Text(joke) .padding() Button("Get Joke") { Task { await fetchRandomJoke() } } } } func fetchRandomJoke() async { let urlString = "https://api.chucknorris.io/jokes/random" do { let response: Joke = try await AF.request(urlString) .serializingDecodable(Joke.self) .value // Update the joke state variable on the main thread using Swift's concurrency model await MainActor.run { joke = response.value } } catch { // Handle potential errors, like network issues, on the main thread await MainActor.run { joke = "Failed to fetch joke. Please try again." } } } }
  104. import SwiftUI import Alamofire import Foundation struct User: Decodable, Identifiable

    { let id: Int let name: String let username: String let email: String } struct ContentView: View { @State private var users: [User] = [] var body: some View { NavigationView { List(users) { user in VStack(alignment: .leading) { Text(user.name) .font(.headline) Text("@\(user.username)") .font(.subheadline) Text(user.email) .font(.footnote) } } .navigationTitle("Users") .task { await fetchUsers() } } } func fetchUsers() async { let urlString = "https://jsonplaceholder.typicode.com/users" do { let response: [User] = try await AF.request(urlString) .serializingDecodable([User].self) .value await MainActor.run { users = response } } catch { print("Failed to fetch users: \(error.localizedDescription)") // Handle error or update the UI accordingly } } }