前言
为了更加熟悉和了解SwiftUI
,本系列将从实战角度出发完成100个SwiftUI项目,方便大家更好地学习和掌握SwiftUI
。
这同时也是对自己学习SwiftUI
过程的知识整理。
如有错误,以你为准。
项目搭建
首先,创建一个新的SwiftUI
项目,命名为Timer
。
逻辑分析
计时器的原理比较简单,对于用户而言主要操作就3个:开始、暂停、复位。
用户点击开始按钮,计时器上的文字开始按照时间累加,点击暂停时,计时器的数字停止并展示暂停时的数字,点击复位按钮,则计时器重新归零。
但其中还是会有一些容易遗忘的逻辑,比如刚开始时,用户只能点击开始按钮,系统隐藏或者禁用暂停和复位操作。
而计时器开始计时后,用户只能点击暂停操作,系统隐藏或者禁用开始和复位操作。点击暂停按钮后,用户才能点击复位操作。
页面样式
了解完计时器的逻辑之后,我们来完成页面样式的设计。
App标题
App
标题,我们使用Text
文本作为标题样式,示例:
// 计时器标题 func titleView() -> some View { HStack { Text("计时器") .font(.title) .fontWeight(.bold) Spacer() } } 复制代码
为了让App
更加美观,我们在Assets
文件中导入了一张图片作为App
主视图的展示,示例:
// 图片 func dinnerImageView() -> some View { Image("dinner") .resizable() .scaledToFit() } 复制代码
上述代码中,我们给Image
图片设置了2个修饰符,进行等比例缩放。
这样,我们就得到了标题和App
示例图片。
计时文字
计时文字部分,首先我们需要声明一个变量存储我们的计时数值,示例:
@State var timeText: String = "0.00" 复制代码
然后,我们可以使用Text
绑定并展示计时的文字,示例:
// 计时文字 func timerTextView() -> some View { Text(timeText) .font(.system(size: 48)) .padding(.horizontal) .background(Color(.systemGray6)) .cornerRadius(8) } 复制代码
上述代码中,我们使用Text
文字样式,绑定timeText
参数,并使用了一些修饰符设置了文字的大小、计时文字的排布位置、背景颜色和圆角。
操作按钮
对于操作按钮部分,我们需要3个按钮:开始按钮、暂停按钮、复位按钮。
开始按钮
开始按钮部分,由于和其他按钮样式分离,我们可以单独构建,示例:
// 开始按钮 func startBtn() -> some View { ZStack { Circle() .frame(width: 60, height: 60) .foregroundColor(.green) Image(systemName: "play.fill") .foregroundColor(.white) .font(.system(size: 32)) } } 复制代码
上述代码中,我们构建了一个圆形背景,设置大小为60*60,颜色为绿色。按钮本身使用Apple
提供的系统图标,设置尺寸为32,填充颜色为白色。
暂停和复位
当我们点击开始按钮,那么操作按钮就会变成2个:暂停和复位。
其中,暂停按钮有2种状态,一种是未操作时,一种则是已经点击暂停,因此我们需要声明一个是否暂停的变量来存储它,示例:
@State var isPause: Bool = false 复制代码
然后和开始按钮一样,我们构建暂停和复位按钮的样式,示例:
// 暂停和复位按钮 func pauseAndResetBtn() -> some View { HStack(spacing: 60) { // 暂停按钮 ZStack { Circle() .frame(width: 60, height: 60) .foregroundColor(.red) Image(systemName: isPause ? "play.fill" : "pause.fill") .foregroundColor(.white) .font(.system(size: 32)) } // 复位按钮 ZStack { Circle() .frame(width: 60, height: 60) .foregroundColor(.blue) Image(systemName: "arrow.uturn.backward.circle.fill") .foregroundColor(.white) .font(.system(size: 32)) } } } 复制代码
整体样式布局
整体样式部分,由于操作区存在2种样式,一种是点击开始前,一种是点击计时开始,我们还需要声明一种是否开始的状态存储它,示例:
@State var isStart: Bool = true 复制代码
最后是样式的整体部分,我们在body
中布局样式,示例:
var body: some View { VStack(spacing: 20) { titleView() dinnerImageView() timerTextView() Spacer() //操作按钮 if isStart { pauseAndResetBtn() } else { startBtn() } } .padding() .padding(.bottom, 40) } 复制代码
这样,样式部分我们就设计好了。
计时方法
方法创建
计时的方法主要使用到了Timer
函数,首先我们要声明两个变量,一个用来更新复位后的时间,一个用来计数,示例:
@State private var startTime = Date() @State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() 复制代码
然后创建两个方法,一个用来开始计数,一个用来停止计数,示例:
// 开始计时方法 func startTimer() { timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect() } // 停止计时方法 func stopTimer() { timer.upstream.connect().cancel() } 复制代码
开始计时
然后在点击开始按钮时,调用开始计数的方法,示例:
// 开始按钮 func startBtn() -> some View { ZStack { Circle() .frame(width: 60, height: 60) .foregroundColor(.green) Image(systemName: "play.fill") .foregroundColor(.white) .font(.system(size: 32)) }.onTapGesture { self.isStart = true timeText = "0.00" startTime = Date() self.startTimer() } } 复制代码
上述代码中,我们使用onTapGesture
修饰符给开始按钮添加交互,当我们点击开始按钮时,首先转换isStart
状态,这样我们的操作按钮样式就会切换到暂停和复位的操作。
然后是timeText
初始化展示内容为0.00
,然后startTime
从当前timeText
开始,再调用startTimer
方法开始计时。
停止计时
停止计时方法也很简单,不过这里要注意的是,暂停按钮承载了暂时和继续计时的操作,示例:
// 暂停和复位按钮 func pauseAndResetBtn() -> some View { HStack(spacing: 60) { // 暂停按钮 ZStack { Circle() .frame(width: 60, height: 60) .foregroundColor(.red) Image(systemName: isPause ? "play.fill" : "pause.fill") .foregroundColor(.white) .font(.system(size: 32)) } .onTapGesture { if !isPause { self.isPause = true self.stopTimer() } else { self.isPause = false self.startTimer() } } } 复制代码
上述代码中,我们也给暂停按钮添加了交互,当我们isPause
没有停止时,我们点击暂停按钮,则isPause
状态切换为停止,这样我们对应的暂停按钮的样式也会切换,然后调用stopTimer
停止计时的方法。
而当我们暂停的时候点击暂停按钮时,我们切换isPause
状态更新样式,同时又调用startTimer
开始计时的方法继续计时。
计时复位
对于复位操作,我们要简单很多,我们只需要在点击时将isStart
、isPause
更新为false
,最后把计时展示文字timeText
更新为0.00
就可以了。代码如下:
// 复位按钮 ZStack { Circle() .frame(width: 60, height: 60) .foregroundColor(.blue) Image(systemName: "arrow.uturn.backward.circle.fill") .foregroundColor(.white) .font(.system(size: 32)) } .onTapGesture { self.isStart = false self.isPause = false timeText = "0.00" } 复制代码
完成后,我们预览下项目成果。
项目预览
本章完整代码
import SwiftUI struct ContentView: View { @State var timeText: String = "0.00" @State var isPause: Bool = false @State var isStart: Bool = false @State private var startTime = Date() @State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() var body: some View { VStack(spacing: 20) { titleView() dinnerImageView() timerTextView() Spacer() // 操作按钮 if isStart { pauseAndResetBtn() } else { startBtn() } } .padding() .padding(.bottom, 40) } // 计时器标题 func titleView() -> some View { HStack { Text("计时器") .font(.title) .fontWeight(.bold) Spacer() } } // 图片 func dinnerImageView() -> some View { Image("dinner") .resizable() .scaledToFit() } // 计时文字 func timerTextView() -> some View { Text(timeText) .font(.system(size: 48)) .padding(.horizontal) .background(Color(.systemGray6)) .cornerRadius(8) .onReceive(timer) { _ in if self.isStart { timeText = String(format: "%.2f", Date().timeIntervalSince(self.startTime)) } } } // 开始按钮 func startBtn() -> some View { ZStack { Circle() .frame(width: 60, height: 60) .foregroundColor(.green) Image(systemName: "play.fill") .foregroundColor(.white) .font(.system(size: 32)) }.onTapGesture { self.isStart = true timeText = "0.00" startTime = Date() self.startTimer() } } // 暂停和复位按钮 func pauseAndResetBtn() -> some View { HStack(spacing: 60) { // 暂停按钮 ZStack { Circle() .frame(width: 60, height: 60) .foregroundColor(.red) Image(systemName: isPause ? "play.fill" : "pause.fill") .foregroundColor(.white) .font(.system(size: 32)) } .onTapGesture { if !isPause { self.isPause = true self.stopTimer() } else { self.isPause = false self.startTimer() } } // 复位按钮 ZStack { Circle() .frame(width: 60, height: 60) .foregroundColor(.blue) Image(systemName: "arrow.uturn.backward.circle.fill") .foregroundColor(.white) .font(.system(size: 32)) } .onTapGesture { self.isStart = false self.isPause = false timeText = "0.00" } } } // 开始计时方法 func startTimer() { timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect() } // 停止计时方法 func stopTimer() { timer.upstream.connect().cancel() } } 复制代码
不错不错!
如果本专栏对你有帮助,不妨点赞、评论、关注~