Save 37% off PRO during our Black Friday Sale! »

利用_SwiftUI_製作電子書_App.pdf

 利用_SwiftUI_製作電子書_App.pdf

使用 SwiftUI 開發電子書 App
認識 SwiftUI 的 UI 元件 & modifier
使用 TabView 製作分頁
使用 NavigationView & NavigationLink 切換頁面 & 傳資料到下一頁
使用 List 製作表格
使用 HStack,VStack,ZStack,Spacer,padding,offset,overlay & position 排版
利用 ScrollView 實現水平捲動
設計照片牆頁面(grid layout)。
支援 dark mode
使用 iOS 13 的 SF Symbol
利用 extract subview 將 view 模組化
支援 iPhone,iPad,Mac
SwiftUI 的 Preview

Transcript

  1. 利⽤ SwiftUI 製作 電⼦書 App 新手的 iOS App 練功坊 5

    Peter Pan潘
  2. 彼得潘簡介 App程式設計入⾨:iPhone.iPad 彼得潘的 Swift 程式設計入⾨ 正職: 作家 副業: 專欄作家,⼯程師,講師,顧問,家教,App評審, App接案,企業包班,創業家,iOS

    APP ⾦牌擺渡⼈ iOS App⼯程師/外包廠商的⾯試鑑賞師 http://apppeterpan.strikingly.com
  3. 叫我彼得潘,Peter,Peter Parker Deeplove,⿁塚,Swift⼩王⼦,情歌王⼦ http://bit.ly/2XvKMcw

  4. 彼得潘的 SwiftUI 學習⽂章 http://bit.ly/2lHDosw

  5. SwiftUI 的好處 • Better apps. Less code: 程式愈少,bug 愈少 •

    Declarative(陳述) Syntax : 更容易理解,⽤程式描述畫⾯長 什麼樣⼦ • Preview: 可以預覽程式產⽣的 App 畫⾯,甚⾄將預覽畫⾯ 變成可以操作互動的 App。 • iOS,macOS,watchOS, tvOS App 可以採⽤類似的寫法。 • 利⽤ binding(綁定)機制,資料跟畫⾯更容易同步
  6. SwiftUI 的限制 • iOS 13 以上才能使⽤ • 只能⽤ Swift 撰寫

    SwiftUI 程式,不能⽤ Objective-C • 預覽 SwiftUI 設計的畫⾯要搭配 macOS 10.15 以 上版本
  7. 建立 Single View App 專案 選擇 SwiftUI

  8. 預覽(preview) SwiftUI 設計的畫⾯ http://bit.ly/2PnvRNW

  9. Xcode preview 顯⽰的 iPhone 機型 http://bit.ly/2xdYLEo

  10. 練習 建立 Single View SwiftUI 專案和 觀看預覽畫⾯ • macOS 10.14

    Mojave 開發 SwiftUI App 的缺點 http://bit.ly/2m1weQ1 • 從 Apple Beta Software Program 網站安裝 10.15 beta https://beta.apple.com/sp/betaprogram/ • 不是 macOS 10.15,請⼿動輸入程式,將 App 安裝到 模擬器看畫⾯
  11. 定義畫⾯的型別 struct ContentView: View { var body: some View {

    Text("Hello World") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } 預覽呈現的畫⾯,在此我們⽣成型別 ContentView 的元件當 預覽畫⾯ SwiftUI 的 UI 元件以 struct 定義, 遵從 protocol View
  12. SwiftUI 的 UI 元件以 struct 定義, 遵從 protocol View struct

    Button<Label> : View where Label : View struct Text : Equatable extension Text : View struct Image : Equatable extension Image : View • SwiftUI 的 UI 元件都是 View • 類似類別繼承的概念,比⽅ Dog & Cat 繼承 Animal, 所以產⽣的⼩狗和⼩貓物件都是 Animal
  13. protocol View public protocol View { /// The type of

    view representing the body of this view. /// /// When you create a custom view, Swift infers this type from your /// implementation of the required `body` property. associatedtype Body : View /// Declares the content and behavior of this view. var body: Self.Body { get } } 遵從 protocol View 須定義 computed property body 精於算計的 computed property http://bit.ly/2lVJeqh 幫助 protocol 實現型別代號的 Associated Type http://bit.ly/2lDJIS6
  14. 變數 body 的內容決定畫⾯顯⽰ 的東⻄ var body: some View { return

    Text("Hello World") } var body: some View { Text("Hello World") } 只有⼀⾏程式時可以省略 return 沒有省略 return • 變數 body 是 computed property,類似 function,所以有 { } • 讀取變數 body 時,將執⾏ { } 的程式,得到它回傳的東⻄ • 回傳 Text,所以畫⾯顯⽰⽂字 省略 return http://bit.ly/2FuvcTA
  15. 不只⼀⾏程式, 所以要加 return var body: some View { print("Hello World")

    return Text("Hello World") }
  16. var body: some View struct ContentView: View { var body:

    some View { Text("Hello World") } } • 回傳的元件的型別須遵從 protocol View,必須是 某⼀種 view,畫⾯才能顯⽰,比⽅⽂字,圖片等 • some 跟 Swift 的 Opaque Types 有關, https://docs.swift.org/swift-book/LanguageGuide/OpaqueTypes.html
  17. SwiftUI 設計 UI 元件樣式的 各種⽅法 http://bit.ly/2lPOgoh

  18. 客製 UI 元件樣式的 SwiftUI modifier http://bit.ly/2IJ7cyd 利⽤ Re-Indent 縮排 SwiftUI

    程式 http://bit.ly/2lPVRn5
  19. 練習 實驗各種 modifier ps:不是 macOS 10.15,請⼿動輸入程式, 將 App 安裝到模擬器看畫⾯

  20. 加入元件,ViewBuilder,Group 畫⾯加入元件的⽅法 http://bit.ly/2lWiy8Y 最後⼀名的 trailing closure http://bit.ly/2lFJOID ViewBuilder & Ambiguous

    reference to member buildBlock() http://bit.ly/2lZTTR1 在結尾輸入 { } ⽣成 SwiftUI 元件 http://bit.ly/2lWVaIn
  21. 練習 加入元件

  22. 利⽤ frame 調整⼤⼩ Text("我要⼀步⼀步學App,在最餓點吃著蘋果寫著Code,⼩⼩的天流過的淚 和汗,總有⼀天我有屬於我的App。") .frame(width: 200, height: 400) 指定寬⾼

  23. 省略 height,⾼度變成 可呈現所有⽂字的⾼度 Text("我要⼀步⼀步學App,在最餓點吃著蘋果寫著Code,⼩⼩的天流過的淚 和汗,總有⼀天我有屬於我的App。").frame(width: 200)

  24. 顯⽰美麗圖片的 SwiftUI image http://bit.ly/2jWn4DM

  25. 練習 加入圖片

  26. iOS 13 的 SF Symbols • 超過 1500 個 •

    https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/ • https://developer.apple.com/design/ • 沒有美術天份也能開發美美的 App 了 ! • 可以另外⾃⼰設計 • 圖片可調整樣式和搭配⽂字字型
  27. Mac 的 SF Symbols App 瀏覽圖片的模樣和名字 https://developer.apple.com/design/downloads/SF-Symbols.dmg

  28. 參數 systemName 傳入 SF Symbol 的名字 利⽤ Font 設定粗細和⼤⼩,利⽤ foregroundColor

    設定顏⾊ VStack { Image(systemName: "magnifyingglass") .resizable() .scaledToFit() .frame(width: 200, height: 300) Image(systemName: "magnifyingglass") .font(Font.system(size: 100, weight: .heavy)) .foregroundColor(.blue) }
  29. VStack VStack { Image("peter") Text("我要⼀步⼀步學App,等待朋友崇拜看著我的臉,⼩⼩的天有⼤⼤的夢想, 重重的課做著輕輕的App。我要⼀步⼀步學App,在最餓點吃著蘋果寫著Code,⼩⼩的天 流過的淚和汗,總有⼀天我有屬於我的App。") }

  30. VStack 的 init init(alignment: HorizontalAlignment = .center, spacing: CGFloat? =

    nil, @ViewBuilder content: () -> Content)
  31. 調整 VStack 的間距 & 對⿑ VStack(alignment: .leading, spacing: 50) {

    Image("peter") Text("我要⼀步⼀步學App,等待朋 友崇拜看著我的臉,⼩⼩的天有⼤⼤的夢想, 重重的課做著輕輕的App。我要⼀步⼀步學 App,在最餓點吃著蘋果寫著Code,⼩⼩的 天流過的淚和汗,總有⼀天我有屬於我的 App。") } VStack(alignment: .trailing, spacing: 50) { Image("peter") Text("我要⼀步⼀步學App,等待朋 友崇拜看著我的臉,⼩⼩的天有⼤⼤的夢想, 重重的課做著輕輕的App。我要⼀步⼀步學 App,在最餓點吃著蘋果寫著Code,⼩⼩的 天流過的淚和汗,總有⼀天我有屬於我的 App。") }
  32. leading & trailing • 語⾔順序為由左到右時,leading 為左,trailing 為右 • 語⾔順序為由右到左(RTL: Right-To-Left)時,leading

    為右,trailing為左,例如阿拉伯
  33. HStack HStack { Image("peter") Text("我要⼀步⼀步學App,等待朋友崇拜看著我的臉,⼩⼩的天有⼤⼤的夢想,重重的課做著輕輕的 App。我要⼀步⼀步學App,在最餓點吃著蘋果寫著Code,⼩⼩的天流過的淚和汗,總有⼀天我有屬於我的App。") }

  34. 調整 HStack 的間距 & 對⿑ HStack(alignment: .top, spacing: 50) {

    Image("peter") Text("我要⼀步⼀步學App,等待朋 友崇拜看著我的臉,⼩⼩的天有⼤⼤的夢想, 重重的課做著輕輕的App。我要⼀步⼀步學 App,在最餓點吃著蘋果寫著Code,⼩⼩的 天流過的淚和汗,總有⼀天我有屬於我的 App。") } HStack(alignment: .bottom, spacing: 50) { Image("peter") Text("我要⼀步⼀步學App,等待朋 友崇拜看著我的臉,⼩⼩的天有⼤⼤的夢想, 重重的課做著輕輕的App。我要⼀步⼀步學 App,在最餓點吃著蘋果寫著Code,⼩⼩的 天流過的淚和汗,總有⼀天我有屬於我的 App。") }
  35. ZStack ZStack { Image("peter") Text("我要⼀步⼀步學App,等待朋友崇拜看著我的臉,⼩⼩的天有⼤⼤的夢想,重重的課做著輕輕的 App。我要⼀步⼀步學App,在最餓點吃著蘋果寫著Code,⼩⼩的天流過的淚和汗,總有⼀天我有屬於我的App。") } 可以將元件⼀層層疊上, 類似疊疊樂

  36. ZStack 對⿑有多個選擇

  37. 調整 ZStack 的對⿑ ZStack(alignment: .topLeading) { Image("peter") Text("我要⼀步⼀步學App,等待朋友崇拜看著我的臉,⼩⼩的天有⼤⼤的夢想,重重的 課做著輕輕的App。我要⼀步⼀步學App,在最餓點吃著蘋果寫著Code,⼩⼩的天流過的淚和 汗,總有⼀天我有屬於我的App。")

    }
  38. 利⽤ Embed in 加入元件 http://bit.ly/2lxrNg2

  39. 利⽤ Spacer,padding,offset,position, overlay & background 調整元件位置 http://bit.ly/2mSS6xv

  40. 利⽤ Spacer & Padding 調整元件位置 VStack { Image("peter") Text("我要⼀步⼀步學App,等待朋友崇拜 看著我的臉,⼩⼩的天有⼤⼤的夢想,重重的課

    做著輕輕的App。我要⼀步⼀步學App,在最餓 點吃著蘋果寫著Code,⼩⼩的天流過的淚和汗, 總有⼀天我有屬於我的App。") .padding() Spacer() }
  41. 練習 Stack,padding,Spacer,offset, overlay & background

  42. SwiftUI 跑錯地⽅的紅⾊錯誤 http://bit.ly/2pBZ1g2

  43. 新增 SwiftUI View 畫⾯ 比⽅新增顯⽰個⼈簡介的 IntroView http://bit.ly/2lG62du struct IntroView: View

    { var body: some View { Text("Hello World!") } }
  44. IntroView struct IntroView: View { var body: some View {

    Text("2⽉7⽇⽔中瓶,愛瘋⼀切為蘋果。桌球快狠不忘準,⾳樂聲裡 讀推理。 非思不可jobs, 為愛尋夢往前⾶。iPad不是放⼤Phone,兩者皆通彼 得潘。") } } IntroView.swift
  45. 練習 新增 SwiftUI View 畫⾯

  46. 利⽤ NavigationView & NavigationLink 切換⾴⾯ http://bit.ly/2nloFnX 請升級到 Xcode 11.1 以上,11.0

    有 bug
  47. 加入 NavigationView navigation bar NavigationView { VStack { Image("peter") Text("我要⼀步⼀步學App,等待朋友

    崇拜看著我的臉,⼩⼩的天有⼤⼤的夢想,重重的 課做著輕輕的App。我要⼀步⼀步學App,在最餓 點吃著蘋果寫著Code,⼩⼩的天流過的淚和汗,總 有⼀天我有屬於我的App。") .padding() Spacer() } } ContentView.swift
  48. 利⽤ navigationBarTitle 顯⽰標題 NavigationView { VStack { Image("peter") Text("我要⼀步⼀步學App,等待朋友崇 拜看著我的臉,⼩⼩的天有⼤⼤的夢想,重重的課

    做著輕輕的App。我要⼀步⼀步學App,在最餓點吃 著蘋果寫著Code,⼩⼩的天流過的淚和汗,總有⼀ 天我有屬於我的App。") .padding() Spacer() } .navigationBarTitle("情歌王") } ContentView.swift 透過在 NavigationView { } 裡的元件呼叫 navigationBarTitle,比⽅從以上程式的 VStack 呼叫
  49. IntroView 顯⽰標題 struct IntroView: View { var body: some View

    { Text("2⽉7⽇⽔中瓶,愛瘋⼀切為蘋果。桌球快狠不忘準,⾳樂聲裡 讀推理。 非思不可jobs, 為愛尋夢往前⾶。iPad不是放⼤Phone,兩者皆通彼 得潘。") .navigationBarTitle("⾃我介紹") } } NavigationView 在之前畫⾯,preview 不知道, 因此 preview 看不到 navigation bar & 標題 IntroView.swift
  50. 加入 NavigationLink 點選圖片切換⾴⾯ NavigationView { VStack { NavigationLink(destination: IntroView()) {

    Image("peter") .renderingMode(.original) } Text("我要⼀步⼀步學App,等待朋友崇拜看著我的臉,⼩⼩的 天有⼤⼤的夢想,重重的課做著輕輕的App。我要⼀步⼀步學App,在最餓 點吃著蘋果寫著Code,⼩⼩的天流過的淚和汗,總有⼀天我有屬於我的 App。") .padding() Spacer() } .navigationBarTitle("情歌王") } ContentView.swift
  51. Live Preview & Preview on Device http://bit.ly/2m1KhoV • 點選圖片切換⾴⾯可從 preview

    測試 • 修改程式 Live Preview 會⾃動更新,不⽤重新啟動 App。
  52. 練習 利⽤ NavigationView & NavigationLink 切換⾴⾯

  53. 利⽤ List 呈現表格

  54. 新增顯⽰情歌列表的 SongList struct SongList: View { var body: some View

    { Text("Hello World!") } } SwiftUI 的表格稱為 List SongList.swift
  55. 控制 iOS App 的第⼀個畫⾯ http://bit.ly/2YWmU2J func scene(_ scene: UIScene, willConnectTo

    session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let contentView = SongList() if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: contentView) self.window = window window.makeKeyAndVisible() } } SceneDelegate.swift 改成 SongList
  56. 練習 設定第⼀個畫⾯

  57. 新增顯⽰每⼀⾸歌的 SongRow 新增 SongRow.swift struct SongRow: View { var body:

    some View { HStack { Image("對的時間點") .resizable() .scaledToFill() .frame(width: 80, height: 80) .clipped() VStack(alignment: .leading) { Text("對的時間點") Text("林俊傑") } } } } SongRow.swift
  58. 利⽤ Spacer 讓 HStack 的寬度變 成螢幕寬度 struct SongRow: View {

    var body: some View { HStack { Image("對的時間點") .resizable() .scaledToFill() .frame(width: 80, height: 80) .clipped() VStack(alignment: .leading) { Text("對的時間點") Text("林俊傑") } Spacer() } } } SongRow.swift
  59. 利⽤ previewLayout 調整 preview 的尺⼨ struct SongRow_Previews: PreviewProvider { static

    var previews: some View { SongRow() .previewLayout(.fixed(width: 300, height: 70)) } } SongRow.swift
  60. 利⽤ List 顯⽰表格 struct SongList: View { var body: some

    View { List { SongRow() SongRow() SongRow() SongRow() SongRow() SongRow() SongRow() SongRow() SongRow() SongRow() } } } SongList.swift
  61. 練習 製作列表 row 上的元件預設會跟邊邊有間距 等下會說明如何移除間距

  62. 定義代表歌曲資料的型別 Song

  63. 定義代表歌曲資料的型別 Song struct Song { var name: String var singer:

    String } 新增 Song.swift Song.swift
  64. SongRow 顯⽰ Song 的內容 struct SongRow: View { var song:

    Song var body: some View { HStack { Image(song.name) .resizable() .scaledToFill() .frame(width: 80, height: 80) .clipped() VStack(alignment: .leading) { Text(song.name) Text(song.singer) } Spacer() } } } SongRow.swift 儲存顯⽰的歌曲
  65. Missing argument for parameter SongRow.swift

  66. 預覽畫⾯建立 SongRow 時傳入 Song struct SongRow_Previews: PreviewProvider { static var

    previews: some View { SongRow(song: Song(name: "對的時間點", singer: "林俊傑")) .previewLayout(.fixed(width: 300, height: 70)) } } SongRow.swift
  67. 更新 SongList 的歌單 struct SongList: View { var body: some

    View { List { SongRow(song: Song(name: "對的時間 點", singer: "林俊傑")) SongRow(song: Song(name: "說好不 哭", singer: "周杰倫")) } } } 如果有 100 ⾸歌 ? SongList.swift
  68. 定義儲存照片資料的 array songs 新增 Data.swift let songs = [Song(name: "對的時間點",

    singer: "林俊傑"), Song(name: "說好不哭", singer: "周杰倫")] ⽅便測試的全域變數 Data.swift
  69. ⽤ List 搭配 range 顯⽰表格 • data: 數字範圍,控制 row 的數量

    • rowContent: 傳入 closure(以 { } 包含的程式), 回傳表格每⼀⾏顯⽰的 view
  70. ⽤ List 搭配 range 顯⽰表格 按 enter 表格將有 3 個

    row,呼叫參數 rowContent 的 { } 程式 得到每個 row 的 view,型別 Int 的參數將告知要 回傳第幾個 row,因為 0..<3,所以參數將為 0, 1, 2
  71. ⽤ List 搭配 range 顯⽰表格 struct SongList: View { var

    body: some View { List(0..<3) { (index) in Text("第\(index)個") } } } SongList.swift index = 0 時,產⽣ Text("第0個") Index = 1 時,產⽣ Text("第1個")
  72. ⽤ List 搭配 array 顯⽰表格 struct SongList: View { var

    body: some View { List(0..<songs.count) { (index) in SongRow(song: songs[index]) } } } SongList.swift index = 0 時,產⽣ SongRow(song: songs[0]) Index = 1 時,產⽣ SongRow(song: songs[1])
  73. 只能傳入 Range, 不接受 ClosedRange

  74. 練習 搭配 array 呈現列表內容

  75. 新增歌曲明細⾴ 照片 + 歌詞

  76. 增加 property lyrics struct Song { var name: String var

    singer: String var lyrics: String } Song.swift
  77. 更新 Songs let songs = [Song(name: "對的時間點", singer: "林俊傑", lyrics:

    "如果 愛情是場 遠程的渦旋 僅管 繞著圈⼦ 也要走向前 不離⼼太遠 我要⾯朝最 藍的晴天 不脫離軌道有你在⾝邊"), Song(name: "說好不哭", singer: "周 杰倫", lyrics: "沒有了聯絡 後來的⽣活 我都是聽別⼈說 說妳怎麼了 說妳怎 麼過 放不下的⼈是我 ⼈多的時候 就待在⾓落 就怕別⼈問起我 你們怎麼了 妳低 著頭 護著我連抱怨都沒有")] Data.swift
  78. 新增歌曲明細⾴ 新增 SongDetail.swift struct SongDetail: View { var song: Song

    var body: some View { VStack { Image(song.name) .resizable() .scaledToFill() .frame(minWidth: 0, maxWidth: .infinity, maxHeight: 300) .clipped() Text(song.lyrics) .padding() } } } struct SongDetail_Previews: PreviewProvider { static var previews: some View { SongDetail(song: songs[0]) } } Image 的寬度等於螢幕寬度 SongDetail.swift 儲存歌曲資訊
  79. 將 SongList 包在 NavigationView 裡 & 顯⽰標題 struct SongList: View

    { var body: some View { NavigationView { List(0..<songs.count) { (index) in SongRow(song: songs[index]) } .navigationBarTitle("情歌王") } } } SongList.swift
  80. 加入 NavigationLink 切換⾴⾯ & 傳資料到下⼀⾴ NavigationView { List(0..<songs.count) { (index)

    in NavigationLink(destination: SongDetail(song: songs[index])) { SongRow(song: songs[index]) } } .navigationBarTitle("情歌王") } SongList.swift 將點選的歌曲傳到下⼀⾴的 SongDetail
  81. 歌曲明細⾴顯⽰標題 struct SongDetail: View { var song: Song var body:

    some View { VStack { Image(song.name) .resizable() .scaledToFill() .frame(height: 300) .clipped() Text(song.lyrics) .padding() } .navigationBarTitle(song.name) } } SongDetail.swift
  82. 練習 新增照片明細⾴ & ⾴⾯切換

  83. ⽤ TabView 實現兩個 tab ⾴⾯

  84. ⽤ TabView 實現兩個 tab ⾴⾯ 新增 AppView.swift 利⽤ tabItem(_:) 設定

    tab 的圖⽂ tabItem(_:) 只能顯⽰傳統 tab 的樣式,比⽅圖片在上,⽂字在下 struct AppView: View { var body: some View { TabView { SongList() .tabItem { Image(systemName: "music.house.fill") Text("情歌") } ContentView() .tabItem { Image(systemName: "info.circle.fill") Text("About") } } } } AppView.swift
  85. 將第⼀個畫⾯變成 AppView SceneDelegate.swift func scene(_ scene: UIScene, willConnectTo session: UISceneSession,

    options connectionOptions: UIScene.ConnectionOptions) { let contentView = AppView()
  86. 練習 ⽤ TabView 實現兩個 tab ⾴⾯

  87. ⽔平捲動的 Scroll View ScrollView(.horizontal) { HStack(spacing: 20) { Image("peter0") .resizable()

    .scaledToFill() .frame(width: 200) .clipped() Image("peter1") .resizable() .scaledToFill() .frame(width: 200) .clipped() Image("peter2") .resizable() .scaledToFill() .frame(width: 200) .clipped() } .frame(height: 200) } 圖片寬⾼ 200 * 200 ScrollView 裡包 HStack
  88. ForEach: 利⽤集合裡的東⻄⽣成⼀ 個個 view,然後再合併成⼀個 view ScrollView(.horizontal) { HStack(spacing: 20) {

    ForEach(0..<3) { (index) in Image("peter\(index)") .resizable() .scaledToFill() .frame(width: 200) .clipped() } } .frame(height: 200) } 先介紹 ForEach 搭配 range 的寫法 http://bit.ly/2kH7lsK
  89. ⽔平捲動的 Scroll View IntroView.swift

  90. 天⽣⽀援 dark mode 的 SwiftUI Color http://bit.ly/2meuJhy 利⽤ Environment Overrides

    快速切換 App 的 light mode & dark mode http://bit.ly/2lYt1kA
  91. 利⽤ extract subview 將 view 模組化

  92. extract subview 將某個 subview 變成獨立的型別 按住 cmd 鍵點選 Image,然後點選 Extract

    Subview ps: 如果沒有出現 Extract Subview 的選項,請⾃⼰⼿動建立 好處: 增加程式可讀性,可重覆使⽤ SongDetail.swift SongDetail
  93. 輸入 view 的型別名 輸入 SongImage VStack { SongImage() Text(song.lyrics) .padding()

    Spacer() } SongDetail.swift
  94. 錯了 ! 找不到 song Use of unresolved identifier SongDetail.swift

  95. 在 SongImage 加上 property song struct SongImage: View { var

    song: Song var body: some View { Image(song.name) .resizable() .scaledToFill() .frame(height: 300) .clipped() } } SongDetail.swift
  96. 建立 SongImage 時傳入 song struct SongDetail: View { var song:

    Song var body: some View { VStack { SongImage(song: song) Text(song.lyrics) .padding() Spacer() } .navigationBarTitle(song.name) } } SongDetail.swift
  97. 設定 SwiftUI 表格內容邊距的 listRowInsets http://bit.ly/2nUm6K9 讓內容靠邊

  98. 結合 List & ScrollView 上下捲動的 List 裡有⽔平捲動的 ScrollView ⽔平捲動 上下捲動

  99. 結合 List & ScrollView List ScrollView row row row row

    HStack Image Image Image
  100. 結合 List & ScrollView NavigationView { List { ScrollView(.horizontal) {

    HStack(spacing: 20) { ForEach(0..<3) { (index) in Image("peter\(index)") .resizable() .scaledToFill() .frame(width: 200) .clipped() } } .frame(height: 200) } ForEach(0..<songs.count) { (index) in NavigationLink(destination: SongDetail(song: songs[index])) { SongRow(song: songs[index]) } } } .navigationBarTitle("情歌王") } • List { } 裡包 ScrollView & ForEach 呈現的 SongRow • 點選 ScrollView 裡的元件到下⼀⾴ ? ScrollView 裡的 Image 加上 NavigationLink
  101. 清除分隔線 http://bit.ly/2nRPjFx

  102. 照片牆⾴⾯(grid layout)

  103. struct ContentView: View { let names = [["Federer", "Nadal"], ["Djokovic",

    "Murray"]] var columnCount = 2 let photoWidth = (UIScreen.main.bounds.size.width - 10) / 2 var body: some View { List { ForEach(0..<names.count) { (row) in HStack(spacing:10) { ForEach(0..<self.names[row].count) { (column) in Image(self.names[row][column]) .resizable() .scaledToFill() .frame(width: self.photoWidth, height: self.photoWidth) .clipped() } } } .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 10, trailing: 0)) } .onAppear { UITableView.appearance().separatorColor = .clear } } } 照片牆⾴⾯(grid layout) array 裡包 array,外層 array 決定 row 的數量,內層 array 決定每個 row 的照片數量 以 HStack 裝 row 的照片
  104. List 的 row 有多個可點選的 NavigationLink http://bit.ly/2M8K11T 例⼦: 點選照片牆的照片到下⼀⾴

  105. ⽀援 iPhone,iPad & Mac 將 iPad App 變成 Mac App

    http://bit.ly/2Lh1IfF
  106. 作業 使⽤ SwiftUI 製作電⼦書 App http://bit.ly/2oDpfya

  107. 補充

  108. 客製樣式的 list row 圓⾓ & 陰影

  109. 讓 view 變圓⾓ • 讓東⻄變圓形的各種⽅法 http://bit.ly/2korkMz • 讓 view 變圓⾓的

    cornerRadius & clipShape http://bit.ly/2lS42zq
  110. 邊框,shadow,opacity 邊框: http://bit.ly/2lXfIkA shadow: 陰影 opacity: 不透明度,0 ~1,0 表⽰完全透明

  111. 圓形邊框陰影照片 struct SongRow: View { var song: Song var body:

    some View { HStack { Image(song.name) .resizable() .scaledToFill() .frame(width: 80, height: 80) .clipShape(Circle()) .overlay(Circle().stroke(Color.white, lineWidth: 4)) .shadow(radius: 10) VStack(alignment: .leading) { Text(song.name) Text(song.singer) } Spacer() } } }
  112. 客製樣式的 list row List(0..<songs.count) { (index) in VStack { Image(songs[index].name)

    .resizable() .scaledToFill() .frame(minWidth: 0, maxWidth: .infinity, maxHeight: 200) .clipped() .padding() Text(songs[index].name) .padding(.bottom, 10) } .background(Color.yellow) .cornerRadius(20) .shadow(radius: 5) } .onAppear { UITableView.appearance().separatorColor = .clear } 黃⾊圓⾓陰影的⽂青卡片
  113. 利⽤ SwiftUI 的 background 設定背景 顏⾊,背景圖片,漸層背景 & 圓⾓背景 http://bit.ly/2n2aCDY

  114. ⽤ List 搭配 id & Identifiable 顯⽰ 表格 先介紹 ForEach

    http://bit.ly/2kH7lsK
  115. ⽤ List 搭配 array & id 顯⽰表格 List(songs, id: \.name)

    { (song) in SongRow(song: song) }
  116. ⽤ List 搭配 array & Identifiable 顯⽰表格 struct Song: Identifiable

    { var id = UUID() var name: String var singer: String var lyrics: String } List(songs) { (song) in SongRow(song: song) }
  117. 圖片等於螢幕寬度 & 設定比例的⽅法 http://bit.ly/2m0RjKw

  118. 全螢幕的背景顏⾊或圖片 Full Screen http://bit.ly/2msrLqr 利⽤ edgesIgnoringSafeArea

  119. 組合多種⽂字樣式 http://bit.ly/2nDhMyM

  120. 分類表格的 Section 比⽅晴天情歌跟雨天情歌 let sunnyDaySongs = [Song(name: "晴天", singer: "何維健",

    lyrics: "要記得晴天勝得過下雨天 你的笑要被我看⾒ 就這樣笑著笑著 煩惱 不⾒ 你就是我的太陽 別在乎別⼈怎麼想")] let rainyDaySongs = [Song(name: "下雨天", singer: "南拳媽媽", lyrics: "下雨天了怎麼辦 我好想你 我不敢打給你 我找不到原因"), Song(name: "⼜下雨了", singer: "李⼼潔", lyrics: "⼜下雨了你最喜 歡的天氣 收⾳機正播放著傷⼼的歌曲 你在這座城的那裏看雨 是⼀個⼈還是已 經有⼈陪著你")] Data.swift
  121. 分類表格的 Section 在 Section 的 { } 裡以 ForEach 搭配

    array 設定內容 NavigationView { List { Section(header: Text("Sunny")) { ForEach(0..<sunnyDaySongs.count) { (index) in NavigationLink(destination: SongDetail(song: sunnyDaySongs[index])) { SongRow(song: sunnyDaySongs[index]) } } } Section(header: Text("Rainy")) { ForEach(0..<rainyDaySongs.count) { (index) in NavigationLink(destination: SongDetail(song: rainyDaySongs[index])) { SongRow(song: rainyDaySongs[index]) } } } }.navigationBarTitle("情歌王") }
  122. 分類表格的 Section header header

  123. List ⾃動為 row 加入靠左的 HStack 中英⽂時 Leading 為左邊 NavigationView {

    List(0..<songs.count) { index in NavigationLink(destination: SongDetail(song: songs[index])) { Image(songs[index].name) .resizable() .scaledToFill() .frame(width: 80, height: 80) .clipped() VStack(alignment: .leading) { Text(songs[index].name) Text(songs[index].singer) } } } } 可以省略 HStack & Spacer
  124. struct SongList: View { var body: some View { NavigationView

    { List(0..<songs.count) { index in SongRow(song: songs[index]) } } } } struct SongRow: View { var song: Song var body: some View { NavigationLink(destination: SongDetail(song: song)) { Image(song.name) .resizable() .scaledToFill() .frame(width: 80, height: 80) .clipped() VStack(alignment: .leading) { Text(song.name) Text(song.singer) } } } }
  125. 更多有趣的 SwiftUI 應⽤ Maybe 之後的新⼿練功坊 使⽤ SwiftUI 的 UI 元件

    & data binding 創作有趣的 App http://bit.ly/2oI1apV 利⽤ SwiftUI 的 Path 繪圖 http://bit.ly/2khdk7g
  126. Always Online 有問題歡迎 LINE 我 https://www.youtube.com/watch?v=ZPg9yv5LR3s 只要我沒有在約會就會回你 現在正是學習 SwiftUI 的

    對的時間點