0
点赞
收藏
分享

微信扫一扫

SwiftUI100天:使用SwiftUI搭建一个计时器App

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

前言

为了更加熟悉和了解​​SwiftUI​​,本系列将从实战角度出发完成100个SwiftUI项目,方便大家更好地学习和掌握​​SwiftUI​​。

这同时也是对自己学习​​SwiftUI​​过程的知识整理。

如有错误,以你为准。

项目搭建

首先,创建一个新的​​SwiftUI​​​项目,命名为​​Timer​​。

SwiftUI100天:使用SwiftUI搭建一个计时器App_修饰符

逻辑分析

计时器的原理比较简单,对于用户而言主要操作就3个:开始暂停复位

用户点击开始按钮,计时器上的文字开始按照时间累加点击暂停时,计时器的数字停止并展示暂停时的数字,点击复位按钮,则计时器重新归零

但其中还是会有一些容易遗忘的逻辑,比如刚开始时,用户只能点击开始按钮,系统隐藏或者禁用暂停和复位操作。

而计时器开始计时后,用户只能点击暂停操作,系统隐藏或者禁用开始和复位操作。点击暂停按钮后,用户才能点击复位操作。

页面样式

了解完计时器的逻辑之后,我们来完成页面样式的设计。

SwiftUI100天:使用SwiftUI搭建一个计时器App_Swift_02

App标题

​App​​​标题,我们使用​​Text​​文本作为标题样式,示例:

// 计时器标题
func titleView() -> some View {
HStack {
Text("计时器")
.font(.title)
.fontWeight(.bold)
Spacer()
}
}

SwiftUI100天:使用SwiftUI搭建一个计时器App_Swift_03

为了让​​App​​​更加美观,我们在​​Assets​​​文件中导入了一张图片作为​​App​​主视图的展示,示例:

// 图片
func dinnerImageView() -> some View {
Image("dinner")
.resizable()
.scaledToFit()
}

SwiftUI100天:使用SwiftUI搭建一个计时器App_swift_04

上述代码中,我们给​​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)
}

SwiftUI100天:使用SwiftUI搭建一个计时器App_SwiftUI_05

上述代码中,我们使用​​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))
}
}

SwiftUI100天:使用SwiftUI搭建一个计时器App_swift_06

上述代码中,我们构建了一个圆形背景,设置大小为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))
}
}
}

SwiftUI100天:使用SwiftUI搭建一个计时器App_修饰符_07

整体样式布局

整体样式部分,由于操作区存在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)
}

SwiftUI100天:使用SwiftUI搭建一个计时器App_位操作_08

这样,样式部分我们就设计好了。

计时方法

方法创建

计时的方法主要使用到了​​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"

完成后,我们预览下项目成果。

项目预览

SwiftUI100天:使用SwiftUI搭建一个计时器App_swift_09

本章完整代码

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()
}
}

不错不错!

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


举报

相关推荐

0 条评论