일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 러닝타이머
- AnyObject
- Timer
- CoreLocation
- xcode로 날씨앱 만들기
- CLLocationManagerDelegate
- swift
- 단일 책임원칙
- MKMapItem
- addannotation
- font book
- 러닝기록앱
- SwiftUI Boolean 값
- weak var
- Startign Assignments
- 한국어 개인정보처리방침
- UICollectionViewFlowLayout
- 서체관리자
- Protocol
- App Store Connect
- MKMapViewDelegate
- weatherKit
- RunningTimer
- Required Reason API
- UIAlertAction
- 영문 개인정보처리방침
- WeatherManager
- dispatchsource
- Xcode
- 클로저의 캡슐화
Archives
- Today
- Total
VesselWheel
러닝기록 타이머 만들기(2/3)(with thread, RunLoop) 본문
이 글은 1/3에서 활용한 Timer 클래스의 커스텀 매소드를 호출하여 View에서 호출하는 로직을 구현하였다.
MyTimer Class
더보기
//
// MyTimer.swift
// Run-It
//
// Created by Jason Yang on 2/26/24.
//
import UIKit
import CoreLocation
class MyTimer {
var timer: Timer?
var time = 0
var distance = 0.0
var pace = 0.0
var pausedTime = 0 // 일시정지된 시간을 저장할 변수
var pausedDistance = 0.0 // 일시정지된 거리를 저장할 변수
var pausedPace = 0.0 // 일시정지된 페이스를 저장할 변수
var isPaused = false
var updateUI: (() -> Void)?
// var backgroundTask: UIBackgroundTaskIdentifier = .invalid
//TODO: UIBackgroundTaskIdentifier는 앱이 백그라운드에 들어갔을 때 30초까지만 유지되기 때문에 종료시점과 재시작시점을 호출하는 방법으로 로직을 변경
init(time: Int, distance: Double, pace: Double, updateUI: @escaping () -> Void) {
self.time = time
self.distance = distance
self.pace = pace
self.updateUI = updateUI
startTimer()
}
@objc func timerFired() {
// 이 메서드는 타이머가 동작할 때마다 호출됩니다.
DispatchQueue.main.async {
self.time += 1
self.distance += 0.01 // RunningTimerManager과 연계해서 변경 필요
// 거리가 0.05km, 즉 50m 이상일 때만 페이스를 계산
self.pace = self.distance >= 0.05 ? Double(self.time) / self.distance : 0
print("Timer fired with time: \(self.time), distance: \(self.distance), pace: \(self.pace)")
self.updateUI?() // 상태가 변경될 때마다 UI 업데이트
}
}
public func startTimer() {
DispatchQueue.main.async {
self.timer = Timer(timeInterval: 1.0, target: self, selector: #selector(self.timerFired), userInfo: nil, repeats: true)
RunLoop.current.add(self.timer!, forMode: RunLoop.Mode.common)
self.timer?.fire()
}
}
public func pauseTimer() {
timer?.invalidate()
pausedTime = time // 일시정지 시점의 시간 저장
pausedDistance = distance // 일시정지 시점의 거리 저장
pausedPace = pace // 일시정지 시점의 페이스 저장
}
public func resumeTimer() {
// 일시정지 했던 시점의 시간, 거리, 페이스를 복원
time = pausedTime
distance = pausedDistance
pace = pausedPace
print("Resuming timer with time: \(time), distance: \(distance), pace: \(pace)")
startTimer()
}
public func stopTimer() {
timer?.invalidate()
timer = nil
// endBackgroundUpdateTask()
}
// private func beginBackgroundUpdateTask() {
// self.backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
// self?.endBackgroundUpdateTask()
// }
// }
//
// private func endBackgroundUpdateTask() {
// UIApplication.shared.endBackgroundTask(self.backgroundTask)
// self.backgroundTask = .invalid
// }
}
전체코드
더보기
//
// RunningTimerViewController.swift
// Running&Eat
//
// Created by Jason Yang on 2/21/24.
//
import UIKit
import SnapKit
class RunningTimerViewController: UIViewController{
//MARK: - UI properties
var distance: Double = 0
var time: Int = 0
var pace: Double = 0
var myTimer: MyTimer?
let statusBarView = UIView()
let timeLabel: UILabel = {
let label = UILabel()
label.text = "시간"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 30)
return label
}()
lazy var timeNumberLabel: UILabel = {
let label = UILabel()
label.text = "0:00:00"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 45)
return label
}()
let topContainer : UIView = {
let container = UIView()
return container
}()
let distanceContainer = UIView()
let topSplitLine: UIView = {
let line = UIView()
line.alpha = 0.5
line.backgroundColor = .gray
return line
}()
let middleSplitLine: UIView = {
let line = UIView()
line.alpha = 0.5
line.backgroundColor = .gray
return line
}()
let paceLabel: UILabel = {
let label = UILabel()
label.text = "페이스"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 30)
return label
}()
lazy var paceNumberLabel: UILabel = {
let label = UILabel()
label.text = "0:00"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 45)
return label
}()
let distanceLabel: UILabel = {
let label = UILabel()
label.text = "거리"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 30)
return label
}()
lazy var distanceNumberLabel: UILabel = {
let label = UILabel()
label.text = "0.00"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 100)
label.adjustsFontSizeToFitWidth = false
return label
}()
let kilometerLabel: UILabel = {
let label = UILabel()
label.text = "킬로미터"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 30)
return label
}()
lazy var pauseRunningButton: UIButton = {
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
button.tintColor = .black
let configuration = UIImage.SymbolConfiguration(pointSize: 50)
if let image = UIImage(systemName: "pause.fill", withConfiguration: configuration) {
button.setImage(image, for: .normal)
}
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 50
button.clipsToBounds = true
button.addTarget(self, action: #selector(pauseRunning), for: .touchUpInside)
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPresspauseRunning))
longPressGesture.minimumPressDuration = 3
button.addGestureRecognizer(longPressGesture)
return button
}()
let bottomView = UIView()
//MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
addSubview()
setupUI()
setLayout()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
recordRunning()
}
// MARK: - @objc
@objc private func pauseRunning() {
print("TappedButton - pauseRunning()")
myTimer?.pauseTimer()
let pauseRunningViewController = PauseRunningHalfModalViewController()
pauseRunningViewController.myTimer = self.myTimer
// myTimer의 상태를 pauseRunningViewController에 전달
pauseRunningViewController.pausedTime = self.myTimer?.time ?? 0
pauseRunningViewController.pausedDistance = self.myTimer?.distance ?? 0.0
pauseRunningViewController.pausedPace = self.myTimer?.pace ?? 0.0
showMyViewControllerInACustomizedSheet(pauseRunningViewController)
}
@objc func longPresspauseRunning(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
UIView.animate(withDuration: 0.3, animations: {
self.pauseRunningButton.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}) { _ in
self.pauseRunning()
}
} else if sender.state == .ended || sender.state == .cancelled {
UIView.animate(withDuration: 0.3, animations: {
self.pauseRunningButton.transform = CGAffineTransform.identity
})
}
}
}
extension RunningTimerViewController{
// MARK: - Running Timer Method
private func recordRunning() {
myTimer = MyTimer(time: time, distance: distance, pace: pace, updateUI: { [weak self] in
self?.updateTimerUI()
})
}
private func updateTimerUI() {
DispatchQueue.main.async {
let hours = (self.myTimer?.time ?? 0) / 3600
let minutes = (self.myTimer?.time ?? 0 % 3600) / 60
let seconds = (self.myTimer?.time ?? 0 % 3600) % 60
self.timeNumberLabel.text = String(format: "%01d:%02d:%02d", hours, minutes, seconds)
if self.myTimer?.distance ?? 0 >= 0.05 {
let paceMinutes = Int(self.myTimer?.pace ?? 0) / 60
let paceSeconds = Int(self.myTimer?.pace ?? 0) % 60
self.paceNumberLabel.text = String(format: "%02d:%02d", paceMinutes, paceSeconds)
} else {
// 거리가 50m 미만일 때는 페이스를 표시하지 않음
self.paceNumberLabel.text = "--:--"
}
self.distanceNumberLabel.text = String(format: "%.2f", self.myTimer?.distance ?? 0)
}
}
// MARK: - setupUI
private func setupUI() {
view.backgroundColor = .systemGreen
statusBarView.backgroundColor = .systemGreen
bottomView.backgroundColor = .systemGreen
[statusBarView, bottomView].forEach { subView in view.addSubview(subView)
}
}
// MARK: - addSubview
private func addSubview() {
view.addSubview(statusBarView)
view.addSubview(topContainer)
topContainer.addSubview(timeLabel)
topContainer.addSubview(timeNumberLabel)
topContainer.addSubview(paceLabel)
topContainer.addSubview(paceNumberLabel)
topContainer.addSubview(topSplitLine)
view.addSubview(distanceContainer)
distanceContainer.addSubview(middleSplitLine)
distanceContainer.addSubview(distanceLabel)
distanceContainer.addSubview(distanceNumberLabel)
distanceContainer.addSubview(kilometerLabel)
view.addSubview(pauseRunningButton)
view.addSubview(bottomView)
}
// MARK: - Layout
private func setLayout() {
statusBarView.snp.makeConstraints { make in
make.leading.top.trailing.equalToSuperview()
make.bottom.equalTo(view.safeAreaLayoutGuide.snp.topMargin)
}
timeLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(25)
make.leading.equalToSuperview().offset(2)
}
timeNumberLabel.snp.makeConstraints { make in
make.top.equalTo(timeLabel.snp.bottom).offset(40)
make.leading.equalTo(timeLabel)
}
paceLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(25)
make.trailing.equalToSuperview().offset(-10)
}
paceNumberLabel.snp.makeConstraints { make in
make.top.equalTo(paceLabel.snp.bottom).offset(40)
make.trailing.equalTo(paceLabel)
}
// topContainer의 정중앙에 수직의 선
topSplitLine.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.centerX.equalToSuperview()
make.height.equalTo(150)
make.width.equalTo(1) //
}
topContainer.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top)
make.width.equalTo(360)
make.height.equalTo(200)
}
distanceContainer.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(topContainer.snp.bottom)
make.width.equalTo(360)
make.height.equalTo(330)
}
middleSplitLine.snp.makeConstraints { make in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
make.height.equalTo(1)
make.width.equalTo(350) //
}
distanceLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(25)
make.leading.equalToSuperview().offset(10)
}
distanceNumberLabel.snp.makeConstraints { make in
make.top.equalTo(distanceLabel.snp.bottom).offset(40)
make.centerX.equalToSuperview()
}
kilometerLabel.snp.makeConstraints { make in
make.top.equalTo(distanceNumberLabel.snp.bottom).offset(5)
make.centerX.equalToSuperview()
}
pauseRunningButton.snp.makeConstraints { make in
make.top.equalTo(distanceContainer.snp.bottom).offset(50)
make.centerX.equalToSuperview()
make.width.equalTo(100)
make.height.equalTo(100)
}
bottomView.snp.makeConstraints { make in
make.leading.bottom.trailing.equalToSuperview()
make.top.equalTo(view.safeAreaLayoutGuide.snp.bottomMargin)
}
}
}
2. PauseRunningHalfModalViewController
더보기
//
// PauseRunningHalfModalViewController.swift
// Run-It
//
// Created by Jason Yang on 2/23/24.
//
import UIKit
class PauseRunningHalfModalViewController: UIViewController {
//MARK: - UI properties
var pausedTime: Int = 0
var pausedPace: Double = 0.0
var pausedDistance: Double = 0.0
var myTimer: MyTimer?
let modaltopContainer : UIView = {
let container = UIView()
return container
}()
let modaltimeLabel: UILabel = {
let label = UILabel()
label.text = "시간"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 25)
return label
}()
lazy var modaltimeNumberLabel: UILabel = {
let label = UILabel()
label.text = "0:00:00"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 45)
return label
}()
let modaltopSplitLine: UIView = {
let line = UIView()
line.alpha = 0.5
line.backgroundColor = .gray
return line
}()
let modaldistanceLabel: UILabel = {
let label = UILabel()
label.text = "거리"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 25)
return label
}()
lazy var modaldistanceNumberLabel: UILabel = {
let label = UILabel()
label.text = "0:00:00"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 45)
return label
}()
let modalkilometerLabel: UILabel = {
let label = UILabel()
label.text = "킬로미터"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 15)
return label
}()
let modalpaceContainer = UIView()
let modalmiddleSplitLine: UIView = {
let line = UIView()
line.alpha = 0.5
line.backgroundColor = .gray
return line
}()
let modalpaceLabel: UILabel = {
let label = UILabel()
label.text = "평균 페이스"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 25)
return label
}()
lazy var modalpaceNumberLabel: UILabel = {
let label = UILabel()
label.text = "0:00"
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 45)
return label
}()
let bottombuttonContainer = UIView()
let restartbuttonContainer = UIView()
lazy var restartRunningButton: UIButton = {
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
button.tintColor = .black
let configuration = UIImage.SymbolConfiguration(pointSize: 50)
if let image = UIImage(systemName: "restart", withConfiguration: configuration) {
button.setImage(image, for: .normal)
}
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 50
button.clipsToBounds = true
button.addTarget(self, action: #selector(restartRunning), for: .touchUpInside)
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(modallongPressrestartRunning))
longPressGesture.minimumPressDuration = 3
button.addGestureRecognizer(longPressGesture)
return button
}()
let stopbuttonContainer = UIView()
lazy var stopRunningButton: UIButton = {
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
button.tintColor = .black
let configuration = UIImage.SymbolConfiguration(pointSize: 50)
if let image = UIImage(systemName: "stop.fill", withConfiguration: configuration) {
button.setImage(image, for: .normal)
}
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 50
button.clipsToBounds = true
button.addTarget(self, action: #selector(stopRunning), for: .touchUpInside)
return button
}()
//MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
addModalSubview()
setupModalUI()
setModalLayout()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateModalUI()
}
// MARK: - @objc
@objc private func restartRunning() {
print("TappedButton - restartRunning()")
myTimer?.resumeTimer()
let runningTimerViewController = RunningTimerViewController()
runningTimerViewController.modalPresentationStyle = .fullScreen
// myTimer 인스턴스를 전달
runningTimerViewController.myTimer = self.myTimer
// 일시정지되었던 시간, 거리, 페이스를 전달
runningTimerViewController.time = self.myTimer?.pausedTime ?? 0
runningTimerViewController.distance = self.myTimer?.pausedDistance ?? 0.0
runningTimerViewController.pace = self.myTimer?.pausedPace ?? 0.0
self.present(runningTimerViewController, animated: true)
}
@objc func modallongPressrestartRunning(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
UIView.animate(withDuration: 0.3, animations: {
self.restartRunningButton.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}) { _ in
self.restartRunning()
}
} else if sender.state == .ended || sender.state == .cancelled {
UIView.animate(withDuration: 0.3, animations: {
self.restartRunningButton.transform = CGAffineTransform.identity
})
}
}
@objc private func stopRunning() {
print("TappedButton - stopRunning()")
let alert = UIAlertController(title: "운동을 완료하시겠습니까?", message: "근처 편의점에서 물 한잔 어떻신가요?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "운동 완료하기", style: .default, handler: { _ in
let startRunningViewController = StartRunningViewController()
startRunningViewController.modalPresentationStyle = .fullScreen
self.present(startRunningViewController, animated: true)
}))
// // Core Data context를 가져옵니다.
// let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
//
// // RunningData Entity의 새 인스턴스를 생성합니다.
// let runningData = NSEntityDescription.insertNewObject(forEntityName: "RunningData", into: context) as! RunningData
//
// // 러닝 데이터를 설정합니다.
// runningData.time = Int32(self.time)
// runningData.distance = self.distance
// runningData.pace = self.pace
//
// // 변경 사항을 저장합니다.
// do {
// try context.save()
// } catch {
// // 저장에 실패한 경우 에러를 출력합니다.
// print("Failed to save running data: \(error)")
// }
// self.dismiss(animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "취소하기", style: .destructive, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
extension PauseRunningHalfModalViewController {
// MARK: - Running Timer Method
private func updateModalUI() {
// 시간, 거리, 페이스를 포맷에 맞게 변환
let hours = pausedTime / 3600
let minutes = (pausedTime % 3600) / 60
let seconds = (pausedTime % 3600) % 60
let paceMinutes = Int(pausedPace) / 60
let paceSeconds = Int(pausedPace) % 60
// 레이블의 텍스트를 설정
modaltimeNumberLabel.text = String(format: "%01d:%02d:%02d", hours, minutes, seconds)
modaldistanceNumberLabel.text = String(format: "%.2f", pausedDistance)
modalpaceNumberLabel.text = String(format: "%02d:%02d", paceMinutes, paceSeconds)
}
// MARK: - setupUI
private func setupModalUI() {
view.backgroundColor = .white
}
// MARK: - addSubview
private func addModalSubview() {
view.addSubview(modaltopContainer)
modaltopContainer.addSubview(modaltimeLabel)
modaltopContainer.addSubview(modaltimeNumberLabel)
modaltopContainer.addSubview(modaldistanceLabel)
modaltopContainer.addSubview(modaldistanceNumberLabel)
modaltopContainer.addSubview(modalkilometerLabel)
modaltopContainer.addSubview(modaltopSplitLine)
view.addSubview(modalpaceContainer)
modalpaceContainer.addSubview(modalmiddleSplitLine)
modalpaceContainer.addSubview(modalpaceLabel)
modalpaceContainer.addSubview(modalpaceNumberLabel)
view.addSubview(bottombuttonContainer)
bottombuttonContainer.addSubview(restartbuttonContainer)
bottombuttonContainer.addSubview(stopbuttonContainer)
restartbuttonContainer.addSubview(restartRunningButton)
stopbuttonContainer.addSubview(stopRunningButton)
}
// MARK: - Layout
private func setModalLayout() {
modaltimeLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(25)
make.leading.equalToSuperview().offset(10)
}
modaltimeNumberLabel.snp.makeConstraints { make in
make.top.equalTo(modaltimeLabel.snp.bottom).offset(20)
make.leading.equalTo(modaltimeLabel)
}
// topContainer의 정중앙에 수직의 선
modaltopSplitLine.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.centerX.equalToSuperview()
make.height.equalTo(100)
make.width.equalTo(1) //
}
modaldistanceLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(25)
make.leading.equalTo(modaltopSplitLine.snp.trailing).offset(10)
}
modaldistanceNumberLabel.snp.makeConstraints { make in
make.top.equalTo(modaldistanceLabel.snp.bottom).offset(20)
make.leading.equalTo(modaldistanceLabel)
}
modalkilometerLabel.snp.makeConstraints { make in
make.top.equalTo(modaldistanceNumberLabel.snp.bottom).offset(2)
make.centerX.equalTo(modaldistanceNumberLabel.snp.centerX)
}
modalpaceLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(15)
make.leading.equalToSuperview().offset(10)
}
modalpaceNumberLabel.snp.makeConstraints { make in
make.top.equalTo(modalpaceLabel.snp.bottom).offset(10)
make.leading.equalTo(modalpaceLabel.snp.leading)
}
modaltopContainer.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top)
make.width.equalTo(360)
make.height.equalTo(150)
}
modalpaceContainer.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(modaltopContainer.snp.bottom)
make.width.equalTo(360)
make.height.equalTo(150)
}
modalmiddleSplitLine.snp.makeConstraints { make in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
make.height.equalTo(1)
make.width.equalTo(350) //
}
bottombuttonContainer.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(modalpaceContainer.snp.bottom)
make.width.equalTo(360)
make.height.equalTo(100)
}
restartbuttonContainer.snp.makeConstraints { make in
make.leading.equalToSuperview()
make.centerY.equalToSuperview()
make.width.equalTo(180)
make.height.equalTo(100)
}
stopbuttonContainer.snp.makeConstraints { make in
make.trailing.equalToSuperview()
make.centerY.equalToSuperview()
make.width.equalTo(180)
make.height.equalTo(100)
}
restartRunningButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.centerY.equalToSuperview()
make.width.equalTo(100)
make.height.equalTo(100)
}
stopRunningButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.centerY.equalToSuperview()
make.width.equalTo(100)
make.height.equalTo(100)
}
}
}
1. MyTimer 클래스를 활용하여 RunningTimerViewController에서 호출하기
class RunningTimerViewController: UIViewController {
//MARK: - UI properties
var distance: Double = 0
var time: Int = 0
var pace: Double = 0
var myTimer: MyTimer?
...
lazy var pauseRunningButton: UIButton = {
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
button.tintColor = .black
let configuration = UIImage.SymbolConfiguration(pointSize: 50)
if let image = UIImage(systemName: "pause.fill", withConfiguration: configuration) {
button.setImage(image, for: .normal)
}
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 50
button.clipsToBounds = true
button.addTarget(self, action: #selector(pauseRunning), for: .touchUpInside)
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPresspauseRunning))
longPressGesture.minimumPressDuration = 3
button.addGestureRecognizer(longPressGesture)
return button
}()
//MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
addSubview()
setupUI()
setLayout()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
recordRunning()
}
// MARK: - @objc
@objc private func pauseRunning() {
print("TappedButton - pauseRunning()")
myTimer?.pauseTimer()
let pauseRunningViewController = PauseRunningHalfModalViewController()
pauseRunningViewController.myTimer = self.myTimer
// myTimer의 상태를 pauseRunningViewController에 전달
pauseRunningViewController.pausedTime = self.myTimer?.time ?? 0
pauseRunningViewController.pausedDistance = self.myTimer?.distance ?? 0.0
pauseRunningViewController.pausedPace = self.myTimer?.pace ?? 0.0
showMyViewControllerInACustomizedSheet(pauseRunningViewController)
}
extension RunningTimerViewController {
// MARK: - Running Timer Method
private func recordRunning() {
myTimer = MyTimer(time: time, distance: distance, pace: pace, updateUI: { [weak self] in
self?.updateTimerUI()
})
}
private func updateTimerUI() {
DispatchQueue.main.async {
let hours = (self.myTimer?.time ?? 0) / 3600
let minutes = (self.myTimer?.time ?? 0 % 3600) / 60
let seconds = (self.myTimer?.time ?? 0 % 3600) % 60
self.timeNumberLabel.text = String(format: "%01d:%02d:%02d", hours, minutes, seconds)
if self.myTimer?.distance ?? 0 >= 0.05 {
let paceMinutes = Int(self.myTimer?.pace ?? 0) / 60
let paceSeconds = Int(self.myTimer?.pace ?? 0) % 60
self.paceNumberLabel.text = String(format: "%02d:%02d", paceMinutes, paceSeconds)
} else {
// 거리가 50m 미만일 때는 페이스를 표시하지 않음
self.paceNumberLabel.text = "--:--"
}
self.distanceNumberLabel.text = String(format: "%.2f", self.myTimer?.distance ?? 0)
}
}
// -> pauseRunningButton을 원하는 뷰에 addSubview()해주고, setLayout() 레이아웃을 설정
private func addSubview() {}
private func setLayout() {}
2. MyTimer 클래스를 활용하여 PauseRunningHalfModalViewController에서 호출하기
import UIKit
class PauseRunningHalfModalViewController: UIViewController {
//MARK: - UI properties
var pausedTime: Int = 0
var pausedPace: Double = 0.0
var pausedDistance: Double = 0.0
var myTimer: MyTimer?
//...
lazy var restartRunningButton: UIButton = {
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
button.tintColor = .black
let configuration = UIImage.SymbolConfiguration(pointSize: 50)
if let image = UIImage(systemName: "restart", withConfiguration: configuration) {
button.setImage(image, for: .normal)
}
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 50
button.clipsToBounds = true
button.addTarget(self, action: #selector(restartRunning), for: .touchUpInside)
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(modallongPressrestartRunning))
longPressGesture.minimumPressDuration = 3
button.addGestureRecognizer(longPressGesture)
return button
}()
let stopbuttonContainer = UIView()
lazy var stopRunningButton: UIButton = {
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
button.tintColor = .black
let configuration = UIImage.SymbolConfiguration(pointSize: 50)
if let image = UIImage(systemName: "stop.fill", withConfiguration: configuration) {
button.setImage(image, for: .normal)
}
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 50
button.clipsToBounds = true
button.addTarget(self, action: #selector(stopRunning), for: .touchUpInside)
return button
}()
//MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
addModalSubview()
setupModalUI()
setModalLayout()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateModalUI()
}
// MARK: - @objc
@objc private func restartRunning() {
print("TappedButton - restartRunning()")
myTimer?.resumeTimer()
let runningTimerViewController = RunningTimerViewController()
runningTimerViewController.modalPresentationStyle = .fullScreen
// myTimer 인스턴스를 전달
runningTimerViewController.myTimer = self.myTimer
// 일시정지되었던 시간, 거리, 페이스를 전달
runningTimerViewController.time = self.myTimer?.pausedTime ?? 0
runningTimerViewController.distance = self.myTimer?.pausedDistance ?? 0.0
runningTimerViewController.pace = self.myTimer?.pausedPace ?? 0.0
self.present(runningTimerViewController, animated: true)
}
@objc func modallongPressrestartRunning(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
UIView.animate(withDuration: 0.3, animations: {
self.restartRunningButton.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}) { _ in
self.restartRunning()
}
} else if sender.state == .ended || sender.state == .cancelled {
UIView.animate(withDuration: 0.3, animations: {
self.restartRunningButton.transform = CGAffineTransform.identity
})
}
}
@objc private func stopRunning() {
print("TappedButton - stopRunning()")
let alert = UIAlertController(title: "운동을 완료하시겠습니까?", message: "근처 편의점에서 물 한잔 어떻신가요?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "운동 완료하기", style: .default, handler: { _ in
let startRunningViewController = StartRunningViewController()
startRunningViewController.modalPresentationStyle = .fullScreen
self.present(startRunningViewController, animated: true)
}))
//...
// 원하는 뷰와 레이아웃 설정하기
private func setupModalUI()
private func updateModalUI()
private func setModalLayout()
https://hyesunzzang.tistory.com/257
'Xcode Study' 카테고리의 다른 글
러닝기록 타이머 만들기(3/3)(with dispatchsource ) (0) | 2024.02.27 |
---|---|
Background Tasks (0) | 2024.02.27 |
TIL의 중요성(feat. 능소부능대(能小復能大)) (0) | 2024.02.27 |
러닝기록 타이머 만들기(1/3)(with thread, RunLoop) (0) | 2024.02.26 |
UIPageController(작성중) (0) | 2024.02.22 |