前言
從本篇文章開始,我們來學習SwiftUI中常用的Views和Modifies。首先我們需要使用起來,以后有機會再看看比較深層次的知識點。
一、與OC文件的比較
在學習之前,我們來比較一下SwiftUI文件和OC文件所對應的控件添加區(qū)塊的不同,新建項目,默認Interface選擇SwiftUI,Language選擇Swift,并且新增一個OC文件,查看之間不一樣的地方:


可見,在SwiftUI里面,多了
Views(普通視圖)和Modifies(修飾視圖),左側列表中包括我們常用的所有視圖及容器控件,單擊左邊列表,右邊展示的是介紹及用法,所以一開始不知道怎么使用的話,可以參照右邊官方示例學習,或者更簡便的是可以直接拖拽進代碼里面,會自動生成代碼,不妨動手試下吧。
二、啟動圖設置
SwiftUI里面我們可以直接在TARGETS -- {主項目} -- info文件中直接設置啟動圖的一些屬性樣式,其中包括圖片,背景顏色等等,我們可以試著設置下啟動圖片:

或者可以設置一個顏色值,就可以設置啟動時候的背景顏色,刪除上面添加的啟動圖,設置
Background color:

重啟工程,發(fā)現(xiàn)啟動背景顏色就改成了當前的
AccentColor。(拓展:在Assets里面也可以自定義顏色值,當前的AccentColor也可用于項目代碼中,類似AccentColor一樣自定義一個顏色值,也可用于項目中)
三、常用Views / Modifies
1、Text & Label
Text 更像是OC中的UILabel,而Label更傾向于一個<標簽>:
VStack{
Text("Hello, world!").padding().foregroundColor(Color("nowColor"))
Label("Label1111", systemImage: "42.circle")
}
其中nowColor為上方在Assets中自定義的顏色值,顯示效果如下:

Text里的字符串可以使用
markdown語法,你也可以試試,更多使用:
//Text
Text("ContentView_Previews").italic()
.frame(width: 180,height: 100,alignment: .center)
.background(Color.red)
//Label
Label {
//此處可以添加按鈕等視圖一樣可以實現(xiàn)
Text("我是Label")
} icon: {
Circle().stroke(lineWidth: 5).foregroundColor(.red).frame(width: 34,height: 34,alignment: .center).overlay(Text("5"))
}
2、Button / Link
按鈕及按鈕點擊事件,用法如下:
Button {
print("tapbTN1")
} label: {
Text("button1").foregroundColor(.blue)
}
Button(action: {
print("tapBtn2")
}) {
Text("Button2")
}
//簡單實用
Button("button3") {
print("buton3")
}.foregroundColor(.green).font(.largeTitle)
Link是鏈接外鏈的視圖:
Link(destination: URL(string: "https://www.baidu.com")!) {
Text("Link1")
}
Link("Link2", destination: URL(string: "https://www.baidu.com")!)
3、Image / AsyncImage
將Image視圖拖進代碼,如下:
Image("Image Name")
//Image("33")
當我們將上文中33寫入時,發(fā)現(xiàn)圖片并不展示,此處有個坑,即此種寫法它會去找Assets里面有沒有33,上文中圖片33是直接拉進來的,所以不展示,那么我們可以用下面方式顯示33:
Image(uiImage: .init(named: "33")!)
但官方推薦使用在Assets里面添加圖片的方式,寫法簡單點。
另外注意圖片的填充方式:
//撐開屏幕,占滿父視圖(Vstack)剩余位置,不遵循寬高比(會壓縮)
Image(uiImage: .init(named: "33")!).resizable()
//會根據(jù)寬高比展示,寬 > 高 以屏幕寬為最大,高 > 寬 以父視圖剩余空間撐開,VStack上下?lián)伍_
Image(uiImage: .init(named: "66")!).resizable().scaledToFit()
//會根據(jù)寬高比展示,寬 > 高 以父視圖剩余空間撐開,VStack上下?lián)伍_,只展示圖片部分內容(會裁剪), 高 > 寬 以屏幕最大寬度,按寬高比展示圖片部分內容(會裁剪)
Image(uiImage: .init(named: "88")!).resizable().scaledToFill()
WWDC21 新出的AsyncImage類似我們OC中的三方庫SD_WebImage,
AsyncImage(url: URL.init(string: "https://img1.baidu.com/it/u=333716107,1538170532&fm=253&fmt=auto&app=138&f=JPEG?w=600&h=488")) { img in
img.resizable()
} placeholder: {
ProgressView()
}.frame(width: 120,height: 200)
其中img.resizable()不可省略,不寫的話,不能展示,即不寫的話就是沒有對加載結果圖片做顯示操作。
4、TimelineView / Canvas
TimelineView:按計劃地去更新View。比如多久刷新一次View,也可以設定開始日期然后以怎樣的時間間隔去刷新等。
- 每隔一分鐘去更新
TimelineView(.everyMinute) { context in
}
- 從某個時間點開始,隔多久更新
TimelineView(.periodic(from: Date.now, by: 1.0)) { context in
Text(context.date.description).font(.largeTitle)
}
類似我們經常使用的NSTimer定時器,這種用法使用的比較多。
- 以不規(guī)則的一組時間節(jié)點去刷新
TimelineView(.explicit(...
Canvas:繪制畫布,提供一塊區(qū)域供我們畫想畫的東西。
Canvas { context, size in
context.stroke(Path(ellipseIn: CGRect(origin: .zero, size: size)), with: .color(.orange),lineWidth: 5)
}.frame(width: 100,height: 50).border(.blue,width: 3)

5、TextEditor / TextField
使用TextEditor時需要指定frame,因為它的布局默認是填充滿父視圖剩余空間,功能上類似于TextView:
TextEditor(text: .constant("hsjshjhajhjsh")).frame(width: 200,height: 200)
運行在模擬器上,發(fā)現(xiàn)鍵盤輸入后,TextEditor上的文字不變,這里需要定義數(shù)據(jù)@State var string = "okokokok",用string來裝載輸入的東西,@State我們此處不去做解釋。
TextEditor(text: $string).frame(width: 200,height: 200)
這樣才能達到實時顯示輸入內容。
TextField就是之前一樣的輸入框,使用如下:
TextField("PlaceHolder", text: $string).textFieldStyle(.roundedBorder).font(.largeTitle).frame(width: 200, height: 50)
//.focused()光標是否在上面
//捕捉點擊Return 按鈕事件
.onSubmit {
print("點擊了Return")
}
.textInputAutocapitalization()設置輸入框的英文文字是否大小寫:
-
.characters輸入的字母全轉大寫 -
.words輸入的字母以空格為分界,自動轉換為一個個單詞 -
.never輸入內容全為小寫
.disableAutocorrection()設置鍵盤輸入時的矯正,但平時我們不怎么希望用到此功能,可置為true或false。
注意:當我們給輸入框添加圓角邊框時候,如果使用下面方式效果不是很好,顯示不全:
.border(.red,width: 1)
.cornerRadius(20)

此時我們可以使用下面這種方式來給TextField添加邊框:
.overlay {
RoundedRectangle(cornerRadius: 20).stroke(.red,lineWidth: 10)
}

輸入框使用中,我們需要關注鍵盤的隱藏,需要給界面一個點擊事件,例如輸入框在一個Vstack中,那需要給這個Vstack 添加點擊事件:
.onTapGesture {
print("tap")
//熱區(qū)
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
點擊Return能隱藏鍵盤,點擊上方區(qū)域時,發(fā)現(xiàn)點擊文字或別的視圖顯示區(qū)域可以隱藏鍵盤,但是Vstack區(qū)域內視圖顯示區(qū)域之外的空白區(qū)域不能將鍵盤隱藏,所以我們還需要給Vstack添加一個熱區(qū):
.contentShape(Rectangle())
熱區(qū)意思就是在這個Vstack區(qū)域內的點擊都能響應.onTapGesture事件。
6、Picker
一個彈窗選擇器(Selecter)控件,代碼如下:
enum Person: String{
case Tom
case Jim
case Lily
case Lucy
}
@State var selectedPerson = Person.Lily
var body: some View {
VStack {
Picker(selection: $selectedPerson, label: Text("Picker")) {
Text("Tom").tag(Person.Tom)
Text("Jim").tag(Person.Jim)
Text("Lily").tag(Person.Lily)
Text("Lucy").tag(Person.Lucy)
}
Text("slected Person: \(selectedPerson.rawValue)")
}
.padding()
}

.pickerStyle(.inline)當給Picker改變樣式時候,展示的樣式截然不同,此處使用.inline就類似.wheel的樣式,也可切換著看看.segmented,此種樣式就是UIKit里的UISegmentedControll。默認是.menu樣式。(可以試著將Picker單獨放在NavigationView中查看.pickerStyle不一樣的地方)
拓展 1:SwiftUI中的文本長按選擇會出現(xiàn)Copy/Share...彈框,默認是關閉的:
//disabled 關閉 enabled 開啟
Text("slected Person").textSelection(.enabled)

7、ProgressView
進度條ProgressView,
ProgressView(value: 0.5,total: 10,label: {
//上面文字
Text("Lcr")
},currentValueLabel: {
//下面文字
Text("Lcr111")
}).background(.green)
當加上背景顏色之后,感覺視覺效果不是很好(背景色會覆蓋進度條上下文字背景),所以個人覺得還是去掉上下文字,保留一條進度條就好,然后另外寫上下Text去展示需要展示的數(shù)據(jù)。
.tint(.red) 設置前面進度條的顏色。
ProgressView(),這種寫法默認(.circular)就是小菊花樣式,.linear 進度條樣式。
上面的進度條改變樣式后也可以變成小菊花樣式。
ProgressView().progressViewStyle(.circular)
ProgressView().progressViewStyle(.linear)
8、Slider
滑塊控件Slider
@State var speed = 0 //當前值
//value當前值,in值的范圍0-100,step每次滑動變化值,onEditingChanged監(jiān)聽滑動事件
//.tint 滑塊左側進度條顏色
Slider(value:$speed,in: 0...100,step: 25,onEditingChanged: { editing in
print(editing)
}).tint(.red)
如果需要展示前后的數(shù)值文字:
Slider(value: $speed, in: 0...100,step: 25) {
} minimumValueLabel: {
Text("0")
} maximumValueLabel: {
//Text("100")
Text("\(speed)")
}
拓展:一個容器(如VStack)里面的字視圖最多不能超過10個View
9、Toggle / Stepper
類似OC中的UISwitch,開關控件
@State var toggleStatus = false
Toggle(isOn: $toggleStatus) {
Text("Toggle")
}.tint(.clear).colorMultiply(.orange)
Stepper類似購物車加減按鈕:
@State var value = 0
Stepper(value: $value, in: 0...9, step: 3) {
Text("\(value)").font(.largeTitle)
}
每次增加減少都是以3來計算。
@State var value = 0
let colors:[Color] = [.orange, .red, .gray, .blue, .green, .purple, .pink]
func stepperAction(isIncrement: Bool) {
value = value + (isIncrement ? 1 : -1)
if value >= colors.count {value = 0}
if value < 0 {value = colors.count - 1}
}
Stepper {
Text("Value: \(value) Color: \(colors[value].description)").foregroundColor(colors[value]).font(Font.system(.title2).bold())
} onIncrement: {
stepperAction(isIncrement: true)
} onDecrement: {
stepperAction(isIncrement: false)
}

本文學習了一些基本的視圖控件的使用,有時間拿起鍵盤敲起來,動動手加深印象,gogogo!!!