티스토리 뷰

오늘은 유데미를 통해 배운 에그 타이머 예제를 통해 타이머를 알아보고자 합니다!!

이 에그 타이머가 꽤 마음에 들어서 제 버전으로 만들어 보았어요.

(달걀이 너무 귀여워,,,🥚)

 

 

 


1. 에그 타이머 UI 잡기

언제나 제일 기본적인 부분이져. UI잡기부터 해볼게요.

코드는 접어둘게요!

더보기
더보기
import UIKit

class ViewController: UIViewController {

    let mainLbl = UILabel()
    let background = UIImageView()
    
    let softBtn = UIButton()
    let softLbl = UILabel()
    let mediumBtn = UIButton()
    let mediumLbl = UILabel()
    let hardBtn = UIButton()
    let hardLbl = UILabel()
    let progressBar = UIProgressView()
    
    let pointColor = UIColor(red: 81.0 / 255.0, green: 215.0 / 255.0, blue: 202.0 / 255.0, alpha: 1.0)
    let pointColor2 = UIColor(red: 95.0 / 255.0, green: 87.0 / 255.0, blue: 215.0 / 255.0, alpha: 1.0)
    
   
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }
}


//MARK: -UI
extension ViewController {
    final private func configureUI() {
        setAttributes()
        addTarget()
        setConstraints()
    }
    
    final private func setAttributes() {
        mainLbl.text = "달걀🥚삶기"
        mainLbl.textAlignment = .center
        mainLbl.font = UIFont.boldSystemFont(ofSize: 40)
        mainLbl.textColor = pointColor2
        
        background.image = UIImage(named: "cool-background")
        
        
        softBtn.setImage(UIImage(named: "soft_egg"), for: .normal)
        softLbl.text = "반숙"
        
        mediumBtn.setImage(UIImage(named: "medium_egg"), for: .normal)
        mediumLbl.text = "반+완숙"
        
        hardBtn.setImage(UIImage(named: "hard_egg"), for: .normal)
        hardLbl.text = "완숙"
        
        [softBtn, mediumBtn, hardBtn].forEach {
            $0.contentMode = .scaleAspectFit
        }
        
        [softLbl, mediumLbl, hardLbl].forEach {
            $0.textAlignment = .center
            $0.textColor = pointColor
            $0.font = UIFont.boldSystemFont(ofSize: 25)
        }
        progressBar.backgroundColor = .white
        progressBar.progress = 0.5
        progressBar.tintColor = pointColor2
    }
    
    final private func addTarget() {
        softBtn.addTarget(self, action: #selector(BtnTapped(_:)), for: .touchUpInside)
        mediumBtn.addTarget(self, action: #selector(BtnTapped(_:)), for: .touchUpInside)
        hardBtn.addTarget(self, action: #selector(BtnTapped(_:)), for: .touchUpInside)
    }
    
    final private func setConstraints() {
        let softStack = UIStackView(arrangedSubviews: [softLbl,softBtn])
        softStack.axis = .vertical
        softStack.spacing = 10
        
        let mediumStack = UIStackView(arrangedSubviews: [mediumLbl,mediumBtn])
        mediumStack.axis = .vertical
        mediumStack.spacing = 10
        
        let hardStack = UIStackView(arrangedSubviews: [hardLbl,hardBtn])
        hardStack.axis = .vertical
        hardStack.spacing = 10
        
        let stackView = UIStackView(arrangedSubviews: [softStack, mediumStack, hardStack])
        stackView.axis = .horizontal
        stackView.distribution = .fillEqually
        stackView.spacing = 20


        [background, mainLbl, stackView, progressBar].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        NSLayoutConstraint.activate([
            progressBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -200),
            progressBar.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
            progressBar.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
            //progressBar.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
            
            background.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            background.topAnchor.constraint(equalTo: view.topAnchor),
            background.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            background.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            
            mediumLbl.heightAnchor.constraint(equalTo: softLbl.heightAnchor),
            hardLbl.heightAnchor.constraint(equalTo: softLbl.heightAnchor),

            
            stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
            stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 240),
            stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
            stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -300),
            
            
            mainLbl.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
            mainLbl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 120),
            
            
        ])
    }
}

각 달걀 상태별로 레이블과 달걀버튼을 스택으로 묶은 후, 그 3개(반숙 + 반완숙 + 완숙)를 다시 스택뷰로 묶었어요!

아! 그리고 저는 색깔을 넣고 싶어서 포인트 컬러 2개도 설정했어요!


타이머 만들기

 

우선 타이머와 여러가지 기본 설정이 필요해요.

viewDidLoad 위에 아래 5가지 변수 또는 상수를 만들어줍니다요

var timer = Timer() // 타이머
var totalTime = 0 // 달걀 삶는 전체 시간
var timePassed = 0 // 지나간 시간
let timeToBoil = ["반숙":5, "반+완숙":8, "완숙":10] // 달걀별 삶는 시간

 

원래 반숙은 7분인가 8분인가 해야하는데, 그걸 기다릴 순 없으니, 간단하게 5초, 8초, 10초로 설정할게요!


버튼(달걀그림) 터치 시 이벤트를 extension을 통해 따로 빼줘볼게요!

어차피 달걀마다 발생할 이벤트가 동일하기 때문에 이벤트 메서드는 하나만 만들어주고, 버튼에 addTarget할 때 모두 이 메서드를 액션으로 주려구요.

//MARK: -Event
extension ViewController {
    @objc func BtnTapped(_ sender: UIButton) {
	// 달걀그림(버튼) 터치 시 발생할 이벤트
        progressBar.progress = 0.0
}

1. progressBar

progressBar는 UI를 설정할 때 thumb가 중간에 오도록 설정했었는데, 버튼을 누를 때는 맨 처음으로 오는 게 맞으니까 0.0으로 다시 설정해주었어요.

 

 

2. 버튼 이벤트 설정

버튼의 sender값은 총 세개 softBtn(반숙), mediumBtn(반+완숙), hardBtn(완숙)여서 switch 문을 활용했어요.

각 버튼의 값을 위에서 선언한 timeToBoil 딕셔너리에서 찾아서 그 value값을 hardness에 넣어준 후, 다시 그 값을 totalTime에 넣어줄게요!

 

 

예를 들어,

softBtn(반숙) 버튼을 누르면 딕셔너리에서 "반숙"의 value값인 5를 찾아 hardness에 넣겠져?

그리고 그 값을 다시 totalTime에 할당해주는 걸로!

//MARK: -Event
extension ViewController {
    @objc func BtnTapped(_ sender: UIButton) {
        switch sender {
        case softBtn:
            guard let hardness = timeToBoil["반숙"] else { return }
            totalTime = hardness
        case mediumBtn:
            guard let hardness = timeToBoil["반+완숙"] else { return }
            totalTime = hardness
        case hardBtn:
            guard let hardness = timeToBoil["완숙"] else { return }
            totalTime = hardness
        default:
            break
        }
}

 

3. Timer

그리고 아까 찍어낸 timer 객체에 Timer 설정을 해줄게요!

저는 아래 타입메서드를 사용할건데요. 타입메서드니까, 앞에 타입(Timer)까지 명시해주는 거 잊지말기

 

 

 

이 타입 메서드는 5개의 파라미터를 가지는 놈인데,

-ti : 타이머의 간격

-target : 타이머가 실행될 때 aSelector에서 지정한 메시지를 보낼 대상이래요. 바로 자기 자신(self)이져

-aSelector : 타이머가 작동하면 타겟에서 보낼 메세지래요. 이건 밑에서 메서드를 만들거에요.

-userinfo : 타이머의 사용자 정보라는데, nil일거래요. (뭔 말?)

-repeats : 타이머의 반복 여부. true면 타이머가 무효화(invalidate)될 때까지 타이머가 반복돼요.

 

파리미터를 설정해주면? 아래와 같습니다.

//MARK: -Event
extension ViewController {
    @objc func BtnTapped(_ sender: UIButton) {
        switch sender {
        case softBtn:
            guard let hardness = timeToBoil["반숙"] else { return }
            totalTime = hardness
        case mediumBtn:
            guard let hardness = timeToBoil["반+완숙"] else { return }
            totalTime = hardness
        case hardBtn:
            guard let hardness = timeToBoil["완숙"] else { return }
            totalTime = hardness
        default:
            break
        }
        
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)  
    }
}

 


이제 타이머의 파라미터 중 하나였던 selector 파라미터에 보낼 메시지를 만들어볼게요.

버튼 이벤트 밑에 만들어주고, selctor로 파라미터로 줄게요!

//MARK: -Event
extension ViewController {
    @objc func BtnTapped(_ sender: UIButton) {
        //생략
    }
    @objc func updateTime() {
    	//타이머 작동 시 발생될 일
        if timePassed < totalTime {
            timePassed += 1
            let percentage = Float(timePassed) / Float(totalTime)
            progressBar.setProgress(percentage, animated: true)
            print(timePassed)
        } else {
            timePassed = 0
            mainLbl.text = "다 됐다!"
        }        
    }
}

softBtn(반숙)을 터치했다고 상상해볼게요.

BtnTapped(_:)의 switch문을 통해 totalTime에 5라는 값이 들어왔져?

totalTime = 5 / timePassed = 0

 

 

이게 이제 if문으로 들어갑니다

if 0(timePased) < 5(totalTime)

-> 참

    -> timePassed + 1 과 progressview에 (지나간시간/전체시간)을 %를 부여해줄게요.

-> 거짓 (timePassed가 5보다 작지 않아지는 순간)

    -> 다시 timePassed = 0 설정

    -> 달걀🥚삶기 텍스트를 "다됐다!"로 변경

;;또 왜저래;;ㅎ;; 제법 엉망이네요;;ㅎㅎ

바로 타이머를 안 멈춰줘서 그런건데요. (안 멈추면 저렇게 빨라지더라구여,,,무서워,,)

두 시점에서 멈춰줘야 해요.

1) 달걀 버튼을 눌렀을 때

2) 정해진 시간이 다 지났을 때 (if문의 else문에서)

 

 

+ 다른 달걀을 눌렀을 때, 메인레이블이 계속 "다됐다!" 상태니까 매우 이상해요. 버튼 이벤트에 다시 메인레이블을 "달걀🥚삶기"로 설정해볼게요.

(바뀐 부분은 ⭐️표시)

extension ViewController {
	
    @objc func BtnTapped(_ sender: UIButton) {
        timer.invalidate() //⭐️ 타이머야 멈춰줘
        progressBar.progress = 0.0
        mainLbl.text = "달걀🥚삶기" //⭐️ 메인레이블 재설정
        
        switch sender {
        case softBtn:
            guard let hardness = timeToBoil["반숙"] else { return }
            totalTime = hardness
        case mediumBtn:
            guard let hardness = timeToBoil["반+완숙"] else { return }
            totalTime = hardness
        case hardBtn:
            guard let hardness = timeToBoil["완숙"] else { return }
            totalTime = hardness
        default:
            break
        }
        
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
    }
    
    @objc func updateTime() {
        if timePassed < totalTime {
            timePassed += 1
            let percentage = Float(timePassed) / Float(totalTime)
            progressBar.setProgress(percentage, animated: true)
            print(timePassed)
        } else {
            timer.invalidate() //⭐️ 타이머야 멈춰줘
            timePassed = 0
            mainLbl.text = "다 됐다!"
        }
    }
}

 

 

timePassed를 프린트해서 앱실행과 같이 볼게요!

(시간관계상 반숙과 반+완숙만!)


이렇게 에그 타이머는 완료!!!되었습니다!! 

다음 번에는 시간이 다되면, 음악이 울리도록 해보께요!! 

퐈이링!!

 

 

틀린 부분은 언제나 말씀주세여 슨배님덜!!

감사합니다!!!

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크