이번 출시 프로젝트를 하면서 다른분들이 만들어낸 결과물들을 보던 중 어떤 특정 조건에 대해서 버튼을 비활성화 하는 기능을 많이 봤습니다.
💡사용자 측면에서 정말 좋은 기능이라고 생각되어서 해당 기능이 포함된 간단한 토이 프로젝트를 진행해보려고 합니다.
패스트 캠퍼스 강의를 기반으로 작성했습니다.
전체적인 기능을 먼저 살펴보면 처음에는 Date Picker
와 버튼을 통해서 타이머를 설정하고(취소버튼 비활성화)
, 타이머가 시작되면 Date Picker
가 사라지고 남은시간에 대한 Label, ProgressBar
가 보입니다.
사용자가 타이머 시간을 설정하기 전에는 취소
버튼이 비활성화 되어 있는 것을 확인 할 수 있습니다.
어떻게 버튼을 비활성화 시킬 수 있을까요?
😿.isEnabled = false
한줄이면 됩니다. 각 상황에 맞는 분기처리만 잘해주면 됩니다.
/*
다른 뷰 객체들이 사라지고 생기는건 isHidden 을 이용하면 됩니다.
스무스하게 사라지고 생기는 애니메이션 효과를 적용하고 싶다면 각 객체들의 alpha 값을 일정시간에 거쳐서 변화하도록 설정하면 됩니다.
*/
UIView.animate(withDuration: 0.5) {
self.timerLabel.alpha = 1
self.progressView.alpha = 1
self.datePicker.alpha = 0
}
DispatchSourceTimer
를 이용합니다. Dispatch(?) 익숙한 이름입니다..
동기, 비동기
에 대해 학습할 때 나왔던 DispatchQueue
입니다.
이번기회에 제대로 정리해보겠습니다. (언제나 정리당하는건 나였다..)
func startTimer() {
if self.timer == nil {
self.timer = DispatchSource.makeTimerSource(flags: [], queue: .main)
//1초 간격으로 아래의 event 가 발생한다고 생각
self.timer?.schedule(deadline: .now(), repeating: 1)
self.timer?.setEventHandler(handler: { [weak self] in
//여기에 1초 간격으로 실행시킬 이벤트 작성
self.currentSeconds -= 1
})
self.timer?.resume()
}
}
timer 가 nil 인 경우에는, event 에 초단위로 실행 시킬 이벤트를 작성하고 타이머를 사용할 것임을 명시해줍니다.
switch timerStatus {
//타이머를 일시정지를 눌렀을 경우
case .start:
self.timerStatus = .pause
self.startButton.isSelected = false
self.timer?.suspend()
//일시정지된 타이머를 다시 시작했을 경우
case .pause:
self.timerStatus = .start
self.startButton.isSelected = true
self.timer?.resume()
//초기 상태에서 시작 버튼을 누른 경우
case .end:
self.currentSeconds = self.duration
self.timerStatus = .start
UIView.animate(withDuration: 0.5) {
self.timerLabel.alpha = 1
self.progressView.alpha = 1
self.datePicker.alpha = 0
}
self.startButton.isSelected = true
self.cancelButton.isEnabled = true
startTimer()
}
그리고, 타이머가 정지상태에서 설정한 시간과 함께 시작을 누르면 해당 시간을 (duration)
을 초단위로 감소할 변수 currentSeconds
에 넣어줍니다. 그리고 앞선 startTimer 함수를 호출해서 타이머를 시작시킵니다.
일시정지를 하는 경우 timer.suspend()
를 통해서 초단위로 발생하는 event 를 정지시킵니다.
그리고 다시 타이머를 동작 시킬때는 timer.resume()
을 호출합니다.
타이머를 종료하는 경우에는 timer
를 어떻게 설정해줘야 할까요?
func stopTimer() {
//suspend 상태의 timer에 바로 Nil 값을 넣으면 런타임 에러발생
//다시 시작시켜놓고 stop 시켜야 한다.
if self.timerStatus == .pause {
self.timer?.resume()
}
self.timerStatus = .end
self.timer?.cancel()
self.timer = nil //nil 로 메모리 해제 필요!
}
timer.cancle()
로 현재 이벤트를 취소하고 nil 값으로 메모리 해제과정이 필요합니다.
❗️메모리 해제를 하지 않은경우, 화면을 벗어나도 timer가 계속해서 동작합니다
❗️timer 가 suspend 상태일때, 바로 cancel을 하게 되면 오류가 발생합니다!
타이머가 동작할 때, UIImageView 가 빙글빙글 돌고 Progress Bar 가 시간에 맞게 감소하고 있습니다.
self.progressView.progress = Float(self.currentSeconds) / Float(self.duration)
//1초 동안 어떻게 움직이게 할것인가
UIView.animate(withDuration: 0.5, delay: 0) {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
}
UIView.animate(withDuration: 0.5, delay: 0.5) {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi * 2)
}
그럼 앞선 startTimer()
함수에 1초 마다 발생하는 이벤트에 위의 내용을 추가합니다.
1초 간격으로 동작하기 때문에 0.5초 간격으로 0~180, 180~360 도 회전하도록 설정합니다.
전체적인 코드는 깃허브에서 확인할 수 있습니다.
사실 DispatchSourceTimer
를 사용하면서 지금까지 미루고 미뤘던 Dispatch Queue 와 동기/비동기에 대해서 확실하게 정리하려 했으나 실패했습니다...어렵네요ㅠㅠ
그래서 블로그 다른 게시글에 계속 공부하면서 시리즈로 쓰면서 완벽하게 이해할 수 있도록 해보려고 합니다.