강의/앨런 Swift 앱 만들기

앨런 Swift 앱 만들기 UIKit | 타이머 만들기(+ UISlider)

ziziDev 2024. 6. 12. 22:47
반응형

안녕하세요

오늘은 제가 강의를 보며 배웠던걸 정리하고자 합니다

 

 

 

앨런 iOS 앱 개발 (15개의 앱을 만들면서 근본원리부터 배우는 UIKit) - MVVM까지 | 앨런(Allen) - 인프런

앨런(Allen) | 탄탄한 신입 iOS개발자가 되기 위한 기본기 갖추기. 15개의 앱을 만들어 보면서 익히는.. iOS프로그래밍의 기초, 15개의 앱을 만들면서 배우는 UIKit!근본부터 다른 강의, 들어보실래요?

www.inflearn.com

 

⭐️인프런에서 앨런님의 swift 앱 만들기⭐️

를 통해서 정리하였습니다

 

정말 정말 잘 설명해주기 때문에 제가 정리해놓은것 보다 보시는걸 추천드립니다

 

자료제공은 절대 불가합니다

 


타이머 만들기

|

 

 

 

화면은 스토리보드를 사용하여 만들었습니다

 

상단 스택뷰

 

첫 번째 스택뷰

Timer

초를 선택하세요

를 묶어서 스택뷰를 만들었습니다

 

+

두 번째 스택뷰

슬라이더 및 슬라이더 양 옆 각 각 하나씩 레이블 추가하여 스택뷰를 만들었습니다

그리고 슬라이더 양 옆 라벨에 너비를 추가하고 스택뷰 정렬과 분배를 추가하여 만들었습니다

 

 

하단 스택뷰

버튼 RESET + START 

타이머를 재설정하거나 시작하는 기능을 가진 두 개의 버튼을 가지고 스택뷰를 만들어 주었습니다

 

버튼의 스택뷰 정렬 및 분배 방식

 

그리고 화면의 위치가 올바르게 배치되기 위해서 오토레이아웃을 사용해서 각각 제약조건을 추가하여 

배치하였습니다

 

 

import UIKit
//audio
import AVFoundation

class ViewController: UIViewController {


    @IBOutlet weak var mainLabel: UILabel!
    
    @IBOutlet weak var slider: UISlider!
    
    weak var timer: Timer?
     
    var number: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        configureUI()
    }

    
    @IBAction func sliderChanged(_ sender: UISlider) {
        //슬라이더 value를 메인레이블 텍스트를 셋팅
        
        let seconds = Int(slider.value * 60)
        
        mainLabel.text = "\(seconds) 초"
        number = seconds
    }
    
    private func configureUI() {
        mainLabel.text = "초를 선택하세요"
        
        //자연스러운 효과를 위해 애니메이션을 줌
        //setValue : 첫 번째 매개변수는 0-1사이의 정중앙을 맞춰주기 위해서 설정
        slider.value = 0.5
        number = 30
        //timer = nil
        timer?.invalidate()
    }
    
    
    @IBAction func startButtonTapped(_ sender: UIButton) {
        
        //1초 지나갈 때마다 실행
        timer?.invalidate()
        //Timer.scheduledTimer(withTimeInterval: <#T##TimeInterval#>, repeats: <#T##Bool#>, block: <#T##(Timer) -> Void#>)
        Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(doSomethingAfter1Second), userInfo: nil, repeats: true)
        
        
        //만약 weak self라고 선언했으면 내부 선언 ?로 해야함 약한참조로 인해서 언제든 해제시켜줘야하기때문에
        //캡처리스트 [self]를 사용하게되면 self를 사용하지 않아도됨
        /*
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [self] _ in
            
            if self.number > 0 {
                number -= 1
                //value 자료형은 Float
                slider.value = Float(number) / Float(60)
                mainLabel.text = "\(number) 초"
                
            } else {
                number = 0
                mainLabel.text = "초를 선택하세요"
                
                //타이머 비활성화
                timer?.invalidate()
                AudioServicesPlayAlertSound(SystemSoundID(1322))
                
            }
        })
         */
    }
    
    @objc func doSomethingAfter1Second(){
        
        if self.number > 0 {
            number -= 1
            //value 자료형은 Float
            slider.value = Float(number) / Float(60)
            mainLabel.text = "\(number) 초"
            
        } else {
            number = 0
            mainLabel.text = "초를 선택하세요"
            
            //타이머 비활성화
            timer?.invalidate()
            AudioServicesPlayAlertSound(SystemSoundID(1322))
        }
    }
    
    @IBAction func resetButtonTapped(_ sender: UIButton) {
       //초기화 세팅
        configureUI()
        
    }

 

 

오디오 기능중 알림음을 재생하기 위해서

AudioServicesPlayAlertSound(SystemSoundID(1322))

AVFoundation 프레임워크를 가져와야합니다

 

그리고 Timer객체를 약한 참조(weak var)를 선언하여

메모리 누수를 방지해주고 있습니다

 

그리고 시작버튼이 눌렀을 때 호출되는 메서드인

startButtonTapped(_sender: UIButton)에서

 

time?.invalidate()

|

기존 타이머 기능이 작동되고 있다면 무효화하는 코드(타이머 비활성화)입니다

Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(doSomethingAfter1Second), userInfo: nil, repeats: true)

|

새로운 타이머를 설정하여 1초마다 doSomethingAfter1Second함수를 호출하고 있습니다

0보다 클 때 number를 1씩 감소하여 슬라이더 value를 조절하며 현재 몇 초 남았는지를 보여줍니다

그리고 만약 0초가 된다면

알림이 울리며 초기화되는걸 알 수 있습니다

 

그리고 주석한 부분

클로저는 실행될 때 필요한 외부 변수와 상수를 캡처하여 저장하게되는데

클로저와 클로저가 캡처한 객체들 간에 강한 참조가 형성될 수 있으므로 메모리누수로 이어지게 갈 수  있습니다

그래서 이를 방지 하기위해서 캡처리스트를 사용할 수 있습니다

캡처리스트로 통해서 약한 참조로 캡처하도록 지정할 수 있지만

캡처리스트 자체가 약한 참조/강한 참조 속성을 정의하는게 아니라 그저 클로저가

순간적으로 값을 캡처는 방법을 결정하는 도구일 뿐입니다

 

그리고 [self] 강한 참조 캡처리스트를 사용(<->[weak self])하게되면

클로저가 존재하는 한 해제되지 않고 메모리 누수의 위험을 증가하고 있습니다

보통 강한 참조를 명시적으로 사용하는 이유는

특정 상황에서 객체의 수명을 보장해야할 필요가 있을 경우 사용합니다

타이머가 반드시 객체의 수명을 연장해야하는 경우가 있으나 캡처리스트 대신 약한 참조를 사용하여 강한 참조 사이클을 방지하고

메모리 누수를 피하여 메모리 관리 측면에서 안전하게 사용할 수 있습니다

 

UISlider

|

타이머의 시간을 설정하는 데 사용됩니다

슬라이더의 값에 따라 레이블에 표시되는 초가 변경되며 타이머가 슬라이더의 값에 따라 작동하고 있습니다

 

그리고 슬라이더 value는 0-1사이의 자료형이 Float입니다

그래서 초기에 0.5로 설정하게되면 슬라이더가 중앙에 위치하게 됩니다

 

sliderChanged

|

슬라이더를 누르면서 조절하게 되면 호출되는 메서드입니다

 

let seconds = Int(slider.value * 60)

 

60초를 기준으로 잡고 만약 슬라이더 정 중앙의 값은 0.5

이므로 30초 우측 맨 끝으로 가게된다면 60초인걸 알 수 있습니다

 

그리고 슬라이더는 초기화 메서드에서 중앙값으로 설정되고 타이머 시작/업데이트/초기화에 슬라이더 값을

조정하여서 타이머 상태를 반영하는것을 볼 수 있습니다

 

 

결과

|

 

여기서 0초가되고나서 reset버튼을 누르게되어도

1초가 줄어드는 이유는

 

 

Timer를 변수에 넣지 않고 사용했기 때문에 일어났던 일이였습니다 그래서 타이머를 변수에 넣지 않고 실행하게 된다면

계속 줄어드는 현상이 발생하게됩니다

 

타이머에 대해서 더 알아보다

slider.setValue를 통해서 값과 애니메이션을 부여 유무를

정할 수 있는 것에 대해서 알 수 있었습니다

반응형