注: 本文编写时,使用 Xcode 12.3、Swift 5.3.2 来构建 App
入门 Swift UI 已经有一段时间了,但是却一直没有写过什么练手项目,虽然之前跟着 Hackingwithswift 上找着写过几个 Demo。突然打算自己独立写一个练手项目,因为是练手项目,所以布局和功能上也很简单,App 的类型大概和 TODO 类似。
准备
打开 Xcode 新建一个项目在此不再展开。在左侧文件树中打开 ContentView.swift
,这是 View 的入口文件。你可以看到如下代码。
swift
代码语言:javascript复制1import SwiftUI
2
3struct ContentView: View {
4 var body: some View {
5 Text("Hello, world!")
6 .padding()
7 }
8}
9
10struct ContentView_Previews: PreviewProvider {
11 static var previews: some View {
12 ContentView()
13 }
14}
COPY
在 Swift UI 2.0 中,UI 主入口文件从复杂的 AppDelegate.swift
和 SceneDelegate.swift
转变为仅仅只有几行的 xxApp.swift
,得益于 Swift 5.3 加入的 @main
关键字
swift
代码语言:javascript复制1import SwiftUI
2
3@main
4struct MeetApp: App {
5 var body: some Scene {
6 WindowGroup {
7 ContentView()
8 }
9 }
10}
COPY
布局
HomeView
首先新建一个 View,Command N
选择 SwiftUI View,命名为 HomeView.swift
。将 HomeView 修改为如下代码。
swift
代码语言:javascript复制1struct HomeView: View {
2 var body: some View {
3 VStack {
4 Text("我不去想,是否能够成功 ,既然选择了远方 ,便只顾风雨兼程。")
5 .foregroundColor(.blue)
6 .padding(.vertical)
7
8 Text("hasty")
9 }.padding()
10 }
11}
COPY
接下来,绘制圆形 Button。在 Swift UI 中绘制图形十分简单,Swift UI 中内置了 Circle
组件,只要使用 ZStack 和 Circle 结合,很容易编写这个组件。
swift
代码语言:javascript复制1struct CircleButtonShape: View {
2 var systemImage: String
3 var color: Color = .pink
4 var body: some View {
5 ZStack {
6 Circle()
7 .fill(color)
8 .frame(width: 50, height: 50, alignment: .center)
9 .shadow(radius: 3)
10 Image(systemName: systemImage).foregroundColor(.white)
11 }
12 }
13}
COPY
这个组件绘制了整个图形,其中 Image 接收一个 SFSymbol 字符串。SF Symbols 可以在
这里下载
。绘制完了图形接下来需要在 View 中使用这个图形,并定位到对应的地点。
在 Swift UI 中,可以使用 ZStack 结合 .postion
定位到指定地点。为了获取到整个视窗的长宽,还需要 GeometryReader
去读取子 View 的长宽。在根 View 包裹可以获取到设备的长宽。
swift
代码语言:javascript复制1 GeometryReader { reader in
2 ZStack {
3 VStack {
4 Text("我不去想,是否能够成功 ,既然选择了远方 ,便只顾风雨兼程。")
5 .foregroundColor(.blue)
6 .padding(.vertical)
7
8 HStack {
9 Spacer()
10
11 Text("hasty")
12 }
13 }.padding()
14
15 Button(action: {
16 // TODO:
17 }, label: {
18 CircleButtonShape(systemImage: "arrow.clockwise")
19 })
20 .position(x: reader.size.width - 50, y: reader.size.height - 50)
21
22 }
23 }
COPY
接下来绘制底部的 ActionView。包含两个 Icon。
swift
代码语言:javascript复制1struct ActionView: View {
2 @State var liked = false
3
4 var body: some View {
5 HStack(spacing: 20) {
6 Button(action: {
7 }, label: {
8 Image(systemName: liked ? "suit.heart.fill" : "suit.heart")
9 .foregroundColor(liked ? .red : .primary)
10 .font(.custom("icon", size: 28))
11 })
12 Button(action: {
13 }, label: {
14 Image(systemName: "square.and.arrow.up")
15 .font(.custom("icon", size: 28))
16 .foregroundColor(.primary)
17 })
18 }
19 }
20}
COPY
在 HomeView 中 ZStack 末尾添加。
swift
代码语言:javascript复制1ActionView().offset(x: 0, y: reader.size.height / 2 - 50)
COPY
可以看到如图。
这里使用了 .offset
而不是 .position
去定位,是因为使用 position 去定位会丢失 width: 100%
,可以理解为 CSS 中 block
使用 absolute
之后变成了 inline-block
, 而使用 .offset
只是relative
中的定位。
TabView
接下来,绘制底部 Tabbar。在 Swift UI 中使用默认的 Tabbar 极为简单。只需要使用 TabView
即可。
在 xxApp.swift
(为你的 project_nameApp.swift,比如我的 Project 为 Meet,则为 MeetApp.swift
) 中增加 TabView
swift
代码语言:javascript复制1struct MeetApp: App {
2 var body: some Scene {
3 WindowGroup {
4 TabView {
5 ContentView().tabItem { Label("遇见", systemImage: "circle") }
6 }
7 }
8 }
9}
COPY
TabView 中每个 View 都会在底部 tab 中存在一个 Item,使用 .tabItem
定义这个 item 的文字和 image。有且只有一个 text 和 image。我们再新建一个 SwiftUI View 文件,命名为 LikeView.swift
。在 MeetApp.swift
中增加一个 View。
swift
代码语言:javascript复制1 TabView(selection: $activeTabIndex) {
2 ContentView().tabItem {
3 Label("遇见", systemImage: "largecircle.fill.circle")
4 }
5
6 LikeView().tabItem {
7 Label("喜欢", systemImage: "heart.circle.fill")
8 }
9 }
10 .accentColor(.pink) // 修改默认主题色
COPY
然后我们给 tabItem 增加 tag,让 Swift UI 知道当前选定的 tab 是哪个。如果被选中,修改为 Solid 的 Icon。当然我们可以使用 @State
和 .onTapGesture
实现。
swift
代码语言:javascript复制1@main
2struct MeetApp: App {
3 @State var activeTabIndex = 0
4
5 var body: some Scene {
6 return WindowGroup {
7 TabView(selection: $activeTabIndex) {
8 HomeView().tabItem {
9 Label("遇见", systemImage: activeTabIndex != 0 ? "circle" : "largecircle.fill.circle")
10 .onTapGesture {
11 activeTabIndex = 0
12 }
13 }
14 .tag(0)
15
16 LikeView().tabItem {
17 Label("喜欢", systemImage: activeTabIndex != 1 ? "heart.circle" : "heart.circle.fill")
18 .onTapGesture {
19 activeTabIndex = 1
20 }
21 }
22 .tag(1)
23 }
24 .accentColor(.pink)
25 }
26 }
27}
COPY
注意:.tag
是不可或缺的。否则无效。
大功告成!
下一篇文章,将构建数据层。
(未待完续)