# Introducing SwiftUI ## Ch1 SwiftUI Essentials https://developer.apple.com/tutorials/swiftui/creating-and-combining-views ### Agenda - Introduction - Ch1 SwiftUI Essentials: Creating and Combining Views **40min** - Ch1 SwiftUI Essentials: Building Lists and Navigation **35min** --- ### Introduction - Sample app: *Landmarks* - macOS Monterey --- ### Ch1 SwiftUI Essentials: Creating and Combining Views #### Section 1: Create a New Project and Explore the Canvas - 建立專案:iOS > App (不選 Multiplatform) - ProductName, Interface: `SwiftUI`, Life Cycle: `SwiftUI App` - App 進入點:`{PROJICT_NAME}App.swift` > `@main` - example: ```swift= import SwiftUI @main struct SomeApp: App { // app conform App protocol var body: some Scene { // body return some Scene WindowGroup { // iOS: 1 scene 有 1 window, iPadOS, macOS: 1 scnene 有多 windows ContentView() // root view } } } ``` - Documents - [App](https://developer.apple.com/documentation/swiftui/app): A type that represents the structure and behavior of an app. - [Scene](https://developer.apple.com/documentation/swiftui/scene): A part of an app’s user interface with a life cycle managed by the system. - [WindowGroup](https://developer.apple.com/documentation/swiftui/windowgroup): A scene that presents a group of identically structured windwos. - View: `{FEATURE}View.swift` - example: ```swift= import SwiftUI struct SomeView: View { // view conform View protocol var body: some View { // body return some View Text("Hello, World!") // some view(Text) .padding() // modifier } } ``` - preview: ```swift= struct SomeView_Previews: PreviewProvider { static var previews: some View { SomeView() } } ``` - Resume preview: option + command + p --- #### Section 2: Customize the Text View - modifier 修改現有的 view 和 subview 然後回傳一個 new view - example: ```swift= Text("Turtle Rock") .font(.title) .foregroundColor(.green) ``` - 可以透過 Code Editor 或 SwiftUI Inspector(command + click view) 修改都行,結果會即時顯示在 preview --- #### Section 3: Combine Views Using Stacks - VStack, HStack, ZStack - command + click view's code: `Embed in {H/V}Stack` - 或 `{H/V}Stack { ... }` > 選取 view's codes + option + command + `[`/`]` - `VStack`: ```swift= // VStack(alignment:spacing:content:) VStack(alignment: .center, spacing: nil) { ... } // VStack(content:) VStack { ... } ``` - `HStack`: ```swift= // HStack(alignment:spacing:content:) HStack(alignment: .center, spacing: nil) { ... } // VStack(content:) HStack { ... } ``` - Documents - [VStack](https://developer.apple.com/documentation/swiftui/vstack): A view that arranges its children in a vertical line. - [HStack](https://developer.apple.com/documentation/swiftui/hstack): A view that arranges its children in a horizontal line. - [Spacer](https://developer.apple.com/documentation/swiftui/spacer): A flexible space that expands along the major axis of its containing stack layout, or on both axes if not contained in a stack. - [padding(_:_:)](https://developer.apple.com/documentation/swiftui/view/padding(_:_:)): A view that pads this view inside the specified edge insets with a system-calculated amount of padding. --- #### Section 4: Create a Custom Image View - 每一個 UI Component 都可以建立一個 View, ex: `CircleImage.swift` - mask example: ```swift= struct CircleImage: View { var body: some View { Image("...") .clipShape(Circle()) // vs iOS 13-15.2 mask(_:) or iOS 15+ mask(alignment:_:) .overlay { // vs ZStack, ref: https://stackoverflow.com/questions/63446213/difference-between-a-zstack-or-using-overlay Circle().stroke(.gray, lineWidth: 4) } } } ``` - Documents - [clipShape(_:style:)](https://developer.apple.com/documentation/swiftui/view/clipshape(_:style:)): Sets a clipping **shape** for this view. - [mask(alignment:_:)](https://developer.apple.com/documentation/swiftui/view/mask(alignment:_:)): Masks this view using the alpha channel of the given **view**. - [overlay(alignment:content:)](https://developer.apple.com/documentation/swiftui/view/overlay(alignment:content:)): Layers the views that you specify in front of this view. - [ZStack](https://developer.apple.com/documentation/swiftui/zstack): A view that overlays its children, aligning them in both axes. --- #### Section 5: Use SwiftUI Views From Other Frameworks - source of truth: `@State` ```swift= struct SomeView: View { @State private var stateProperty = ... } ``` - `@State` 都是 `private` - `${a_state_property}` -> `Binding<Type>` - ex: ```swift= @State private var aString = "foo" aString // String $aString // Binding<String> ``` - Live Preview: swith preview to live mode - Documents - [State](https://developer.apple.com/documentation/swiftui/state): A property wrapper type that can read and write a value managed by SwiftUI. - [Binding](https://developer.apple.com/documentation/swiftui/binding): A property wrapper type that can read and write a value owned by a source of truth. --- #### Section 6: Compose the Detail View - H/VStack view + modifier 時,裡面的 elements 也會套用 modifier 效果 - Documents - [frame(width:height:alignment:)](https://developer.apple.com/documentation/swiftui/view/frame(width:height:alignment:)): Positions this view within an invisible frame with the specified size. - [offset(x:y:)](https://developer.apple.com/documentation/swiftui/menu/offset(x:y:)): Offset this view by the specified horizontal and vertical distances. - [ignoresSafeArea(_:edges:)](https://developer.apple.com/documentation/swiftui/view/ignoressafearea(_:edges:)): Expands the view out of its safe area. - [Divider](https://developer.apple.com/documentation/swiftui/divider): A visual element that can be used to separate other content. --- #### Check Your Understanding - Q1: When creating a custom SwiftUI view, where do you declare the view’s layout? - Q2: Which layout renders from the following view code? ```swift= var body: some View { HStack { CircleImage() VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) Text("Joshua Tree National Park") } } } ``` - Q3: Which of these is a correct way to return three views from a custom view’s body property? - Q4: Which is the correct way to use modifier methods to configure a view? --- ### Ch2 SwiftUI Essentials: Building Lists and Navigation #### Section 1: Create a Landmark Model --- #### Section 2: Create the Row View - 在 SwiftUI View 新增 property 時,preview 會失效,可以在 resume(option + command + p) - Documents - [resizable(capInsets:resizingMode:)](https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation): Sets the mode by which SwiftUI resizes an image to fit its space. --- #### Section 3: Create the Row View - 修改 preview size ```swift= struct SomeView_Previews: PreviewProvider { static var previews: some View { SomeView() .previewLayout(.fixed(width: 300, height: 70)) } } ``` - 在 preview 中 render 多個 views ```swift= struct SomeView_Previews: PreviewProvider { static var previews: some View { Group { SomeView1() SomeView2() ... } } } ``` - Documents - [PreviewLayout](https://developer.apple.com/documentation/swiftui/previewlayout): A size constraint for a preview. - [Group](https://developer.apple.com/documentation/swiftui/group): A type that collects multiple instances of a content type — like views, scenes, or commands — into a single unit. --- #### Section 4: Create the List of Landmarks - List(static) ```swift= List { SomeView1() SomeView1() ... } ``` - Documents - [List](https://developer.apple.com/documentation/swiftui/list): A container that presents rows of data arranged in a single column, optionally providing the ability to select one or more members. --- #### Section 5: Make the List Dynamic - List(dynamic) ```swift= List(collection, id: \.id) { item in SomeView(item) } ``` - collection 需要 conform `identifiable` --- #### Section 6: Set Up Navigation Between List and Detail - NavigationView ```swift= NavigationView { List(collection) { item in SomeView(item) } .navigationTitle("Title") } ``` - NavigationLink ```swift= NavigationView { List(collection) { item in NavigationLink { SomeViewDetail(item) // detail view } label: { SomeView(item) // cell } } .navigationTitle("Title") } ``` --- #### Section 7: Pass Data into Child Views - Documents - [onAppear(perform:)](https://developer.apple.com/documentation/swiftui/view/onappear(perform:)): Adds an action to perform when this view appears. - [ScrollView](https://developer.apple.com/documentation/swiftui/scrollview): A scrollable view. - [navigationBarTitleDisplayMode(_:)](https://developer.apple.com/documentation/swiftui/text/navigationbartitledisplaymode(_:)): Configures the title display mode for this view. --- #### Section 8: Generate Previews Dynamically - 指定 preview device ```swift= struct SomeView_Previews: PreviewProvider { static var previews: some View { SomeView() .previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)")) // multi devices // ForEach(["iPhone SE (2nd generation)", "iPhone XS Max"], id: \.self) { deviceName in // SomeView() // .previewDevice(PreviewDevice(rawValue: deviceName)) // } } } ``` - Documents - [previewDevice(_:)](https://developer.apple.com/documentation/swiftui/view/previewdevice(_:)): Overrides the device for a preview. - [previewDisplayName(_:)](https://developer.apple.com/documentation/swiftui/text/previewdisplayname(_:)): Sets a user visible name to show in the canvas for a preview. --- #### Check Your Understanding - Q1: In addition to List, which of these types presents a dynamic list of views from a collection? - Q2: You can create a List of views from a collection of Identifiable elements. What approach do you use to adapt a collection of elements that don’t conform to the Identifiable protocol? - Q3: Which type do you use to make rows of a List tappable to navigate to another view? - Q4: Which of these choices is not a way to set the device for previewing your views? --- - A1: In the body property. Custom views implement the body property, which is a requirement of the View protocol. - A2: [3] The nested horizontal and vertical stacks arrange the image to the left of the two text views. - A3: [1] ```swift= VStack { Text("Turtle Rock") .font(.title) Divider() Text("Joshua Tree National Park") } ``` You can use a stack to return multiple views from a body property. - A4: [3] ```swift= Text("Hello world!") .font(.title) .foregroundColor(.purple) ``` A modifier returns a view that applies a new behavior or visual change. You can chain multiple modifiers to achieve the effects you need. --- - A1: [1]ForEach Place a ForEach instance inside a List or other container type to create a dynamic list. - A2: Passing a key path along with the data to List(_:id:). Pass the key path to a uniquely identifying property for your collection’s elements as the second parameter when creating a List. - A3: [1]NavigationLink Provide the destination view and the content of a row when you declare a NavigationLink. - A4: [2]Make a different choice in Canvas Settings in Xcode’s preferences. You can specify the device to use in the active scheme, in code, or by previewing directly on your device. No need for a trip to the preferences!