SwiftUI學(xué)習(xí)整理(一)

最前面想說的話

轉(zhuǎn)眼已經(jīng)是2020年10月了,2020已經(jīng)過去大半了,上半年的計劃已經(jīng)完成傅是,所以下半年的學(xué)習(xí)應(yīng)該提上日程了,利用兩周時間每周一三學(xué)習(xí)周六整理歸納學(xué)習(xí)的東西發(fā)表一篇文章蕾羊,二四鍛煉喧笔,周五周日休息

SwiftUI 學(xué)習(xí)的想法

其實本來不是不打算先學(xué)習(xí)SwiftUI的,但是因為現(xiàn)在iOS14推出了小組件功能必須要用SwiftUI實現(xiàn)龟再,所以提前學(xué)習(xí)一下是很有必要的书闸。預(yù)計發(fā)表三篇關(guān)于SwiftUI的日志,分別整理SwiftUI的基礎(chǔ)布局SwiftUI里的響應(yīng)式布局利凑,最后結(jié)合一個實際項目輸出自己的心得
這篇文章參考的書籍是《SwiftUI和Combine編程》這本書第一二章浆劲,僅為自己學(xué)習(xí)歸納使用。

你好哀澈,SwiftUI

創(chuàng)建第一個項目

“選擇 iOS tab 下的 Single View App 模板牌借,Xcode 將為我們創(chuàng)建一個單頁的 iOS app「畎矗”“接下來膨报,將項目命名為 Calculator,并且選擇 “Use SwiftUI” 作為用戶界面的構(gòu)建方式适荣,讓 Xcode 使用 SwiftUI 來創(chuàng)建第一個畫面丙躏。“點擊 Next 并選擇合適的位置后束凑,我們可以得到一個使用 SwiftUI 作為界面開發(fā)方式的新項目晒旅。在項目導(dǎo)航中找到 ContentView.swift,它的內(nèi)容如下:

import SwiftUI
struct ContentView : View {
       var body: some View {
           Text("Hello World")
       }
}
struct ContentView_Previews : PreviewProvider {
        static var previews: some View {
              ContentView()
        }
}

上面代碼中 ContentView 所定義的是實際的 UI汪诉,ContentView_Previews 則是一個滿足了 PreviewProvider 的 dummy 界面废恋。如果你的操作系統(tǒng)是 macOS 10.15 或以上的話,你可以使用編輯器右上角的切換編輯器視圖按鈕扒寄,選中 “Editor and Canvas” (快捷鍵 Option + Command + 回車)鱼鼓,并單擊右上角的 Resume 按鈕。Xcode 預(yù)覽界面將會讀取 ContentView_Previews 的內(nèi)容该编,并渲染在編輯器右側(cè)的實時預(yù)覽欄中迄本。它就是 ContentView_Previews 的 previews 屬性所返回的 View,也就是 ContentView 的內(nèi)容课竣。默認情況下嘉赎,Xcode 會使用你當前選取的 iOS 模擬器作為預(yù)覽尺寸置媳,為了能讓你得到的內(nèi)容和書中一致,建議你在這個例子中選擇 iPhone XR 模擬器作為目標設(shè)備公条。

使用 Modifier 描述 Text 及 Button

除了改變 Text 的文本內(nèi)容外拇囊,我們會通過在 Text 聲明之后使用方法調(diào)用的方式,來定義文本樣式靶橱。讓我們從單個的計算器按鈕入手寥袭,比如加號鍵。將 ContentView 的 body 部分替換為以下內(nèi)容:

var body: some View {
        Text("+")
              .font(.title) /// 1
              .foregroundColor(.white) /// 2
              .padding() /// 3
              .background(Color.orange) /// 4
}

font关霸,foregroundColor传黄,padding 和 background 各自定義了 Text 的一項屬性:


image.png

font, foregroundColor, background 不用多說,padding將把當前的 View 包裹在一個新的 View 里队寇,并在四周填充空白部分尝江。在上面例子中,我們沒有給定參數(shù)英上,這會在文本的四周都填上系統(tǒng)默認尺寸的空白炭序。我們可以為 padding 指定需要填充的方向以及大小,比如:.padding(.top, 16) 將在上方填充 16 point 的空白苍日,.padding(.horizontal, 8) 在水平方向 (也即 [.leading, .trailing]) 填充 8 point

上面的四種方法被成為View的Modifier,modifier 一般來說對順序不敏感惭聂,對布局也不關(guān)心,它們更像是針對對象 View 本身的屬性的修改相恃。而與之相反辜纲,封裝類的 modifier 的順序十分重要。大致來說拦耐,view modifier 分為兩種類別:

像是 font耕腾,foregroundColor 這樣定義在具體類型 (比如例中的 Text) 上,然后返回同樣類型 (Text) 的原地 modifier杀糯。
像是 padding扫俺,background 這樣定義在 View extension 中,將原來的 View 進行包裝并返回新的 View 的封裝類 modifier固翰。

基本布局

Stack 容器

橫向排列的布局空間叫HStack狼纬,豎向排列的布局控件叫VStack, 疊加排列的布局控件叫ZStack,除了ZStack骂际,H/V Stack 類似UIStackView的橫向和縱向


struct CalculatorButton : View {
    let fontSize: CGFloat = 38
    let title: String
    let size: CGSize
    let backgroundColorName: String
    let action: () -> Void
    var body: some View {
        Button(action: action) {
            Text(title)
                .font(.system(size: fontSize))
                .foregroundColor(.white)
                .frame(width: size.width, height: size.height)
                .background(Color(backgroundColorName))
                .cornerRadius(size.width / 2)
        }
    }
}
這樣一來疗琉,ContentView 的 body 里可以簡化為:

var body: some View {
    HStack {
        CalculatorButton(
            title: "1",
            size: CGSize(width: 88, height: 88),
            backgroundColorName: "digitBackground")
        {
            print("Button: 1")
        }
        CalculatorButton(
            title: "2",
            size: CGSize(width: 88, height: 88),
            backgroundColorName: "digitBackground")
        {
            print("Button: 2")
        }
        CalculatorButton(
            title: "3",
            size: CGSize(width: 88, height: 88),
            backgroundColorName: "digitBackground")
        {
            print("Button: 3")
        }
        CalculatorButton(
            title: "+",
            size: CGSize(width: 88, height: 88),
            backgroundColorName: "operatorBackground")
        {
            print("Button: +")
        }
    }
}

ForEach

在項目中新建一個 Swift 文件,將它命名為 CalculatorButtonItem.swift歉铝,添加如下代碼:

enum CalculatorButtonItem {
    enum Op: String {
        case plus = "+"
        case minus = "-"
        case divide = "÷"
        case multiply = "×"
        case equal = "="
    }
    enum Command: String {
        case clear = "AC"
        case flip = "+/-"
        case percent = "%"
    }
    case digit(Int)
    case dot
    case op(Op)
    case command(Command)
}

我們可以粗略地把計算器上的按鈕分為四大類:代表從 0 至 9 的數(shù)字 digit盈简,小數(shù)點 dot,加減乘除等號這樣的操作 (Op),以及清空柠贤、符號翻轉(zhuǎn)等這類命令 (Command)香浩。接下來,可以在 extension 里追加定義必要的外觀:

extension CalculatorButtonItem {
    var title: String {
        switch self {
        case .digit(let value): return String(value)
        case .dot: return "."
        case .op(let op): return op.rawValue
        case .command(let command): return command.rawValue
        }
    }
    var size: CGSize {
        CGSize(width: 88, height: 88)
    }
    var backgroundColorName: String {
        switch self {
        case .digit, .dot: return "digitBackground"
        case .op: return "operatorBackground"
        case .command: return "commandBackground"
        }
    }
}
///@propertyWrapper
struct RowView: View {
    let row: [CalculatorButtonItem]
    
    @Binding var brain: JiSuanQiState
    
    var body: some View {
        HStack {
            ForEach(row, id: \.self) { item in
                BtnView(
                    content: item.title,
                    backgroundcolorName: item.backgroundColorName,
                    size: item.size,
                    action: {
                        debugPrint("輸出按鈕:\(item.title)")
                        self.brain = self.brain.apply(item: item)
                })
            }
        }
    }
}


struct JiSuanqiButtonPad: View {
    let pad: [[CalculatorButtonItem]] = [
        [.command(.clear), .command(.flip),
         .command(.percent), .op(.divide)],
        [.digit(7), .digit(8), .digit(9), .op(.multiply)],
        [.digit(4), .digit(5), .digit(6), .op(.minus)],
        [.digit(1), .digit(2), .digit(3), .op(.plus)],
        [.digit(0), .dot, .op(.equal)]
    ]
    @Binding var brain: JiSuanQiState
    var body: some View {
        VStack(spacing: 8) {
            ForEach(pad①, id: \.self②) { row in
                RowView(row: row, brain: self.$brain)③
            }
        }
    }
}

我們需要對此進行一些解釋种吸。ForEach 是 SwiftUI 中一個用來列舉元素弃衍,并生成對應(yīng) View collection 的類型呀非。它接受一個數(shù)組坚俗,且數(shù)組中的元素需要滿足 Identifiable 協(xié)議。如果數(shù)組元素不滿足 Identifiable岸裙,我們可以使用 ForEach(_:id:) 來通過某個支持 Hashable 的 key path 獲取一個等效的元素是 Identifiable 的數(shù)組猖败。在我們的例子中,數(shù)組 row 中的元素類型 CalculatorButtonItem 是不遵守 Identifiable 的降允。為了解決這個問題恩闻,我們可以為 CalculatorButtonItem 加上 Hashable,這樣就可以直接用 ForEach(row, id: .self) 的方式轉(zhuǎn)換為可以接受的類型了剧董。在 CalculatorButtonItem.swift 文件最后幢尚,加上一行:

extension CalculatorButtonItem: Hashable {}

使用 frame 或 Spacer 占滿屏幕

frame: 中的 minWidth 和 maxWidth 為 Text 的寬度定義了范圍,這會“提示” Text 不必遵守內(nèi)容尺寸翅楼,而是去適應(yīng)容器的尺寸尉剩。將 .infinity 傳遞給 maxWidth,表示不對最大寬度進行限制毅臊,這種情況下 Text 會盡可能占據(jù)它的容器的寬度理茎,變?yōu)槿翆挕rame 還提供了一個 alignment 參數(shù)管嬉,讓我們可以設(shè)定其對齊

Spacer: SwiftUI 允許我們定義可伸縮的空白:Spacer皂林,它會嘗試將可占據(jù)的空間全部填滿。在我們的 body 中蚯撩,可以加入一個 Spacer 來把 VStack 的上半部分全部填滿

VStack(spacing: 12) {
    Spacer()
    Text("0")
        .font(.system(size: 76))
        .minimumScaleFactor(0.5)
        .padding(.trailing, 24)
        .lineLimit(1)
        .frame(minWidth: 0, maxWidth: .infinity, alignment: .trailing)
    CalculatorButtonPad()
        .padding(.bottom)
}

至此一個完整的計算器頁面就算完成了


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末础倍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子胎挎,更是在濱河造成了極大的恐慌著隆,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呀癣,死亡現(xiàn)場離奇詭異美浦,居然都是意外死亡,警方通過查閱死者的電腦和手機项栏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門浦辨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事流酬”也蓿” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵芽腾,是天一觀的道長旦装。 經(jīng)常有香客問我,道長摊滔,這世上最難降的妖魔是什么阴绢? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮艰躺,結(jié)果婚禮上呻袭,老公的妹妹穿的比我還像新娘。我一直安慰自己腺兴,他們只是感情好左电,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著页响,像睡著了一般篓足。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闰蚕,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天栈拖,我揣著相機與錄音,去河邊找鬼陪腌。 笑死辱魁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的诗鸭。 我是一名探鬼主播染簇,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼强岸!你這毒婦竟也來了锻弓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蝌箍,失蹤者是張志新(化名)和其女友劉穎青灼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妓盲,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡杂拨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了悯衬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弹沽。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出策橘,到底是詐尸還是另有隱情炸渡,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布丽已,位于F島的核電站蚌堵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沛婴。R本人自食惡果不足惜吼畏,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瘸味。 院中可真熱鬧宫仗,春花似錦够挂、人聲如沸旁仿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枯冈。三九已至,卻和暖如春办悟,著一層夾襖步出監(jiān)牢的瞬間尘奏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工病蛉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炫加,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓铺然,卻偏偏與公主長得像俗孝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子魄健,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353