[iOS] 알람 앱 (1) - TabBarItem, NavigationBarItem 이미지 추가하기
오늘은 새로운 프로젝트 발제날! 프로젝트의 주제는 알람, 타이머, 스탑워치 기능이 있는 알람앱이다.주제들이 다 어려울 것 같아서 고민하다가 팀원들과 즐겁게 할 수 있을 것 같은 알람 앱으
yujjne.tistory.com
오늘은 어제 구성한 스톱워치 화면에 기능 구현을 시작했다.
Stopwatch 클래스 (Timer)
우선 스탑워치 기능 구현을 위한 Stopwatch 클래스를 작성했다.
import Foundation
class Stopwatch: NSObject {
var counter: Double
var timer: Timer
override init() {
counter = 0.0
timer = Timer()
}
}
Stopwatch 클래스는 타이머를 사용하여 시간을 계산하고 추적하는데 사용되는 클래스이다.
- counter: 시간을 저장하는 변수 (이 변수는 타이머가 시작되면 시간이 증가하고, 타이머가 정지되면 시간이 멈춤)
- timer: 시간을 측정하는 타이머 객체 → 타이머는 실행 루프와 함께 작동한다.
timer는 일정 주기마다 반복되는 실행 루프이고 counter가 증가되어 분, 초 등 단위로 표출되는 것이라고 한다.
아래는 타이머 관련 공식 문서이다! 어려워서 정확히 이해하지는 못했지만..🫠
Timer | Apple Developer Documentation
A timer that fires after a certain time interval has elapsed, sending a specified message to a target object.
developer.apple.com
Lap 버튼, Start 버튼의 프로세스
우선 시간 흐름과 버튼 프로세스를 생각해보자. 위 화면과 같이 버튼 상태가 변화되어야 한다.
1. 초기 상태 : Start 버튼만 활성화
2. Start 버튼을 누르면 시간이 흘러야 함 & Lap 버튼 활성화, Start 버튼은 Stop으로 변경
3. Lap 버튼을 누르면 Lap 시간이 저장되어야 함 (버튼 변화 X)
4. Stop 버튼을 누르면 시간이 멈춰야 함 & Lap 버튼은 Reset 으로 변경, Stop 버튼은 Start 버튼으로 변경
5. Reset 버튼을 누르면 라벨과 Lap 시간 지워지며 초기 상태로 돌아옴
위와 같은 버튼 프로세스에 집중해서 시간 흐름과 버튼 상태 변화 기능을 구현했다. Lap 버튼 클릭 프로세스는 다음번에 구현할 예정이다!
우선 기능 구현에 필요한 프로퍼티를 생성했다.
위에서 작성한 Stopwatch 인스턴스와 시간이 흐르는지 상태 여부를 담고 있는 isPlay 변수이다. 또한 lapTableViewData는 아직 구현하지 않았지만 Lap 기록을 담아둘 배열이다.
// MARK: - 프로퍼티
private let mainStopwatch: Stopwatch = Stopwatch()
private var isPlay: Bool = false
private var lapTableViewData: [String] = []
초기에 뷰를 불러올 때 Lap버튼이 활성화 되지 않아야 하기 때문에 isEnabled(활성화) 여부를 false로 설정했다.
ViewDidLoad에 다음 코드를 추가해줘야한다.
lapButton.isEnabled = false
이제 Lap 버튼과 Start 버튼의 상태 변화에 따른 코드를 작성해야 한다.
버튼의 이벤트를 처리하는 메서드 안에 addTarget을 통해 원하는 동작이 실행되도록 설정했다.
// MARK: - 버튼 이벤트 처리
private func setupButtons() {
lapButton.addTarget(self, action: #selector(lapButtonPressed), for: .touchUpInside)
startButton.addTarget(self, action: #selector(startButtonPressed), for: .touchUpInside)
}
우선 Lap 버튼의 액션부터 작성해보자!
시간이 멈춰있을 땐 Lap버튼의 text가 Reset으로 설정되어 있을 것이다. 이 Reset 버튼을 눌렀을 때는 시간이 초기화가 되고 버튼의 상태도 초기 상태로 돌아와야 한다. (위 프로세스 5번 상황)
또한 시간이 흐를 때 Lap버튼을 누르면 버튼의 상태변화는 일어나지 않고 Lap 시간이 저장되어야 한다. (위 프로세스 3번 상황)
@objc private func lapButtonPressed() {
// 시간이 멈춰있을 때 -> 버튼 누르면 reset 되어야 함
if !isPlay {
resetMainTimer()
lapButton.isEnabled = false
lapButton.setTitle("Lap", for: .normal)
lapButton.setTitleColor(UIColor.gray, for: .normal)
lapButton.layer.borderColor = UIColor.gray.cgColor
}
// 시간이 가고 있을 때 -> 테이블 뷰 셀의 데이터를 추가
else {
let timerLabelText = "\(minutesLabel.text ?? "00"):\(secondsLabel.text ?? "00"):\(milliSecondsLabel.text ?? "00")"
lapTableViewData.append(timerLabelText)
}
tableView.reloadData()
}
상태에 따라 해당 버튼의 색상 및 타이틀, border 색상이 바뀌도록 구현했는데 이 부분은 각자 UI에 맞게 조절하면 될 것이다.
또한 초기 상태로 돌아올 땐 lap 버튼을 비활성화 시켜주고, 버튼을 눌렀을 땐 lap 타임이 업데이트 되므로 테이블뷰를 리로드해줘야 한다.
위에서 사용한 액션 함수들을 아래와 같이 Extension으로 분리해서 만들었다.
라벨과 Lap 시간들을 초기화하는 reset 메서드이다. 여기서 invalidate()은 타이머를 중지시키는 메서드이다!
// MARK: - Action Functions
extension StopwatchViewController {
func resetTimer(_ stopwatch: Stopwatch, labels: [UILabel]) {
stopwatch.timer.invalidate()
stopwatch.counter = 0.0
for label in labels {
label.text = "00"
}
}
func resetMainTimer() {
resetTimer(mainStopwatch, labels: [minutesLabel, secondsLabel, milliSecondsLabel])
lapTableViewData.removeAll()
tableView.reloadData()
}
}
이제 시간이 흐르는 Start 버튼의 액션을 작성해보자.
시간이 멈춰있는 상태에서 Start 버튼을 누르면 시간이 흘러야 한다.(위 프로세스 2번 상황)
isPlay의 상태를 true로 바꿔주고 버튼의 상태가 Stop으로 바뀌게 했고 시간이 흐르도록 구현했다.
또한 시간이 흐를 때는 Stop으로 변경된 버튼을 누르면 시간을 멈추고 해당 상태에 맞도록 버튼들의 상태를 조절했다.(위 프로세스 4번 상황)
@objc private func startButtonPressed() {
lapButton.isEnabled = true
lapButton.setTitle("Lap", for: .normal)
lapButton.setTitleColor(UIColor(named: "mainTextColor"), for: .normal)
lapButton.layer.borderColor = UIColor(named: "mainTextColor")?.cgColor
// 시간이 멈춰있을 때 -> 버튼 누르면 시간이 흘러야 함
if !isPlay {
unowned let weakSelf = self
mainStopwatch.timer = Timer.scheduledTimer(timeInterval: 0.01, target: weakSelf, selector: Selector.updateMainTimer, userInfo: nil, repeats: true)
isPlay = true
startButton.setTitle("Stop", for: .normal)
startButton.setTitleColor(UIColor.red, for: .normal)
startButton.layer.borderColor = UIColor.red.cgColor
}
// 시간이 흐를 때 -> 버튼 누르면 멈춰야 함
else {
mainStopwatch.timer.invalidate()
lapStopwatch.timer.invalidate()
isPlay = false
startButton.setTitle("Start", for: .normal)
startButton.setTitleColor(UIColor(named: "mainActiveColor"), for: .normal)
startButton.layer.borderColor = UIColor(named: "mainActiveColor")?.cgColor
lapButton.setTitle("Reset", for: .normal)
lapButton.setTitleColor(UIColor(named: "mainTextColor"), for: .normal)
lapButton.layer.borderColor = UIColor(named: "mainTextColor")?.cgColor
}
}
시간을 움직이게 만드는 코드를 자세히 확인해보자.
아래 Timer.scheduledTimer라는 메서드를 이용해서 타이머를 움직일 것이다.
파라미터의 내용을 하나씩 보면, timeInterval은 타이머가 실행되는 시간 간격(초 단위로 지정)이다. 소수점 두째 자릿수까지 보여주는 스톱워치를 만들 것이므로 최소 단위 0.01을 기준으로 잡는다!
target은 타이머가 실행될 때, Selector에서 지정한 메시지를 보낼 대상 개체를 의미한다. 타이머가 invalidate(정지)될 때까지 강력한 참조를 유지한다고 나와있다.
selector는 타이머가 실행될 때, 호출한 메서드를 선택하고 updateMainTimer라는 메서드를 가리킨다.
그 외에 사용자 정보를 의미하는 userInfo와 타이머의 반복을 정할 repeats까지 매개변수로 나타나있다. Stop 이후에도 계속 작동해야하니 true로 설정한다.
따라서 주어진 매개변수로 Timer.scheduledTimer 메서드가 호출되면, 0.01초마다 weakSelf 객체의 updateMainTimer 메서드가 호출되는 타이머가 생성된다.⭐️⭐️
위에서 사용한 타이머를 업데이트하는 메서드도 익스텐션에 추가했다.
scheduledTimer의 매개변수로 들어가야 하는 함수이기 때문에 앞에 @objc를 붙여서 정의했다.
또한 stopwatch의 counter 값을 0.01초씩 증가시키고 각 분, 초, 밀리초 값을 계산하여 레이블에 표시하도록 작성했다.
(밀리초는 0.01초마다 증가, 초 단위로 저장, 분은 60초가 지날 때마다 1씩 증가하도록 계산)
// MARK: - Action Functions
extension StopwatchViewController {
@objc func updateMainTimer() {
updateTimer(mainStopwatch, labels: [minutesLabel, secondsLabel, milliSecondsLabel])
}
func updateTimer(_ stopwatch: Stopwatch, labels: [UILabel]) {
stopwatch.counter += 0.01
let minutes = Int(stopwatch.counter / 60)
let seconds = Int(stopwatch.counter.truncatingRemainder(dividingBy: 60))
let milliseconds = Int((stopwatch.counter * 100).truncatingRemainder(dividingBy: 100))
labels[0].text = String(format: "%02d", minutes) // 분
labels[1].text = String(format: "%02d", seconds) // 초
labels[2].text = String(format: "%02d", milliseconds) // 밀리초
}
}
fileprivate extension Selector {
static let updateMainTimer = #selector(StopwatchViewController.updateMainTimer)
}
결과 화면은 다음과 같다!
아래 테이블뷰에는 임시 값을 넣어뒀는데
다음엔 Lap 버튼 클릭에 따른 테이블 뷰의 셀이 추가되도록 구현해야겠다!😎
▼ Reference🔗
Swift-30-Projects/Project 02 - Stopwatch at master · soapyigu/Swift-30-Projects
30 mini Swift Apps for self-study. Contribute to soapyigu/Swift-30-Projects development by creating an account on GitHub.
github.com
'TIL✏️' 카테고리의 다른 글
[iOS] 알람 앱 (4) - UIEditMenuInteraction과 UIPasteboard (3) | 2024.05.16 |
---|---|
[iOS] 알람 앱 (3) - 스톱워치 테이블뷰에 랩 타임 추가하기 (3) | 2024.05.16 |
[iOS] 알람 앱 (1) - TabBarItem, NavigationBarItem 이미지 추가하기 (10) | 2024.05.13 |
[iOS] 책 검색 & 저장 App (1) - Kakao REST API 사용해서 책 검색하기 (4) | 2024.05.07 |
[iOS] UICollectionView: Header(헤더) 사용하기 (1) | 2024.04.23 |