본 포스팅은 다음 버전을 기준으로 작성되었습니다.

  • Swift 5.3
  • iOS 14.1

Intro

오늘은 DatePicker 에서 제공하는 Mode 중 하나인 .countDownTimer 를 사용해 실제로 구동 가능한 타이머를 만들어보겠습니다.


Prerequisite

타이머 구현 및 실습을 위해 DatePicker 를 배치하고, 상단에는 타이머의 남은 시간을 표시UILabel 을, 하단에는 사용자가 설정한 시간을 기준으로 타이머 구동을 시작UIButton 을 배치했습니다.

import UIKit

class CountDownViewController: UIViewController {

    let label = UILabel()
    let picker = UIDatePicker()
    let button = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    // MARK: - UI
    private func configureUI() {
        setAttributes()
        setContraints()
    }

    private func setAttributes() {
        label.text = "0"
        label.textColor = .black
        label.font = UIFont.systemFont(ofSize: 30)

        picker.datePickerMode = .countDownTimer

        button.setTitle("Start", for: .normal)
        button.addTarget(self, action: #selector(handleButton(_:)), for: .touchUpInside)
    }

    private func setContraints() {
        [label, picker, button].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }

        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.bottomAnchor.constraint(equalTo: picker.topAnchor, constant: -100),

            picker.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            picker.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
            picker.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),

            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.topAnchor.constraint(equalTo: picker.bottomAnchor, constant: 150)
        ])
    }

    // MARK: - Selectors
    @objc
    private func handleButton(_ sender: UIButton) {
        print(picker.countDownDuration)
        print(#function)
    }
}

이제 Simulator 를 실행하고 버튼을 눌러보면 Console 에 사용자가 선택한 시간이 출력되는 것을 확인할 수 있어요.


Analyzing Methods

이제 DatePicker 의 설정을 실습에 맞게 바꿔주고, Timer 기능을 수행할 코드를 작성해보도록 할게요.

CountDown Duration 조절하기

이 옵션은 datePicker.countdownTimer 로 설정되어 있을 때만 사용할 수 있고, 최초에 설정되어 있는 값을 조절할 수 있습니다. 초 단위 설정이 가능하기는 하지만 화면에 표시될 때는 분 단위로 표시되기 때문에 60의 배수로 설정해주세요.

picker.countDownDuration = 600

앱을 실행하자마자 기본 시간이 10분으로 설정되어 있습니다.


Minute 선택 간격 조절하기

사용자가 datePicker 를 스크롤하며 선택할 수 있는 옵션을 제어합니다.

picker.minuteInterval = 5

이 옵션을 적용하고 실행해보면 타이머가 5분 간격으로 선택되는 것을 확인할 수 있을거에요.


Timer 기능 구현하기

DatePicker 자체는 타이머 기능을 수행할 수 있는 어떤 옵션도 없습니다. 단지 인터페이스의 역할만 수행할 뿐이에요.

그래서 우리가 Timer 인스턴스를 직접 생성하고 남은 시간을 관리해야 타이머 기능을 구현할 수 있습니다. 버튼이 눌렸을 때 사용자가 설정한 시간을 입력받아 해당 값을 기준으로 타이머 기능을 만들어주도록 하겠습니다.

먼저 타이머가 0초가 되었을 때 알람을 실행하도록 만들어볼게요. AudioServicesPlaySystemSound 를 사용해 기본 사운드를 재생하고 이 method 를 사용하기 위해 AVFoundationimport 합니다.

import AVFoundation

사용자가 설정한 값을 입력받을 변수가 하나 필요합니다. ViewController 상단에 변수를 하나 생성하도록 할게요.

var duration: Int = 0

이제 버튼과 연결해 놓은 함수에 타이머와 관련된 코드를 작성하겠습니다.

@objc
private func handleButton(_ sender: UIButton) {
    duration = Int(picker.countDownDuration)
    label.text = "\(duration)"

    Timer.scheduledTimer( withTimeInterval: 1, repeats: true) {
        (timer) in
        self.duration -= 1
        self.label.text = "\(self.duration)"

        if self.duration == 0 {
            timer.invalidate()
            AudioServicesPlaySystemSound(1315)
        }
    }
}

Timer 는 반드시 직접 종료해주어야 하는 것을 잊지마세요. 해제는 .invalidate method 를 사용해 구현할 수 있습니다.


Wrap Up

이렇게 DatePicker.countDownTimer 모드로 사용할 수 있는 방법과, Timer 인스턴스와 연계해 실제로 타이머 기능을 구현할 수 있는 방법에 대해 공부해보았습니다. 이제 DatePicker 의 주요 기능들에 대해 충분히 공부했으니 필요한 프로젝트에 적용해 보세요!


Entire Code

import UIKit
import AVFoundation

class CountDownViewController: UIViewController {

    let label = UILabel()
    let picker = UIDatePicker()
    let button = UIButton(type: .system)
    var duration: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    // MARK: - UI
    private func configureUI() {
        setAttributes()
        setContraints()
    }

    private func setAttributes() {
        label.text = "0"
        label.textColor = .black
        label.font = UIFont.systemFont(ofSize: 30)

        picker.datePickerMode = .countDownTimer
        picker.countDownDuration = 60
        picker.minuteInterval = 1

        button.setTitle("Start", for: .normal)
        button.addTarget(self, action: #selector(handleButton(_:)), for: .touchUpInside)
    }

    private func setContraints() {
        [label, picker, button].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }

        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.bottomAnchor.constraint(equalTo: picker.topAnchor, constant: -100),

            picker.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            picker.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
            picker.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),

            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.topAnchor.constraint(equalTo: picker.bottomAnchor, constant: 150)
        ])
    }

    // MARK: - Selectors
    @objc
    private func handleButton(_ sender: UIButton) {
        duration = Int(picker.countDownDuration)
        label.text = "\(duration)"

        Timer.scheduledTimer( withTimeInterval: 1, repeats: true) {
            (timer) in
            self.duration -= 1
            self.label.text = "\(self.duration)"

            if self.duration == 0 {
                timer.invalidate()
                AudioServicesPlaySystemSound(1315)
            }
        }
    }
}