우선 처음 배운대로 배경화면을 설정해주고 가운데에 Button
을 추가하여 임시로 TAP
이라고 지어주었다.
디테일은 접어두고 실행이 되게끔 하는 것이 중요하니 25분의 타이머가 잘 되는지 확인하고 싶었기 때문에 버튼을 우클릭하여 코드와 연결해준다. 여기서 연결이 된 @IBOutlet
키워드는 프로퍼티들이 스토리보드의 UI 요소들과 연결될 수 있게 하는 역할을 한다.
weak
키워드는 뷰 컨트롤러가 이러한 객체들을 소유하지 않음을 나타내고 참조 사이클과 메모리 누수를 방지하는 데 도움을 줄 수 있으니 참고.
연결해준뒤 바로 타이머 설정을 해준다. 아 참 미리 사용할 컬러는 에셋에 추가 해두니 편했다. 구글링해서 방법 알아냈는데 생각보다 에셋추가는 간단하더라.
추가 해주고 본격적으로 코드 작성에 들어간다.
전 시간에 온도조절 시스템을 만들면서 1초마다 온도가 올라가거나 내려가는 설정을 할 때 배운걸 적용해봤다. 클래스에 timer 와 dropInterval 을 만들어 타이머기능과 떨어지는 간격을 설정해주었다.
온도 조절 시스템 당시 만들 때 타이머설정이 들어가면 코드 맨 처음 시작할 때 import Foundation
이 들어가야했다. 왜 import UIkit
이 들어가지?
아마 그땐 프로젝트
가 아니라 플레이그라운드
로 작성한 것이라 그런건가 싶었는데 그게 맞긴 했다.
UIKit
은 UI를 관리하는 프레임워크이지만, Foundation
의 기능을 포함하고 있으므로 타이머나 시간 처리가 가능하고, Foundation
을 별도로 추가하지 않아도 UIKit만으로도 타이머 같은 기능을 사용할 수 있다고 한다. 오오..갓 UIkit
아무튼 아래에 추가적으로 startTimer
라는 함수를 만들려고 하니 이번 Xcode업데이트로 인해 저렇게 자동완성을 해주었다.
탭을 너무 누르고 싶었지만.....초보자는 하나씩 적어본다.
천천히 해석해보자.
Timer.scheduledTimer
주기적으로 실행되는 타이머를 만든다. scheduled Timer는 예약된 타이머를 뜻한다. 일정 시간 간격마다 지정된 함수를 호출하게 되는 것.
timeInterval: dropInterval
타이머가 반복될 시간 간격을 설정하는 부분. dropInterval
은 초 단위로 설정된 값으로, 예를 들어 0.5
초로 설정되어 있다면 0.5
초마다 타이머가 실행된다.
우리는 아까 위에서 TimeInterval
을 0.1
초 간격으로 했으니 0.1
초로 간격으로 떨어질 것이다.
target: self
타이머가 실행될 때 어느 객체에서 함수를 호출할지 지정하는 부분이다. 여기서는 self, 즉 현재의 뷰 컨트롤러에서 타이머가 실행된다.
selector: #selector(dropPixel)
타이머가 실행될 때 호출할 함수이다. 여기서는 dropPixel
함수가 호출된다. @objc
로 표시된 함수만 선택할 수 있다.
아직 내가 dropPixel
에 대한 함수를 적지 않아서 위에서는 오류가 나고 있던 것.
그리고 #selector
를 사용하는 이유는 Objective-C
와의 호환성 때문에 써야한다고 한다. Swift의 함수는 기본적으로 Cocoa Touch
프레임워크에서 사용되는 Objective-C
와 연결되도록 설계되었는데 특정 메서드를 @objc
로 선언하면 이 메서드는 Objective-C
런타임에서도 호출할 수 있게 된다.
그리고 이 메서드를 타이머나 버튼의 addTarget
등에서 사용하려면 #selector
로 그 메서드를 명확하게 가리켜야 한다고 하니 꼭 사용해야하는 것이었다.
처음에는 그냥 selecter: dropPixel
이라고 하면 안 되지? 했는데,dropPixel
을 그냥 쓰면 Swift의 일반 함수 호출처럼 인식된다고 한다.
하지만 타이머나 이벤트 처리에서는 메서드의 참조(포인터)가 필요하다는 것이다. 단순히 함수를 호출하는 것이 아니라, 그 함수의 위치나 포인터를 전달해야 하기 때문에 #selector
는 이를 위해 함수 참조를 생성하는 역할을 해준다.
포인터란?
C언어에서 포인터(pointer)란 메모리의 주소값을 저장하는 변수, 혹은 자료가 저장되는 기억장치의 기억주소를 가리키는 지시자.
자 이어서,
userInfo: nil
추가적인 정보를 타이머에 전달할 때 사용하는데 여기서는 사용하지 않으므로 nil로 설정했다.
repeats: true
반복 실행 여부를 설정하는 부분이다. repeat-While 문이 생각난다. 아무튼 이걸 true로 설정해줬기 때문에, 타이머는 한 번 실행되고 끝나는 것이 아니라 지정된 간격마다 계속해서 반복될 것이다.
DispatchQueue.main.asyncAfter(deadline: .now() + 1500)
이 줄은 1500초(25분) 후에 특정 작업을 수행하도록 예약하는 부분인데, 이 코드는 25분이 지난 후에 타이머를 정지시키는 역할을 해준다.
DispatchQueue.main.asyncAfter(...):
메인 스레드에서 일정 시간이 지난 후에 작업을 실행하는 함수. iOS에서는 UI 관련 작업을 모두 메인 스레드에서 실행해야 하기 때문에 메인 스레드에서 실행하도록 예약하는 것이다.
DispatchQueue
는 메인 스레드에서 UI 작업을 안전하게 처리하고, 백그라운드에서 오래 걸리는 작업을 효율적으로 처리하기 위해 사용된다고 한다.
비동기적 작업을 통해 앱이 멈추지 않고 부드럽게 동작하도록 하며 시간이 지연된 작업도 실행할 수 있고, 이를 통해 앱의 성능을 유지하고 *멀티스레딩 환경에서 다양한 작업을 효과적으로 처리할 수 있다.
멀티스레딩
은 여러 코어에서 한 번에 여러 개의 스레드를 처리하는 CPU 성능을 활용하는 프로그래밍의 한 유형이다.
deadline: .now() + 1500:
현재 시간 (.now())에 1500초를 더한 시점을 의미한다. 즉, 현재 시간으로부터 1500초(25분) 후에 작업이 실행된다.
self.timer?.invalidate()
이 부분은 타이머를 정지시키는 코드이다.
self.timer?.invalidate():
타이머가 더 이상 실행되지 않도록 타이머를 무효화한다. 타이머가 종료되면서 지정된 시간 간격에 따라 더 이상 dropPixel
함수가 호출되지 않게 된다.
self.timer = nil
타이머를 완전히 해제하는 부분이다.
이어서 내가 원하는건 우선 랜덤으로 한번 떨어트려 보는 것이다. 마치 비가 오듯이 하는 건데, ropPixel()
함수는 픽셀이 떨어지도록 하는 함수고, 화면의 임의의 위치에 작은 픽셀을 생성해서 UIView.animate()
를 통해 픽셀이 아래로 떨어지도록 애니메이션을 적용했다.
픽셀은 3초동안 화면의 맨 위에서 맨 아래로 떨어지고 떨어진 후에는 뷰에서 제거된다.
사실 제거되지 않고 쌓이는 모션을 만들어보고.. 싶은데 우선 되는지 작동확인을 위해 실행먼저 해보려한다.
CGFloat.random(in: 0...(view.frame.width - pixelSize))
은 픽셀이 떨어질 x 좌표를 화면 가로 크기에 맞게 임의로 설정하는 코드이다. 오토레이아웃이 적용될지 모르겠지만 우선 해보자.
이 함수는 사용자가 "TAP" 버튼을 눌렀을 때 호출되는 것인데, 먼저 화면 배경색을 color4로 변경하고, "TAP" 버튼을 서서히 사라지게(투명도 0) 만든 후 숨겨줬다.
이 코드에서는 애니메이션 블록을 통해 버튼이 사라지는 동작을 수행한하는 건 self.tapButton.isHidden = true
이다. Tap 버튼을 숨기는 동작을 수행하는데.. 이는 즉시 버튼을 보이지 않게 하는 것이지 서서히 투명해지는 것은 아니다.
버튼이 사라진 후에 startTimer()
를 호출하여 픽셀 떨어뜨리는 타이머를 시작하는 것. 그렇게 되면 처음 실행했을 때 "TAP" 버튼이 화면에 나타나고, 버튼을 누르면 배경색이 바뀐 다음, 버튼이 서서히 사라질 것이다.
버튼이 사라지면 0.1초 간격으로 작은 픽셀들이 위에서 떨어지기 시작한다.
픽셀은 3초 동안 화면 아래로 떨어지며, 25분 동안 이 동작이 반복될 것이다 한번 보자.
ㅋㅋㅋㅋ 뭔가 피를 흘리는 것 같아서 헛웃음 나온다...
일단 수정하고 싶은 게 누르자마자 TAP이 너무 빨리 사라져 버린 것.
현재 코드에서는 투명도 변경 없이 버튼을 바로 숨기고 있어서 투명도를 서서히 조정하려면 alpha
값을 0
으로 변경해야 한다고 한다.
self.tapButton.isHidden = true
는 즉시 버튼을 화면에서 보이지 않게 하는 것이며, 애니메이션으로 사라지도록 만들려면 alpha 값을 조정해야 한다.
< alpha 값의 설정 >
alpha = 0: 뷰가 완전히 보이지 않음
alpha = 1: 뷰가 완전히 보임
alpha = 0.5: 뷰가 반투명함 (반만 보임)
불투명하게 두는것도 괜찮은 것 같은데, 그리고 한 김에 탭을 다시 눌렀을 때 일시정지가 되고 다시 한번 더 탭을 누르면 일시정지 된 구간부터 시작되는 것도 추가하고 싶어졌다.
그러면 변수 추가를 몇개 해줘야 한다.
timerStartTime:
타이머의 시작 시간을 기록하는 변수
elapsedTime:
경과 시간을 기록하는 변수
func 에 timerStartTime = Date()
도 추가해주고,
IBAction
도 수정해야하는데 위 부터 천천히 내려와보자.
if timer != nil
timer가 nil이 아닐 경우, 그러니까 타이머가 실행 중일 때의 조건인데, 타이머가 실행되고 있다면 일시 정지 기능을 수행하는 것이다. 그리고나서 invalidate(무효화) 코드로 타이머를 중지시키는 것.
timer = nil
타이머를 nil로 설정해서 이후 다시 타이머가 실행되지 않도록 해준다.
elapsedTime += Date().timeIntervalSince(timerStartTime ?? Date())
현재 시간에서 타이머 시작 시간을 빼서 경과 시간을 계산하려고 하는 것이다.
코드가 좀 어려워서 해석해봤다.
Date()
를 통해 현재 시간을 가져오고,timerStartTime
이 nil
인지 확인하여 타이머 시작 시간을 확인 해준다. 만약 nil
이라면, ??
연산자를 사용하여 현재 시간을 대신 사용하게 될 것이다.timeIntervalSince(...)
를 사용해서 현재 시간과 timerStartTime
간의 차이를 초 단위로 계산한다. 쉽게 말해 경과 시간을 계산하는 것이다. 이 코드는 타이머가 시작된 시점부터 지금까지의 경과 시간을 측정하는 것.
그리고 계산된 경과 시간을 elapsedTime
에 더해주면 이로 인해 전체 경과 시간이 업데이트 될 것이다.
우선 내리다가 멈추는 구현은 했는데 시간을 나타내는 걸 추가할지 말지 고민이다.
시간을 나타내는 것 보다 저 픽셀들이 쌓였으면 좋겠는데 말이다. 난 기존 지금 사용하고 있는 뽀모도로 타이머를 잘 쓰고 있지만 시간이 몇 분 남았는지 숫자로 딱 나와있으니 뭔가 '아 몇 분 몇 초 남았구나.. 빨리 해야겠네' 라며 마음이 약간 급해지는 것이 없지않아 있기 때문에 그냥 '아 쌓이고 있구나.. 내 토마토 픽셀들..' 이러면서 다시 편히 업무에 들어가게 하고 싶은 마음이 있다.
내일 저 픽셀들이 쌓이는 걸 구현해보고, 페이지도 하나 더 추가봐야겠다. 어우 확실히 UIkit으로 바로 들어오니 문법 공부할 때와는 차원이 다르다. 그래도 까먹었던 부분은 강의를 들으면서 다시 공부하니 머리에 더 잘 들어온다.
중간 중간에 오타로 인한 어이없는 오류들이 있었는데, 예를들어 date를 data로 쓴다던가.. 그런거는 그냥 쪽팔려서 뺐다.