오늘은 제가 직접 만든 Todo 앱에 포함된 Timer 기능을 소개하고 리뷰해보려고 해요.
이번 포스팅에서는 총 세 가지 기능을 중심으로 살펴볼게요.
- ⏱ Timer 설정 및 시작
- ❌ Timer 취소
- ⏸ Timer 일시정지 및 재개
하나씩 자세히 확인해볼게요!
⏱ Timer 설정 및 시작
1. 시간 설정: Picker로 시/분/초 선택
Picker("시", selection: $selectedHour) {
ForEach(0..<24) { hour in
Text("\(hour) 시간").tag(hour)
}
}
.pickerStyle(.wheel)
.frame(width: 100)
- Picker를 사용해 시, 분, 초를 각각 선택할 수 있게 구성했습니다.
- 선택된 값은 @Binding으로 상위 뷰(TimerView)와 연결되어 공유됩니다.
@Binding var selectedHour: Int
@Binding var selectedMinute: Int
@Binding var selectedSecond: Int
2. 타이머 시작 버튼: 상태값 변경으로 화면 전환
Button {
timerStart = true
} label: {
Text("설정하기")
}
- 설정이 완료되면 timerStart를 true로 변경하여 화면이 TimerSettingView → TimerStartView로 전환됩니다.
- 이때부터 타이머가 본격적으로 작동하게 됩니다.
3. 타이머 시작 시 로직: onAppear로 초기화
.onAppear {
remainingSeconds = (selectedHour * 3600) + (selectedMinute * 60) + selectedSecond
targetEndDate = Date().addingTimeInterval(TimeInterval(remainingSeconds))
}
- 사용자가 설정한 시/분/초 값을 초 단위로 변환하여 remainingSeconds에 저장합니다.
- 동시에 targetEndDate도 계산하여 종료 예상 시각을 표시합니다.
4. 남은 시간 갱신: Timer와 onReceive
.onReceive(timer) { _ in
guard !timerPause else { return }
if remainingSeconds > 0 {
remainingSeconds -= 1
} else {
timerPause = true
timerStart = false
}
}
- Timer.publish(...).autoconnect()를 통해 1초마다 remainingSeconds를 감소시키며 타이머가 작동합니다.
- 0이 되면 자동으로 타이머가 종료되며, 다시 설정 화면으로 돌아가게 됩니다.
❌ Timer 취소
Button {
timerPause = false
timerStart = false
} label: {
Circle()
.aspectRatio(contentMode: .fit)
.frame(width: Constants.ControlWidth * 70)
.foregroundColor(.gray1)
.overlay {
Text("취소")
.font(.system(size: 14))
.foregroundColor(.iconOn)
}
}
- 취소 버튼 클릭 시 실행중인 타이머를 중지 시킴과 동시에 설정화면으로 자동 복귀 됩니다.
⏸ Timer 일시정지 및 재개
1. 상태 변수 선언
@State var timerPause: Bool = false
@State private var remainingSeconds: Int = 0
@State private var targetEndDate: Date? = nil
- timerPause: 현재 타이머가 일시정지 상태인지 여부
- remainingSeconds: 현재 남은 시간 (초 단위)
- targetEndDate: 예상 종료 시각
2. 타이머 동작 로직에서 일시정지 처리
.onReceive(timer) { _ in
guard !timerPause else { return } // 일시정지 상태면 카운트 X
if remainingSeconds > 0 {
remainingSeconds -= 1
} else {
timerPause = true
timerStart = false
// 타이머 종료 처리
}
}
- timerPause == true인 경우, .onReceive 내에서 시간 감소 로직을 건너뜁니다.
- 즉, 일시정지 상태에서는 타이머가 멈춘 듯한 효과를 냅니다.
3. 일시정지 / 재생 버튼 구현
Button {
timerPause.toggle()
if !timerPause {
// 재생 상태로 전환될 때 종료 예정 시간 다시 설정
targetEndDate = Date().addingTimeInterval(TimeInterval(remainingSeconds))
}
} label: {
Circle()
.frame(width: Constants.ControlWidth * 70)
.foregroundColor(.key)
.overlay {
Text(timerPause ? "재생" : "일시정지")
.font(.system(size: 14))
.foregroundColor(.iconOn)
}
}
4. 버튼 누를 때의 상태 변화
이전 상태 | 버튼 클릭 후 | 설명 |
timerPause = false | true | 타이머 정지 (시간 감소 중단) |
timerPause = true | false | 타이머 재생 (카운트 재개) + 종료 시각 재계산 |
5. 종료 예정 시각 표시
func targetEndDateString() -> String {
guard let targetEndDate else { return "--:--" }
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm"
return formatter.string(from: targetEndDate)
}
- 생 시마다 targetEndDate를 다시 갱신하므로, UI 상에 표시되는 시간도 업데이트됩니다.
- 일시정지 중에는 시간 변동이 없습니다.
🎬 Timer 기능 구현 영상
🧩 마무리
오늘은 이렇게 나만의 Todo 앱에 적용한 타이머 기능을 간단히 리뷰해보았습니다!
history탭 같은 경우에는 기능 자체가 너무 간단하기에 UIKit에서 리뷰한것을 참고하시면 좋을 것 같습니다!
📌 전체 코드가 궁금하신 분들은 GitHub 저장소를 참고해주세요:
👉 🔗 GitHub 저장소 바로가기
GitHub - woolnd/SwiftUI_MyTodo
Contribute to woolnd/SwiftUI_MyTodo development by creating an account on GitHub.
github.com
다음 포스팅에서는 작년 하반기 회고록을 오랜만에 작성해볼까 합니다!
앞으로도 꾸준히 기록하며 성장하는 개발자가 되기 위해 달려보겠습니다! 🚀
읽어주셔서 감사합니다 😊
'iOS 개발 > SwiftUI' 카테고리의 다른 글
[SwiftUI] Memo 기능 리뷰 (Feat. 나만의 Todo) (4) | 2025.06.06 |
---|---|
[SwiftUI] Todo 기능 리뷰 (Feat. 나만의 Todo) (3) | 2025.06.02 |
[SwiftUI] @Published 썼는데도 View가 안 바뀐다? (0) | 2025.05.30 |
[SwiftUI] Splash & Onboarding 화면 구현 리뷰 (Feat. 나만의 Todo) (1) | 2025.05.26 |
[SwiftUI] Figma 비율 그대로! 개발하는 Constants 구조체 만들기 (1) | 2025.05.24 |