기존 버튼 방식에서 PickerView로 전환하여 사용성을 크게 개선했다.
private let timePickerView = UIPickerView()
private var hourArray: [Int] = Array(0...23)
private var minuteArray: [Int] = Array(0...59)
private var secondArray: [Int] = Array(0...59)
// PickerView 바인딩
viewModel.$hours
.combineLatest(viewModel.$minutes, viewModel.$seconds)
.receive(on: DispatchQueue.main)
.sink { [weak self] hours, minutes, seconds in
guard let self = self else { return }
// 범위 체크 추가
if hours < self.hourArray.count {
self.timePickerView.selectRow(hours, inComponent: 0, animated: false)
}
if minutes < self.minuteArray.count {
self.timePickerView.selectRow(minutes, inComponent: 1, animated: false)
}
if seconds < self.secondArray.count {
self.timePickerView.selectRow(seconds, inComponent: 2, animated: false)
}
}
.store(in: &cancellables)
extension TimerView: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 3 // 시, 분, 초
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch component {
case 0: return hourArray.count
case 1: return minuteArray.count
case 2: return secondArray.count
default: return 0
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
let value: Int
let suffix: String
switch component {
case 0:
guard row < hourArray.count else { return nil }
value = hourArray[row]
suffix = "시간"
case 1:
guard row < minuteArray.count else { return nil }
value = minuteArray[row]
suffix = "분"
case 2:
guard row < secondArray.count else { return nil }
value = secondArray[row]
suffix = "초"
default:
return nil
}
return "\(value)\(suffix)"
}
}
스와이프 삭제 기능이 추가되었다:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "삭제") { [weak self] (_, _, completion) in
self?.viewModel.deleteTimer(at: indexPath.row)
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction])
}
func deleteTimer(at index: Int) {
// CoreData와 로컬 데이터 동기화
let timers = coreDataManager.fetchTimers()
guard index < timers.count else { return }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.coreDataManager.deleteTimer(timers[index])
self.timerItems.remove(at: index)
}
}
func deleteTimer(_ timer: TimerItem) {
context.delete(timer)
try? context.save() // 즉시 저장
}
SnapKit을 활용한 제약조건 설정:
private func setupConstraints() {
timePickerView.snp.makeConstraints { make in
make.top.equalTo(timerLabel.snp.bottom).offset(20)
make.centerX.equalToSuperview()
make.width.equalToSuperview()
make.height.equalTo(200)
}
labelContainerView.snp.makeConstraints { make in
make.top.equalTo(timePickerView.snp.bottom).offset(20)
make.leading.trailing.equalToSuperview().inset(20)
make.height.equalTo(40)
}
// ... 기타 제약조건
}