携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情。
在本章中,你将学会使用SwiftUI
搭建一个倒计时App
。
项目背景
每逢周末,总要在家里煮顿饭,才对得起满冰箱的菜,和打扫得干干净净的厨房。
在做海鲜的时候,常常会需要准确计时,煮久了不好吃,煮得时间太短又不熟。
这时候,就需要有一个倒计时的App
,帮助我们很好地控制时间。
那么本章,我们就来使用SwiftUI
搭建一个倒计时App
。
项目搭建
首先,创建一个新的SwiftUI
项目,命名为CountDown
。
样式预览
背景圆环
背景圆环的样式,我们可以使用Circle
圆形形状来搭建,示例:
// 背景圆环
func progressTrackView() -> some View {
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(Circle().stroke(Color.black.opacity(0.09), lineWidth: 15))
}
上述代码中,我们创建了一个新的视图progressTrackView
。
我们使用Circle
构建背景圆环,使用fill
修饰符填充颜色去掉背景,再使用frame
修饰符设置大小,最后使用overlay
修饰符赋予了圆环线宽做边框。
进度圆环
完成背景圆环后,我们来完成进度圆环。
首先我们需要两个倒计时参数,一个是总倒计时时间,一个是倒计时当前时间,示例:
@State var totalCountdown: CGFloat = 30
@State var counter: Int = 10
然后我们还需要创建一个方法获得开始时的进度位置,示例:
// 获得开始进度
func startProgress() -> CGFloat {
return (CGFloat(counter) / CGFloat(totalCountdown))
}
同样,我们还需要创建一个方法来获得结束时的进度位置,示例:
// 获得结束进度
func completed() -> Bool {
return startProgress() == 1
}
完成这些基础准备后,我们就可以来构建进度圆环视图了,示例:
// 进度圆环
func progressBarView() -> some View {
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle()
.trim(from: 0, to: startProgress())
.stroke(style: StrokeStyle(lineWidth: 15, lineCap: .round, lineJoin: .round))
.rotationEffect(.init(degrees: -90))
.foregroundColor(
withAnimation(.easeInOut(duration: 0.2)) {
completed() ? Color.green : Color.orange
}
)
)
}
上述代码中,我们构建了一个进度圆环视图progressBarView
。
我们依旧使用Circle
来构建圆环,我们在Circle
圆环的基础上overlay
覆盖一个圆环,外边的圆环需要和背景圆环尺寸保持一致。
进度圆环使用trim
绘制进度,使用stroke
修饰符绘制边框,使用rotationEffect
进度旋转获得进度变化,使用foregroundColor
绘制背景颜色,当进度为0
的时候变成绿色。
进度时间
完成背景圆环和进度圆环后,还需要显示当前的进度时间,我们可以创建一个方法来获得格式化的时间,示例:
// 获得格式化时间
func counterToMinutes() -> String {
let currentTime = Int(totalCountdown) - counter
let seconds = currentTime % 60
let minutes = Int(currentTime / 60)
return "\(minutes):\(seconds < 10 ? "0" : "")\(seconds)"
}
上述代码中,我们构建了一个格式化字符串的方法counterToMinutes
,主要为了根据秒钟转换为格式化的字符串。
然后我们构建进度时间样式,示例:
// 进度时间
func progressTimeView()-> some View {
Text(counterToMinutes())
.font(.system(size: 48))
.fontWeight(.black)
}
操作按钮
倒计时App的操作按钮和之前做过的计时器App的操作类似,一个开始按钮,点击开始按钮后,开始按钮变成暂停按钮。
另一个是重置按钮,点击重置后,回归初始状态。
首先我们需要先声明一个开始状态的参数,示例:
@State var isStart = false
然后使用Image
和系统图标构建样式部分,示例:
// 操作按钮
func btnView() -> some View {
HStack(spacing: 55) {
// 开始按钮
Image(systemName: self.isStart ? "pause.fill" : "play.fill")
.font(.system(size: 40))
.foregroundColor(.white)
.frame(minWidth: 0, maxWidth: 80, minHeight: 0, maxHeight: 80)
.background(self.isStart ? .red : .green)
.clipShape(Capsule())
.onTapGesture {
self.isStart.toggle()
}
// 重置按钮
Image(systemName: "arrow.clockwise")
.font(.system(size: 40))
.foregroundColor(.white)
.frame(minWidth: 0, maxWidth: 80, minHeight: 0, maxHeight: 80)
.background(.blue)
.clipShape(Capsule())
.onTapGesture {
self.counter = 0
withAnimation(.default) {
self.totalCountdown = 30
}
}
}.padding(.bottom, 55)
}
上述代码中,我们构建了一个操作栏视图btnView
。
这里使用HStack
横向视图排布了2个按钮,当我们点击开始按钮时,样式会随isStart
变化,以便于我们操作开始和暂停。
重置按钮,我们也加了一个点击事件,点击时将当前进度counter
设置为0,总进度totalCountdown
设置回30。
开始计时
为达到倒计时效果,我们需要创建一个方法,当我们开始计时时,若当前进度小于总进度,则当前进度累加,示例:
// 开始计时
func startCounting() {
if self.isStart {
if (self.counter < Int(self.totalCountdown)) {
self.counter += 1
}else {
self.isStart.toggle()
}
}
}
然后我们声明一个变量,返回以给定间隔重复发出当前日期的发布者,示例:
@State var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
最后,我们将计时方法加到视图中,并排布已经创建好的元素。示例:
var body: some View {
VStack {
Spacer()
ZStack {
progressTrackView()
progressBarView()
progressTimeView()
}
Spacer()
btnView()
}.onReceive(timer) { time in
self.startCounting()
}
}
项目预览
恭喜你,完成了整个项目的全部内容!
快来动手试试吧。
如果本专栏对你有帮助,不妨点赞、评论、关注~