타이머 만들기!!

Woozoo·2023년 1월 10일
0

개인프로젝트

목록 보기
1/12

타이머 만들기

UI 구성

우선은 Label과 버튼으로 이루어진 간단한 구성!!
@추후에는 UIImage도 추가하고, 시간의 흐름을 나타낼 수 있는 디자인을 주도록 하자


시간 경과를 담아둘 초 를 secondsPassed 로 선언해주고
Timer 클래스를 옵셔널로 선언해줬다!


여기서 잠깐!
swift에서는 TimerDispatchSourceTimer 타이머를 만들 수 있는 두개의 클래스가 있다. 이 둘은 몇가지 차이점이 있는데

  • Timer는 고수준, 객체지향의 Foundation framework 중 하나.
    사용하기 쉽고 편리하면서, task를 특정한 시간이나 시간 간격마다 수행할 수 있다.
    대부분의 경우 이 Timer클래스를 사용하자!!
  • DispatchSourceTimer는 저수준의 Dispatch framework 이다.
    DispatchSource클래스를 베이스로 하고, 더 많은 컨트롤이 가능하지만 Timer보다는 복잡함!

시작 버튼이 눌리면 timer에 스케쥴 타이머를 넣어주고
클로져에서 원하는 로직이 실행되게 해줬다
timer.invalidate은 버튼을 두번 누르게되면 타이머가 여러개 생성되서
임시로 invalidate시킨 후에 다시 timer를 생성해주는 걸로...

Timer 클래스는 background Thread에서 동작한다

따라서 UI update와 관련된 task를 수행하려면 메인 스레드에서 동작하게 해야함.
DispatchQueue.main.async { } 부분이 되겠죠


time 시 분 초 표시하기!!


뭔가.. 하드코딩인 거 같은 느낌...
어떻게 하면 더 깔끔하게 작성이 가능할까?
현재 작성한건 시, 분, 초를 전역변수로 선언하고 초가 60이 된다면 분이 += 1
분이 60이 된다면 시가 += 1 로 해줬다
콤바인으로 수정하면 뭔가 더 깔끔해질 거 같기도.. 나중에 해보자

LongPressGesture 추가

button에다 longPressGesture를 추가해줬다
그리고 timeStart를 메소드로 빼서 objc func에 넣어줌

근데 넣었다가 빼줌...
먼가 직관적이지가 않은 것 같아서

애니메이션 추가

CAAnimation을 이용해서 ui들에 animation을 넣어볼까 고민을 하다가
나중에 캐릭터 애니메이션을 넣어줄 걸 고려해서 그냥 로티 추가하는 걸로 결정했다


오케이! 추가 완료

일시정지와 멈춤!

    enum TimeStatus {
        case start
        case pause
        case end
    }
    
    @IBAction func startButtonPressed(_ sender: UIButton) {
        switch timeStatus {
        case .end:
            timeStatus = .start
            animationView.play()
            timeStart()
        case .start:
            timeStatus = .pause
            timer.invalidate()
            animationView.stop()
        case .pause:
            timeStatus = .start
            timeStart()
            animationView.play()
        }
    }

TimeStatus enum을 만들고 startButton이 눌렸을 때 timeStatus에 따라서 분기 처리를 해줬음 button의 타이틀도 바꿔주면 좋을 거 같다

그런데 막상 버튼의 타이틀을 변경하니 글씨크기가 바뀌는 현상이 발생..!


방법을 찾았다..!!

AttributedTitle로 NSString을 넣어줌..

애플질문참고

메소드로 따로빼줬다


나중에 디자인할 때 font만 임포트해주면 되겠다!!

다시 롱프레스 추가 🤣

롱프레스 추가해서 꾹누르면 종료가 되게 만들어줬다!


움하하하하~
오늘은 여기까지!


탭바 추가


탭바 임베드하고 새로운 뷰컨트롤러 추가했다
방금 넣어준 뷰에서는 공부한 시간들이 저장되고,
리스트 형태로 볼 수 있게 만들 예정
이렇게 만들고나니 Date프로퍼티가 모델에 필요하겠다는 생각이 든다
🤔 하나의 뷰에 있는 데이터를 어떻게 다른 뷰에 전달해주지?
-> Delegate 패턴을 사용해서!


data 넘겨주기

timer 뷰에서 프로토콜을 작성해줬다

protocol TimeViewControllerDelegate: AnyObject {
    func didSavedTime(data: TimeModel)
}

class TimeViewController: UIViewController {
	weak var delegate: TimeViewControllerDelegate?
}

didSavedTime이라는 메소드를 작성하고 프로퍼티로 TimeModel을 받게끔!!

그리고 longPress 제스쳐가 실행되는 시점에서 delegate 메소드를 호출!

이제 TimeListVC에서 어떻게 timeVC의 delegate를 설정할지가 관건이었다
현재 TabBarController를 사용하고 있으니까

요런식으로 접근하면 되지 않을까? 하는 생각에 때려맞춰봤는데...


(당연히 delegate 메소드들은 구현해주고!)


시간초가 전달 된다!!
여기서 방어 로직을 하나 작성해야 할 것 같다
지금 롱프레스 제스쳐가 한번만 입력되는 게 아니라 계속해서 입력되고 있음


UIGestureRecognizer의 State를 통해서 입력의 구분이 가능하다!
.began일 때는 최초의 입력 한번만 일 때라서 타임 스탑 로직을 여기로 옮겨 줬다!


이제 rapidFire가 아닙니다!!!!😆

🤔 @objc func의 파라미터를 설정했는데 selector에서 호출 될 땐 따로 파라미터에 값이 전달 되지 않아도 되는 것 같다. 왜일까...?

한가지 생각해볼 점은 나중에 디자인 작업 시에 .changed State일 때 버튼의 UI에 애니메이션 주는 걸 생각해 볼 수 있을 것 같다


다른 탭으로 넘어갔을 때 타이머가 작동하지 않음!

아마도 다른탭으로 넘어갔을 때 메모리에서 해제가 되어 그런 것 같다.

closuer 선언부에 [weak self]를 사용하면 해결 가능!
항상 아리송한 느낌의 weak self였는데 지금 같은 상황에서 사용한다는 뉘앙스 캐치했음!!
나중에 꼭 한번 정리해보기!!


DiffableDataSource와 Snapshot 구현

Cell들이 추가되는 List뷰에서 스크롤을 할 때 약간의 버벅임이 느껴지는 걸 발견했다
혹시 Diffable과 Snapshot을 이용하면 나아지지 않을까 싶어 지금까지 구현한 걸 바꿔보기로!

  • Presentation
  • Data
  • Layout
    을 구성해줘야한다
    DiffableDatasource가 presentation과 Data를 담당할 거고,
    CompositionalLayout이 Layout을 담당할 예정!

요렇게 dataSource를 설정해줬다!

🤔 DiffableDataSource에 선언된 제네릭 타입들은 Hashable 해야한다고 함.
Hash라는 걸 이용해서 원하는 data를 찾을 때 조금 더 빠르고 효율적으로 찾게 해주는 프로토콜인 것 같은데 정확하게 설명을 못하겠다. ( 정리해보기 )


이런 에러가 마구마구 뜨고 셀이 제대로 표현되지 않는 현상이 발생!!
머선일인고 하니

TimeModel에 Hashable만 붙여주면 Hashable해질줄 알았는데.. 그게 아니었다..
int는 hashable하지가 않은 타입이라고 함
뭐가 문제인고 하니 아이템들이 hashable하지 않아서 구분이 안된단다
진짜 간단하게 해결 가능했음

let id = UUID()를 구조체 내에 정의해서 HashValue를 유니크하게 가질 수 있게끔 만들어줌

그런데 이번엔 또 cell이 추가 되지 않는 현상이 발생..
바보같은 짓을 하고 있었다..

extension TimeListViewController: TimeViewControllerDelegate {
    func didSavedTime(data: TimeModel) {
        list.append(data)
        var snapshot = dataSource.snapshot()
        snapshot.appendItems([data], toSection: .main)
        dataSource.apply(snapshot)
    }
}

list에 append 했을 때 새로운 snapshot을 apply 해주는 과정이 없었음..
이거 Combine이 있었다면 알아서 촥촥 됐을 거 같다

💡 Snapshot도 reloadData 메소드처럼
새로운 Item이 추가되면 apply 해줘야한다!!

그리고 셀의 초수가 같으면 identifier가 동일하게 인식되는 현상이 발생.

새로운 상수에 TimeModel을 담아주는 걸로 해결!

또 다른 이슈는 탭바를 현재 사용하고 있는데 두번째 탭을 터치하기전까지 인스턴스화되지 않아서 버튼을 아무리 눌러도 콜렉션뷰 셀이 생성되지 않는다는 거다
이 부분을 처음 앱이 실행될 때 모든 뷰들을 로드해서 구현해보려 했는데 방법을 못찾았다
그래서 data자체를 CoreData로 저장하고, 두번째 뷰에서 CoreData의 내용들을 로드해주는 식으로 구성해보려 한다

먼저 캘린더뷰!

캘린더뷰를 구성한 후에 날짜를 탭했을 때 나오는 리스트의 셀들을 구성할 수 있을 것 같다

코드로 캘린더뷰를 금방 만들 수 있어 작성해보자

정말 금방 구현가능함!! 🐳


콜렉션뷰 셀 디자인


마땅한 디자인이 생각이 나지 않아 임시로 레이아웃만 잡아뒀다


CoreData!

  • Entity: Class
  • Attributes: Properties
    라고 생각하자!

DataModel을 새로 만들어주고
CoreData를 구현하기 위해 필요한 코드들을 AppDelegate에 추가해줬다


entity 와 Property를 설정을 했는데 이걸 이따 Class로 사용해야함
근데 가끔 Class이름을 인식을 못할 때가 있는 듯 하다
--> 껐다키면됨 ㅋㅋ.. 한참을 찾았다.. 뭐가 잘못 되었는지


context는 staging area라고 생각하면됨 임시로 저장해두는 곳


현재 구조체 TimeModel에 담긴 프로퍼티들을
context에 담아주고, do try문을 통해서 .save()해줬다!

🤔 이렇게 사용해도 되는걸까..?
지금처럼 한단계 거치고 저장되도 상관이 없을지 의문이 생긴다.
아마 로드 하는 단계를 구현해보고 나면 감이 오지 않을까

참 context는 지금 shared로 접근해서 싱글톤처럼 사용할 수 있음! 앱 어디서든 같은 데이터로 접근이 가능함


load



로드는 이렇게 구현해줬다

delete

swipe 삭제를 구현하려다가 정말 먼길을 다녀왔다..
DiffableDatasource를 사용하고 있을 때 collectionViewCell의 swipe 삭제가 구현이 가능하지만 정보가 많이 없음. 헤딩을 하고 다니다
결국 테이블뷰로 구현하기로🥹

애플 공식문서랑 비디오 튜토리얼, 샘플 코드들 많이 참고했다.


💡 새롭게 알게된 사실
dataSource를 여태까진 viewDidLoad에서 작성해줬는데 지금처럼
class로 만들면 delegate 메소드들 호출이 가능함!!!


그리고 tableView 같은경우는 레이아웃이 정말 간단함
tableView.rowHeight로 셀 높이만 지정하면 끝!!

이렇게 작성하고 run 하면 셀이 잘 삭제 된다!!
delete될 때 coredata 로 저장되는 항목만 같이 삭제해주면 될 것 같다.

오늘 너무 달렸으니 여기까지!


삭제를 구현 못하고 있다..

TimeListVC에서 타임모델을 가지고 테이블뷰를 그리는데. 저장을 하는 시점에 DataModel에 class의 형태로 저장이 되고 있고,
그걸 다시 변환해서 로드하고 있음...
그래서 indexPath쪽이 꼬여버린 것 같다.
어쩔 수 없이 UserDefaults로 다시 돌아오기로 함
그렇게 코드를 작성하다 아직 모르는 부분이 많은 것 같다는 생각이 들어
추후에 업데이트 하기로!

profile
우주형

0개의 댓글