(iOS)뽀모도로 타이머 앱 만들기
기능 상세
DatePicker
를 통해 타이머 시간을 설정한다.
- 시작 버튼을 누르면 타이머가 시작되고 일시 정지를 누르면 타이머가 일시 정지된다.
- 취소 버튼을 누르면 타이머가 종료된다.
- 카운트 다운이 완료되면 알람이 울린다.
활용 기술
DispatchSourceTimer
UIViewAnimation
구현
- 타이머를 구현할 때
DispatchSourceTimer
를 사용
- 타이머를 만드는 함수의 인자값 중 queue
부분에 .main
을 선언하여 타이머가 메인 쓰레드에서 돌도록 한다.(UI와 관련된 부분은 모두 메인 쓰레드에서 담당한다고 한다.)
- 타이머가 suspend
인 상태에서 nil
을 입력받게 되면 즉, 일시정지된 상태에서 취소를 누르게 되면 Runtime Error
가 발생하게 된다. 따라서 취소하기 전에 타이머의 상태를 resume
으로 만들어줘야 한다.
UIViewAnimation
을 사용하여 타이머를 시작하거나 취소할 때 부드럽게 화면 전환이 되도록 하였다.
- 추가로, 타이머가 돌아가는 동안 뽀모도로 이미지가 회전하도록 구현하였다.
import UIKit
import AudioToolbox
enum TimerStatus {
case start
case pause
case end
}
class ViewController: UIViewController {
@IBOutlet weak var timerLabel: UILabel!
@IBOutlet weak var progressView: UIProgressView!
@IBOutlet weak var datePicker: UIDatePicker!
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var toggleButton: UIButton!
var duration = 60
var timerStatus: TimerStatus = .end
var timer: DispatchSourceTimer?
var currentSeconds = 0
override func viewDidLoad() {
super.viewDidLoad()
self.configureToggleButton()
}
func setTimerInfoViewVisible(isHidden: Bool) {
self.timerLabel.isHidden = isHidden
self.progressView.isHidden = isHidden
}
func configureToggleButton() {
self.toggleButton.setTitle("시작", for: .normal)
self.toggleButton.setTitle("일시정지", for: .selected)
}
func startTimer() {
if self.timer == nil {
self.timer = DispatchSource.makeTimerSource(flags: [], queue: .main)
self.timer?.schedule(deadline: .now(), repeating: 1)
self.timer?.setEventHandler(handler: { [weak self] in
guard let self = self else { return }
self.currentSeconds -= 1
let hour = self.currentSeconds / 3600
let minutes = (self.currentSeconds % 3600) / 60
let seconds = (self.currentSeconds % 3600) % 60
self.timerLabel.text = String(format: "%02d:%02d:%02d", hour, minutes, seconds)
self.progressView.progress = Float(self.currentSeconds) / Float(self.duration)
UIView.animate(withDuration: 0.5, delay: 0, animations: {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
})
UIView.animate(withDuration: 0.5, delay: 0.5, animations: {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi * 2)
})
if self.currentSeconds <= 0 {
self.stopTimer()
AudioServicesPlaySystemSound(1005)
}
})
self.timer?.resume()
}
}
func stopTimer() {
if self.timerStatus == .pause {
self.timer?.resume()
}
self.timerStatus = .end
self.cancelButton.isEnabled = false
UIView.animate(withDuration: 0.5, animations: {
self.timerLabel.alpha = 0
self.progressView.alpha = 0
self.datePicker.alpha = 1
self.imageView.transform = .identity
})
self.toggleButton.isSelected = false
self.timer?.cancel()
self.timer = nil
}
@IBAction func tapCancelButton(_ sender: UIButton) {
switch self.timerStatus {
case .start, .pause:
self.stopTimer()
default:
break
}
}
@IBAction func tapToggleButton(_ sender: UIButton) {
self.duration = Int(self.datePicker.countDownDuration)
switch self.timerStatus {
case .end:
self.currentSeconds = self.duration
self.timerStatus = .start
UIView.animate(withDuration: 0.5, animations: {
self.timerLabel.alpha = 1
self.progressView.alpha = 1
self.datePicker.alpha = 0
})
self.toggleButton.isSelected = true
self.cancelButton.isEnabled = true
self.startTimer()
case .start:
self.timerStatus = .pause
self.toggleButton.isSelected = false
self.timer?.suspend()
case .pause:
self.timerStatus = .start
self.toggleButton.isSelected = true
self.timer?.resume()
}
}
}
최종 화면
DatePicker
를 통해 타이머 시간을 설정한다.DispatchSourceTimer
UIViewAnimation
구현
- 타이머를 구현할 때
DispatchSourceTimer
를 사용
- 타이머를 만드는 함수의 인자값 중 queue
부분에 .main
을 선언하여 타이머가 메인 쓰레드에서 돌도록 한다.(UI와 관련된 부분은 모두 메인 쓰레드에서 담당한다고 한다.)
- 타이머가 suspend
인 상태에서 nil
을 입력받게 되면 즉, 일시정지된 상태에서 취소를 누르게 되면 Runtime Error
가 발생하게 된다. 따라서 취소하기 전에 타이머의 상태를 resume
으로 만들어줘야 한다.
UIViewAnimation
을 사용하여 타이머를 시작하거나 취소할 때 부드럽게 화면 전환이 되도록 하였다.
- 추가로, 타이머가 돌아가는 동안 뽀모도로 이미지가 회전하도록 구현하였다.
import UIKit
import AudioToolbox
enum TimerStatus {
case start
case pause
case end
}
class ViewController: UIViewController {
@IBOutlet weak var timerLabel: UILabel!
@IBOutlet weak var progressView: UIProgressView!
@IBOutlet weak var datePicker: UIDatePicker!
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var toggleButton: UIButton!
var duration = 60
var timerStatus: TimerStatus = .end
var timer: DispatchSourceTimer?
var currentSeconds = 0
override func viewDidLoad() {
super.viewDidLoad()
self.configureToggleButton()
}
func setTimerInfoViewVisible(isHidden: Bool) {
self.timerLabel.isHidden = isHidden
self.progressView.isHidden = isHidden
}
func configureToggleButton() {
self.toggleButton.setTitle("시작", for: .normal)
self.toggleButton.setTitle("일시정지", for: .selected)
}
func startTimer() {
if self.timer == nil {
self.timer = DispatchSource.makeTimerSource(flags: [], queue: .main)
self.timer?.schedule(deadline: .now(), repeating: 1)
self.timer?.setEventHandler(handler: { [weak self] in
guard let self = self else { return }
self.currentSeconds -= 1
let hour = self.currentSeconds / 3600
let minutes = (self.currentSeconds % 3600) / 60
let seconds = (self.currentSeconds % 3600) % 60
self.timerLabel.text = String(format: "%02d:%02d:%02d", hour, minutes, seconds)
self.progressView.progress = Float(self.currentSeconds) / Float(self.duration)
UIView.animate(withDuration: 0.5, delay: 0, animations: {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
})
UIView.animate(withDuration: 0.5, delay: 0.5, animations: {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi * 2)
})
if self.currentSeconds <= 0 {
self.stopTimer()
AudioServicesPlaySystemSound(1005)
}
})
self.timer?.resume()
}
}
func stopTimer() {
if self.timerStatus == .pause {
self.timer?.resume()
}
self.timerStatus = .end
self.cancelButton.isEnabled = false
UIView.animate(withDuration: 0.5, animations: {
self.timerLabel.alpha = 0
self.progressView.alpha = 0
self.datePicker.alpha = 1
self.imageView.transform = .identity
})
self.toggleButton.isSelected = false
self.timer?.cancel()
self.timer = nil
}
@IBAction func tapCancelButton(_ sender: UIButton) {
switch self.timerStatus {
case .start, .pause:
self.stopTimer()
default:
break
}
}
@IBAction func tapToggleButton(_ sender: UIButton) {
self.duration = Int(self.datePicker.countDownDuration)
switch self.timerStatus {
case .end:
self.currentSeconds = self.duration
self.timerStatus = .start
UIView.animate(withDuration: 0.5, animations: {
self.timerLabel.alpha = 1
self.progressView.alpha = 1
self.datePicker.alpha = 0
})
self.toggleButton.isSelected = true
self.cancelButton.isEnabled = true
self.startTimer()
case .start:
self.timerStatus = .pause
self.toggleButton.isSelected = false
self.timer?.suspend()
case .pause:
self.timerStatus = .start
self.toggleButton.isSelected = true
self.timer?.resume()
}
}
}
최종 화면
DispatchSourceTimer
를 사용- 타이머를 만드는 함수의 인자값 중
queue
부분에 .main
을 선언하여 타이머가 메인 쓰레드에서 돌도록 한다.(UI와 관련된 부분은 모두 메인 쓰레드에서 담당한다고 한다.)- 타이머가
suspend
인 상태에서 nil
을 입력받게 되면 즉, 일시정지된 상태에서 취소를 누르게 되면 Runtime Error
가 발생하게 된다. 따라서 취소하기 전에 타이머의 상태를 resume
으로 만들어줘야 한다.UIViewAnimation
을 사용하여 타이머를 시작하거나 취소할 때 부드럽게 화면 전환이 되도록 하였다.- 추가로, 타이머가 돌아가는 동안 뽀모도로 이미지가 회전하도록 구현하였다.
import UIKit
import AudioToolbox
enum TimerStatus {
case start
case pause
case end
}
class ViewController: UIViewController {
@IBOutlet weak var timerLabel: UILabel!
@IBOutlet weak var progressView: UIProgressView!
@IBOutlet weak var datePicker: UIDatePicker!
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var toggleButton: UIButton!
var duration = 60
var timerStatus: TimerStatus = .end
var timer: DispatchSourceTimer?
var currentSeconds = 0
override func viewDidLoad() {
super.viewDidLoad()
self.configureToggleButton()
}
func setTimerInfoViewVisible(isHidden: Bool) {
self.timerLabel.isHidden = isHidden
self.progressView.isHidden = isHidden
}
func configureToggleButton() {
self.toggleButton.setTitle("시작", for: .normal)
self.toggleButton.setTitle("일시정지", for: .selected)
}
func startTimer() {
if self.timer == nil {
self.timer = DispatchSource.makeTimerSource(flags: [], queue: .main)
self.timer?.schedule(deadline: .now(), repeating: 1)
self.timer?.setEventHandler(handler: { [weak self] in
guard let self = self else { return }
self.currentSeconds -= 1
let hour = self.currentSeconds / 3600
let minutes = (self.currentSeconds % 3600) / 60
let seconds = (self.currentSeconds % 3600) % 60
self.timerLabel.text = String(format: "%02d:%02d:%02d", hour, minutes, seconds)
self.progressView.progress = Float(self.currentSeconds) / Float(self.duration)
UIView.animate(withDuration: 0.5, delay: 0, animations: {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
})
UIView.animate(withDuration: 0.5, delay: 0.5, animations: {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi * 2)
})
if self.currentSeconds <= 0 {
self.stopTimer()
AudioServicesPlaySystemSound(1005)
}
})
self.timer?.resume()
}
}
func stopTimer() {
if self.timerStatus == .pause {
self.timer?.resume()
}
self.timerStatus = .end
self.cancelButton.isEnabled = false
UIView.animate(withDuration: 0.5, animations: {
self.timerLabel.alpha = 0
self.progressView.alpha = 0
self.datePicker.alpha = 1
self.imageView.transform = .identity
})
self.toggleButton.isSelected = false
self.timer?.cancel()
self.timer = nil
}
@IBAction func tapCancelButton(_ sender: UIButton) {
switch self.timerStatus {
case .start, .pause:
self.stopTimer()
default:
break
}
}
@IBAction func tapToggleButton(_ sender: UIButton) {
self.duration = Int(self.datePicker.countDownDuration)
switch self.timerStatus {
case .end:
self.currentSeconds = self.duration
self.timerStatus = .start
UIView.animate(withDuration: 0.5, animations: {
self.timerLabel.alpha = 1
self.progressView.alpha = 1
self.datePicker.alpha = 0
})
self.toggleButton.isSelected = true
self.cancelButton.isEnabled = true
self.startTimer()
case .start:
self.timerStatus = .pause
self.toggleButton.isSelected = false
self.timer?.suspend()
case .pause:
self.timerStatus = .start
self.toggleButton.isSelected = true
self.timer?.resume()
}
}
}
GitHub
https://github.com/pjs0418/PomodoroTimer
출처
패스트캠퍼스, 초격차 패키지 : 30개 프로젝트로 배우는 iOS 앱 개발 with Swift
Author And Source
이 문제에 관하여((iOS)뽀모도로 타이머 앱 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@tony1803/iOS-뽀모도로-타이머-앱-만들기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)