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

Introduction to SwiftUI (2025-04-22)

Introduction to SwiftUI (2025-04-22)

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 Application 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 • 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. @main struct MyScene: App { var body: some Scene {

    #if os(macOS) WindowGroup { Text("Mac") } #else WindowGroup { Text("iOS") } #endif } } The @main attribute in Swift is used to designate the entry point of a Swift program or app. It tells the compiler: “This is where the program starts.” In a SwiftUI app, it replaces the traditional main.swift file used in earlier Swift and UIKit-based projects.
  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.lowercased() } ) 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. ForEach • ForEach in SwiftUI is a view • Can

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

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

    some View { VStack() { ForEach((1..<10)) { Text("\($0)") } } } }
  54. 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'
  55. ForEach and ID 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.
  56. 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
  57. ForEach and ID struct MyColor: Identifiable { var color :

    String var id: String { 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() .cornerRadius(20) } } } } Now id is color string, expects each color to be unique
  58. 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
  59. 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])") } } }
  60. List vs ForEach ForEach • Generates views from a collection

    • No scrolling on its own • Inherits styling from parent view • When generating views inside another container (e.g., VStack, List) List • Displays scrollable, stylized rows • Built-in scrolling (vertical) • Includes system row styles, separators • When creating a table-like list view • Built-in swipe-to-delete support
  61. List struct ContentView: View { let colors = ["red", "green",

    "blue"] var body: some View { List(colors, id: \.self) { color in Text(color) } } }
  62. Old vs New // Old NavigationView { // This is

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

    { NavigationStack { Text("Hello, World!") .navigationTitle("Navigation") } } }
  64. struct Screen2: View { var body: some View { Text("Screen

    2") } } struct ContentView: View { var body: some View { NavigationStack { VStack { Spacer() Text("First View") Spacer() NavigationLink("Move to Second View", value: "screen2") } .navigationDestination(for: String.self) { value in if value == "screen2" { Screen2() } } .navigationTitle("Home") } } }
  65. struct Screen2: View { var body: some View { Text("Screen

    2") } } struct Screen1: View { var body: some View { Text("Screen 1") } } struct ContentView: View { var body: some View { NavigationStack { VStack { NavigationLink("Screen 1", value: "screen1") NavigationLink("Screen 2", value: "screen2") } .navigationDestination(for: String.self) { value in if value == "screen1" { Screen1() } if value == "screen2" { Screen2() } } .navigationTitle("Home") } } }
  66. struct AnotherScreen: View { var message = "" var body:

    some View { Text(message) } } struct ContentView: View { var body: some View { NavigationStack { VStack { NavigationLink("Screen 1", value: "screen1") NavigationLink("Screen 2", value: "screen2") } .navigationDestination(for: String.self) { value in AnotherScreen(message: value) } .navigationTitle("Home") } } }
  67. DetailView and Person itself as Route struct Person: Identifiable, Hashable

    { let id: Int let name: String } struct PersonDetailView: View { let person: Person var body: some View { Text("Hello, \(person.name)!") .font(.largeTitle) .padding() } } struct ContentView: View { let people: [Person] = [ Person(id: 1, name: "Alice"), Person(id: 2, name: "Bob"), Person(id: 3, name: "Charlie") ] var body: some View { NavigationStack { List(people) { person in NavigationLink(person.name, value: person) } .navigationDestination(for: Person.self) { person in PersonDetailView(person: person) } .navigationTitle("People") } } }
  68. Enums • An enum (short for enumeration) is a type

    that defines a set of related values, called cases. You use it when you want to represent a fixed group of possibilities • An enum is one value at a time — either .red, or .green, or .blue, never more than one.
  69. enum Direction { case north case south case east case

    west } var dir1 : Direction = Direction.north var dir2 : Direction = .north
  70. Enums with associated valued enum Route { case screen1 case

    screen2(message: String) } let a: Route = Route.screen1 let b: Route = .screen2(message: "Hello") b holds .screen2 and carries the string "Hello"
  71. Enums with associated valued enum Route, Hashable { case screen1

    case screen2(message: String) } print(Route.screen1.hashValue) // some Int print(Route.screen2(message: "hello").hashValue) // some other Int var routes = Set<Route>() routes.insert(.screen1) routes.insert(.screen2(message: "Hi")) routes.insert(.screen2(message: "Hello")) routes.insert(.screen2(message: "Hi")) // duplicate, won't be added again print(routes) Generates hash function and == function
  72. enum Route: Hashable { case screen1 case screen2 } struct

    Screen2: View { var body: some View { Text("Screen 2") } } struct Screen1: View { var body: some View { Text("Screen 1") } } struct ContentView: View { var body: some View { NavigationStack { VStack { NavigationLink("Screen 1", value: Route.screen1) NavigationLink("Screen 2", value: Route.screen2) } .navigationDestination(for: Route.self) { route in switch route { case .screen1: Screen1() case .screen2: Screen2() } } .navigationTitle("Home") } } }
  73. enum Route: Hashable { case screen1 case screen2(message: String) }

    struct Screen2: View { let message: String var body: some View { Text("Screen 2: \(message)") } } struct Screen1: View { var body: some View { Text("Screen 1") } } struct ContentView: View { var body: some View { NavigationStack { VStack { NavigationLink("Screen 1", value: Route.screen1) NavigationLink("Screen 2", value: Route.screen2(message: "Hello World")) } .navigationDestination(for: Route.self) { route in switch route { case .screen1: Screen1() case .screen2(let message): Screen2(message: message) } } .navigationTitle("Home") } } }
  74. enum Route: Hashable { case screen1 case screen2(message: String) }

    struct Screen2: View { let message: String @Binding var result: String? var body: some View { VStack { Text("Screen 2: \(message)") Button("Return Stuff") { result = "Stuff from Screen2" } } } } struct Screen1: View { var body: some View { Text("Screen 1") } } struct ContentView: View { @State private var messageFromScreen2 : String? = nil var body: some View { NavigationStack { VStack { if let result = messageFromScreen2 { Text("Message: \(result)") .padding() } NavigationLink("Screen 1", value: Route.screen1) NavigationLink("Screen 2", value: Route.screen2(message: "Hello World")) } .navigationDestination(for: Route.self) { route in switch route { case .screen1: Screen1() case .screen2(let message): Screen2(message: message, result: $messageFromScreen2) } } .navigationTitle("Home") } } }
  75. Several Navigation Links enum Route: Hashable { case mint case

    pink case teal } struct ContentView: View { var body: some View { NavigationStack { List { NavigationLink("Mint", value: Route.mint) NavigationLink("Pink", value: Route.pink) NavigationLink("Teal", value: Route.teal) } .navigationDestination(for: Route.self) { route in switch route { case .mint: Text("hello = Mint") case .pink: Text("hello = Pink") case .teal: Text("hello = Teal") } } .navigationTitle("Colors") } } }
  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)") } }.navigationTitle("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. struct PeopleResponse: Decodable { let users: [Person] } struct Person:

    Decodable, Identifiable, Hashable { let id: Int let firstName: String let lastName: String let email: String } struct PersonDetailView: View { let person: Person var body: some View { Text("Hello, \(person.firstName)!") .font(.largeTitle) .padding() } } struct ContentView: View { @State var people: [Person] = [] var body: some View { NavigationStack { List(people) { person in NavigationLink(person.firstName, value: person) } .navigationDestination(for: Person.self) { person in PersonDetailView(person: person) } .navigationTitle("People") }.task { self.people = await fetchUsers() } } func fetchUsers() async -> [Person] { do { let response = try await AF.request("https://dummyjson.com/users") .serializingDecodable(PeopleResponse.self) .value return response.users } catch { print("Failed to fetch users: \(error)") return [] } } }