• Version control can be cumbersome • Debug can be hard • Use several storyboards! Split your app. • Good approach for small apps • Number of developers... 1?
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
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
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.
#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.
var body: some Scene { WindowGroup { ContentView() } } } WindowGroup is container scene that wraps SwiftUI Views. Running this on iPad you can create multiple WindowGroup scenes
{ 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'
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
some View { Text("Hello") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } Your UI For Live Preview
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
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
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
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!
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."
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
"" 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>
"" var body: some View { TextField("Enter username...", text: $name) .padding() Text("Your name is \(name)") .padding() } } Automatically creates the Binding!
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.
: 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
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
• 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
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.
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
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
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).
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.
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
error) in if let httpResponse = response as? HTTPURLResponse { print("statusCode: \(httpResponse.statusCode)") } } HTTPURLResponse is a subclass of URLResponse
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
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
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.
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.
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.
// 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)
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.") }