使用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() } }
项目预览
恭喜你,完成了整个项目的全部内容!
快来动手试试吧。