使用SwiftUI搭建一个倒计时App,让你做饭时不再焦虑~

3,917 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

在本章中,你将学会使用SwiftUI搭建一个倒计时App

项目背景

每逢周末,总要在家里煮顿饭,才对得起满冰箱的菜,和打扫得干干净净的厨房。

在做海鲜的时候,常常会需要准确计时,煮久了不好吃,煮得时间太短又不熟。

这时候,就需要有一个倒计时的App,帮助我们很好地控制时间。

那么本章,我们就来使用SwiftUI搭建一个倒计时App

项目搭建

首先,创建一个新的SwiftUI项目,命名为CountDown

1.png

样式预览

2.png

背景圆环

背景圆环的样式,我们可以使用Circle圆形形状来搭建,示例:

// 背景圆环
func progressTrackView() -> some View {
    Circle()
        .fill(Color.clear)
        .frame(width: 250, height: 250)
        .overlay(Circle().stroke(Color.black.opacity(0.09), lineWidth: 15))
}

3.png

上述代码中,我们创建了一个新的视图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
                    }
                )
        )
}

4.png

上述代码中,我们构建了一个进度圆环视图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)
}

5.png

操作按钮

倒计时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)
}

6.png

上述代码中,我们构建了一个操作栏视图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()
    }
}

项目预览

7.gif

恭喜你,完成了整个项目的全部内容!

快来动手试试吧。

如果本专栏对你有帮助,不妨点赞、评论、关注~