- 알람 앱 (1) - TabBarItem, NavigationBarItem 이미지 추가하기
- 알람 앱 (2) - 스톱워치 구현하기, Timer와 버튼 상태 변화
- 알람 앱 (3) - 스톱워치 테이블뷰에 랩 타임 추가하기
- 알람 앱 (4) - UIEditMenuInteraction과 UIPasteboard
스톱워치 마지막 과정이었던 코어데이터 적용하기까지 아주 늦은 정리를 해보려고 한다..!
역시 미루면 안되는 TIL
우선 다음과 같은 화면에서 현재 시간과 테이블뷰 안의 정보들을 저장해야 했다.
이 화면에 필요한 정보의 전체를 다 저장 하는 엔티티를 만들었다가 관리가 어려워서
방향을 변경해서 지금 현재 타이머의 시간과 랩 테이블 뷰 안의 정보를 담는 엔티티를 따로 만들었다.
코어데이터 모델 생성
StopwatchTimer
메인 타이머의 정보를 담기 위한 엔티티를 먼저 생성했다.
이 엔티티의 속성으로는 시간의 각 라벨의 정보를 담는 minutes, seconds, milliSeconds 세 가지이고 String 타입으로 설정했다.
StopwatchLap
Lap 타임 기록을 저장할 엔티티인 StopwatchLap이다.
이 엔티티에는 테이블뷰의 셀에 보이는 lapNumber, recordNumber, diffTime의 속성을 Int, String 타입으로 설정했다.
메인타이머 CoreData 적용하기
코어데이터의 CRUD를 관리하는 CoreDataManager 클래스를 만들었다.
엔티티를 관리하기 위한 기본적인 설정을 했다.
import CoreData
import UIKit
class StopwatchCoreDataManager {
static let shared = StopwatchCoreDataManager()
static let timerEntity = "StopwatchTimer"
private let context: NSManagedObjectContext? = {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
print("AppDelegate가 초기화되지 않았습니다.")
return nil
}
return appDelegate.persistentContainer.viewContext
}()
다음과 같이 static 키워드를 통한 싱글톤 패턴을 구현하여 전역에서 접근할 수 있게 사용했다.
또한 데이터의 CRUD작업을 위한 AppDelegate에서 persistentContainer의 viewContext를 사용할 수 있게 가져왔다.
이제 코어데이터에 새로운 데이터 저장을 해보자.
// MARK: - Save
func saveTime(minutes: String, seconds: String, milliSeconds: String) {
guard let context = context else { return }
guard let entityDescription = NSEntityDescription.entity(forEntityName: StopwatchCoreDataManager.timerEntity, in: context) else { return }
let newTime = StopwatchTimer(entity: entityDescription, insertInto: context)
newTime.minutes = minutes
newTime.seconds = seconds
newTime.milliSeconds = milliSeconds
do {
print("save time successfully")
try context.save()
} catch {
print("Failed to save time: \(error)")
}
}
위 메서드는 코어데이터의 NSManagedObjectContext와 NSEntityDescription을 사용하여 새로운 StopwatchTimer 엔티티 인스턴스를 생성하고, 이를 저장하는 기능을 한다.
세 개의 문자열 minutes, seconds, milliSeconds를 받아서
위에서 가져온 context를 사용하여 Core Data에 새로운 시간을 저장한다.
다음은 저장한 데이터를 불러오고 읽는 과정이다.
// MARK: - Read
func fetchAllTimes() -> [StopwatchTimer]? {
guard let context = context else { return nil }
let fetchRequest = NSFetchRequest<StopwatchTimer>(entityName: StopwatchCoreDataManager.timerEntity)
do {
let times = try context.fetch(fetchRequest)
return times
} catch {
print("Failed to fetch times: \(error)")
return nil
}
}
entityName 매개변수로 StopwatchCoreDataManager.timerEntity를 사용하여 StopwatchTimer 엔터티를 지정한다.
또한 Core Data의 NSFetchRequest를 사용하여 StopwatchTimer 엔터티의 모든 레코드를 가져오는 기능을 구현한다.
마지막으로 데이터를 삭제하는 기능을 알아보자.
스톱워치 기능 특성 상 전체 삭제만 필요하기에 모든 기록을 삭제하는 메서드만 만들어 사용했다.
// MARK: - Delete
func deleteAllTimes() {
guard let context = context else { return }
let fetchRequest: NSFetchRequest<StopwatchTimer> = StopwatchTimer.fetchRequest()
do {
let times = try context.fetch(fetchRequest)
for time in times {
context.delete(time)
}
try context.save()
print("deleted successfully.")
} catch {
print("Failed to delete all times: \(error)")
}
}
다음 코드는 Core Data의 NSManagedObjectContext와 NSFetchRequest를 사용하여 StopwatchTimer 엔터티의 모든 레코드를 삭제하는 기능을 한다.
context가 유효한지 확인한 후, NSFetchRequest를 생성하여 StopwatchTimer 엔터티의 모든 레코드를 가져온 후 각 레코드를 삭제하고, 변경 사항을 저장한다.
lap 타임 기록을 저장하는 코드도 타이머와 마찬가지로 코어데이터 관련 코드를 작성했다.
아래는 CoreDataManager의 전체 코드이다.
CoreDataManager
import CoreData
import UIKit
class StopwatchCoreDataManager {
static let shared = StopwatchCoreDataManager()
static let timerEntity = "StopwatchTimer"
static let lapEntity = "StopwatchLap"
private let context: NSManagedObjectContext? = {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
print("AppDelegate가 초기화되지 않았습니다.")
return nil
}
return appDelegate.persistentContainer.viewContext
}()
// MARK: - Save
func saveTime(minutes: String, seconds: String, milliSeconds: String) {
guard let context = context else { return }
guard let entityDescription = NSEntityDescription.entity(forEntityName: StopwatchCoreDataManager.timerEntity, in: context) else { return }
let newTime = StopwatchTimer(entity: entityDescription, insertInto: context)
newTime.minutes = minutes
newTime.seconds = seconds
newTime.milliSeconds = milliSeconds
do {
print("save time successfully")
try context.save()
} catch {
print("Failed to save time: \(error)")
}
}
func saveLap(lapNumber: Int64, recordTime: String, diffTime: String) {
guard let context = context else { return }
guard let entityDescription = NSEntityDescription.entity(forEntityName: StopwatchCoreDataManager.lapEntity, in: context) else { return }
let newLap = StopwatchLap(entity: entityDescription, insertInto: context)
newLap.lapNumber = lapNumber
newLap.recordTime = recordTime
newLap.diffTime = diffTime
do {
print("save lap successfully")
try context.save()
} catch {
print("Failed to save lap: \(error)")
}
}
// MARK: - Read
func fetchAllTimes() -> [StopwatchTimer]? {
guard let context = context else { return nil }
let fetchRequest = NSFetchRequest<StopwatchTimer>(entityName: StopwatchCoreDataManager.timerEntity)
do {
let times = try context.fetch(fetchRequest)
return times
} catch {
print("Failed to fetch times: \(error)")
return nil
}
}
func fetchAllLaps() -> [StopwatchLap]? {
guard let context = context else { return nil }
let fetchRequest = NSFetchRequest<StopwatchLap>(entityName: StopwatchCoreDataManager.lapEntity)
do {
let laps = try context.fetch(fetchRequest)
return laps
} catch {
print("Failed to fetch laps: \(error)")
return nil
}
}
// MARK: - Delete
func deleteAllTimes() {
guard let context = context else { return }
let fetchRequest: NSFetchRequest<StopwatchTimer> = StopwatchTimer.fetchRequest()
do {
let times = try context.fetch(fetchRequest)
for time in times {
context.delete(time)
}
try context.save()
print("deleted successfully.")
} catch {
print("Failed to delete all times: \(error)")
}
}
func deleteAllLaps() {
guard let context = context else { return }
let fetchRequest: NSFetchRequest<StopwatchLap> = StopwatchLap.fetchRequest()
do {
let laps = try context.fetch(fetchRequest)
for lap in laps {
context.delete(lap)
}
try context.save()
print("deleted all laps successfully.")
} catch {
print("Failed to delete all laps: \(error)")
}
}
}
코어데이터 사용하기
이제 위에서 구현한 메서드를 호출하여 데이터를 저장하고 가져오고 삭제해보자.
뷰컨트롤러에서 사용되는 CoreData 관련 메서드들을 아래와 같이 정리해서 사용했다.
// MARK: - CoreData
func saveCurrentTime() {
StopwatchCoreDataManager.shared.saveTime(minutes: minutesLabel.text ?? "00", seconds: secondsLabel.text ?? "00", milliSeconds: milliSecondsLabel.text ?? "00")
}
private func loadSavedTimes() {
if let times = StopwatchCoreDataManager.shared.fetchAllTimes(), let lastTime = times.last {
if let minutesString = lastTime.minutes, let secondsString = lastTime.seconds, let millisecondsString = lastTime.milliSeconds {
let minutes = Int(minutesString) ?? 0
let seconds = Int(secondsString) ?? 0
let milliseconds = Int(millisecondsString) ?? 0
// 타이머가 마지막으로 저장된 시간부터 시작하도록 설정
let totalTimeInSeconds = Double(minutes * 60 + seconds) + Double(milliseconds) / 100.0
mainStopwatch.counter = totalTimeInSeconds
updateMainTimer()
} else {
print("Error: Missing time components in the saved time data.")
mainStopwatch.counter = 0.0
}
} else {
mainStopwatch.counter = 0.0
}
// 랩타임 불러오기
if let laps = StopwatchCoreDataManager.shared.fetchAllLaps() {
lapTableViewData = laps.map { $0.recordTime ?? "00:00:00" }
diffTableViewData = laps.map { $0.diffTime ?? "00:00:00" }
}
tableView.reloadData()
}
위 코드는 현재 화면에 표시된 타이머 값을 코어데이터에 저장하는 saveCurrentTime() 와
저장된 타이머의 값을 불러와 화면에 표시하는 loadSavedTimes() 이다.
저장된 값을 바인딩하는 과정도 해주기!
위 메서드를 사용하는 시점을 알아보자.
Stop 버튼을 눌렀을 때 시간의 데이터를 저장하도록 구현했다.
또한 Lap 버튼을 눌렀을 때 랩 타임이 저장되야 하므로 이 시점에 랩 타임 데이터를 저장했다.
// 랩 저장하기
let lapNumber = Int64(lapTableViewData.count)
let recordTime = timerLabelText
let diffTime = self.diffTime
StopwatchCoreDataManager.shared.saveLap(lapNumber: lapNumber, recordTime: recordTime, diffTime: diffTime)
코어데이터는 왜 이렇게 어려운가...
스톱워치는 하안참 전에 이런 과정으로 마무리 되었답니다!
'TIL✏️' 카테고리의 다른 글
[iOS] 라이프 사이클 관리 앱 (1) - 최종 프로젝트 시작, 프로젝트 기획 및 와이어프레임 (0) | 2024.06.04 |
---|---|
[iOS] 알람 앱 (4) - UIEditMenuInteraction과 UIPasteboard (3) | 2024.05.16 |
[iOS] 알람 앱 (3) - 스톱워치 테이블뷰에 랩 타임 추가하기 (3) | 2024.05.16 |
[iOS] 알람 앱 (2) - 스톱워치 구현하기, Timer와 버튼 상태 변화 (2) | 2024.05.14 |
[iOS] 알람 앱 (1) - TabBarItem, NavigationBarItem 이미지 추가하기 (10) | 2024.05.13 |