Alarm 구조체
struct Alarm: Codable, Equatable, Identifiable {
let id: UUID
var time: String // "오전 7:00" 형태
var subtitle: String // "주중", "월요일마다" 등
var isOn: Bool // 알람 활성화 상태
}
Codable : UserDefaults 저장을 위한 JSON 직렬화
Equatable : 변경사항 감지 및 중복 저장 방지
Identifiable : SwiftUI 호환성 및 고유 식별
Weekday 열거형
enum Weekday: Int, CaseIterable, Hashable {
case mon = 0, tue, wed, thu, fri, sat, sun
var shortKo: String { ["월", "화", "수", "목", "금", "토", "일"][rawValue] }
var rawValueForCalendar: Int { [2, 3, 4, 5, 6, 7, 1][rawValue] }
}
Calendar 매핑 : iOS Calendar는 1=일요일, 2=월요일 사용
한글 지원 : 한국어 요일명 자동 변환
Set 활용 : 선택된 요일들의 집합 연산
WeekdaySelectorView
final class WeekdaySelectorView: UIView {
var selectedDays: Set<Weekday> = [] { didSet { updateAll() } }
var onChange: ((Set<Weekday>) -> Void)?
// 반응형 레이아웃: 화면 크기에 따라 버튼 크기/간격 자동 조정
private func relayoutForWidth(_ fullWidth: CGFloat) {
let available = max(0, fullWidth - horizontalInset*2)
let count = CGFloat(buttons.count)
// 1) 기본 크기로 간격 계산
var buttonSize = preferredSize
var spacing = floor((available - count*buttonSize) / (count - 1))
// 2) 간격이 너무 좁으면 버튼 크기 줄이기
if spacing < minSpacing {
spacing = minSpacing
buttonSize = floor((available - (count - 1)*spacing) / count)
buttonSize = max(minSize, min(preferredSize, buttonSize))
}
}
}
반응형 디자인 : 화면 크기 변화에 따른 자동 레이아웃 조정
didSet : 선택 상태 변경 시 자동 UI 업데이트
Closure 패턴 : 부모 뷰와의 데이터 동기화
Mode 열거형으로 생성/편집 구분
enum Mode {
case create
case edit(Alarm)
}
init(mode: Mode) {
self.mode = mode
super.init(nibName: nil, bundle: nil)
}
Associated Value : 편집 모드일 때 기존 알람 데이터 전달
타입 안전성 : 컴파일 타임에 모드별 데이터 보장
다국어 지원 DateFormatter
private lazy var parserKO: DateFormatter = {
let f = DateFormatter()
f.locale = Locale(identifier: "ko_KR")
f.dateFormat = "a h:mm" // "오전 7:00"
return f
}()
private lazy var parserEN: DateFormatter = {
let f = DateFormatter()
f.locale = Locale(identifier: "en_US_POSIX")
f.dateFormat = "a h:mm" // "AM 7:00"
return f
}()
로케일별 포맷 : 한국어/영어 시간 표기 자동 변환
Lazy Loading : 필요할 때만 초기화하여 메모리 효율성
Fallback 처리 : 한국어 파싱 실패 시 영어로 재시도
선택된 요일 → 표시 텍스트
private func subtitleFromSelectedDays(_ selected: Set<Weekday>) -> String {
let count = selected.count
// 특수 패턴 우선 처리
let weekdays: Set<Weekday> = [.mon, .tue, .wed, .thu, .fri]
let weekend: Set<Weekday> = [.sat, .sun]
if selected == weekdays { return "주중" }
if selected == weekend { return "주말" }
// 개별 요일 처리
if count == 1, let one = selected.first {
return "\(one.shortKo)요일마다"
}
// 여러 요일: "월, 화, 수"
let ordered = Weekday.allCases.filter { selected.contains($0) }
return ordered.map { $0.shortKo }.joined(separator: ", ")
}
Set 연산 : 집합 비교로 특수 패턴 감지
순서 보장 : Weekday.allCases 순서대로 정렬
사용자 친화적 : "주중", "주말" 등 직관적인 표현
UNUserNotificationCenter 권한 요청
private func requestNotificationPermission() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
guard settings.authorizationStatus != .authorized else { return }
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound]
) { _, err in
if let err = err { print("requestAuthorization error: \(err)") }
}
}
}
권한 상태 확인 : 이미 허용된 경우 중복 요청 방지
비동기 처리 : 권한 요청 결과를 콜백으로 처리
에러 핸들링 : 권한 요청 실패 시 적절한 처리
편집 모드에서 변경사항 확인
case let .edit(old):
var updated = old
updated.time = display
let sub = subtitleFromSelectedDays(selectedDays)
if !sub.isEmpty { updated.subtitle = sub }
// 변경사항이 없으면 저장하지 않음
if updated == old {
showNoChangesAlert()
return
}
onSave?(updated)
Equatable 활용 : 구조체 비교로 변경사항 감지
불필요한 저장 방지 : 실제 변경이 있을 때만 저장
사용자 피드백 : 변경사항 없을 때 알림 표시
AlarmManager에서 알림 예약
private func schedule(_ alarm: Alarm) {
guard let tm = parseTimeComponents(from: alarm.time),
let hour = tm.hour, let minute = tm.minute else { return }
let content = UNMutableNotificationContent()
content.body = alarm.subtitle.isEmpty ? "알람" : alarm.subtitle
content.sound = UNNotificationSound(named: .init("radial.caf"))
// 요일별 반복 예약
let weekdays = parseWeekdays(from: alarm.subtitle)
for w in weekdays {
var dc = DateComponents()
dc.weekday = w
dc.hour = hour
dc.minute = minute
let trigger = UNCalendarNotificationTrigger(dateMatching: dc, repeats: true)
let req = UNNotificationRequest(identifier: "\(base).weekday.\(w)", content: content, trigger: trigger)
center.add(req)
}
}
시간 파싱 : "오전 7:00" → DateComponents 변환
요일별 반복 : UNCalendarNotificationTrigger로 주간 반복
고유 식별자 : 알람별로 고유한 알림 ID 생성