AlarmApp

hyun·2025년 8월 13일
0

iOS

목록 보기
39/54

1. 데이터 모델 설계

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 호환성 및 고유 식별

2. 요일 선택 시스템

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 활용 : 선택된 요일들의 집합 연산

3. 요일 선택 UI 컴포넌트

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 패턴 : 부모 뷰와의 데이터 동기화

4. 알람 편집 화면

Mode 열거형으로 생성/편집 구분

enum Mode { 
  case create
  case edit(Alarm) 
}

init(mode: Mode) {
  self.mode = mode
  super.init(nibName: nil, bundle: nil)
}

Associated Value : 편집 모드일 때 기존 알람 데이터 전달
타입 안전성 : 컴파일 타임에 모드별 데이터 보장

5. 날짜/시간 파싱 시스템

다국어 지원 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 처리 : 한국어 파싱 실패 시 영어로 재시도

6. 요일 문자열 변환 로직

선택된 요일 → 표시 텍스트

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 순서대로 정렬
사용자 친화적 : "주중", "주말" 등 직관적인 표현

7. 알림 권한 관리

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)") }
    }
  }
}

권한 상태 확인 : 이미 허용된 경우 중복 요청 방지
비동기 처리 : 권한 요청 결과를 콜백으로 처리
에러 핸들링 : 권한 요청 실패 시 적절한 처리

8. 변경사항 감지 및 저장

편집 모드에서 변경사항 확인

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 활용 : 구조체 비교로 변경사항 감지
불필요한 저장 방지 : 실제 변경이 있을 때만 저장
사용자 피드백 : 변경사항 없을 때 알림 표시

9. 로컬 알림 스케줄링

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 생성

0개의 댓글