[iOS] 타이머 만들기

Sangwon Shin·2021년 12월 3일
0

iOS

목록 보기
2/9

🤷‍♂️ 갑자기 왠 타이머?

이번 출시 프로젝트를 하면서 다른분들이 만들어낸 결과물들을 보던 중 어떤 특정 조건에 대해서 버튼을 비활성화 하는 기능을 많이 봤습니다.

💡사용자 측면에서 정말 좋은 기능이라고 생각되어서 해당 기능이 포함된 간단한 토이 프로젝트를 진행해보려고 합니다.

패스트 캠퍼스 강의를 기반으로 작성했습니다.


📃 결과물

전체적인 기능을 먼저 살펴보면 처음에는 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을 하게 되면 오류가 발생합니다!


👻 Animation 효과

타이머가 동작할 때, 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 도 회전하도록 설정합니다.


🏷 P.S.

전체적인 코드는 깃허브에서 확인할 수 있습니다.

사실 DispatchSourceTimer 를 사용하면서 지금까지 미루고 미뤘던 Dispatch Queue 와 동기/비동기에 대해서 확실하게 정리하려 했으나 실패했습니다...어렵네요ㅠㅠ

그래서 블로그 다른 게시글에 계속 공부하면서 시리즈로 쓰면서 완벽하게 이해할 수 있도록 해보려고 합니다.

profile
개발자가 되고싶어요

0개의 댓글