๐Ÿ“… EventKit ์™„์ „์ •๋ณต

์บ˜๋ฆฐ๋”์™€ ๋ฆฌ๋งˆ์ธ๋”๋ฅผ ์•ฑ์— ํ†ตํ•ฉ! EventKit์œผ๋กœ ์ผ์ • ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

โœจ EventKit์ด๋ž€?

EventKit์€ iOS์˜ ์บ˜๋ฆฐ๋” ๋ฐ ๋ฆฌ๋งˆ์ธ๋” ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ๋ถ€ํ„ฐ ์•Œ๋žŒ ์„ค์ •, ๋ฐ˜๋ณต ์ผ์ •๊นŒ์ง€ ๋ชจ๋“  ์บ˜๋ฆฐ๋” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. iCloud์™€ ์ž๋™ ๋™๊ธฐํ™”๋˜์–ด ๋ชจ๋“  ๊ธฐ๊ธฐ์—์„œ ์ผ๊ด€๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ฆ ์ฃผ์š” ๊ธฐ๋Šฅ

๊ธฐ๋Šฅ ๊ฐœ์š”
โœ… ์บ˜๋ฆฐ๋” ์ด๋ฒคํŠธ ์ƒ์„ฑ/์ˆ˜์ •/์‚ญ์ œ
โœ… ๋ฆฌ๋งˆ์ธ๋” ์ƒ์„ฑ/์ˆ˜์ •/์‚ญ์ œ
โœ… ์•Œ๋žŒ ์„ค์ • (์‹œ๊ฐ„ ๊ธฐ๋ฐ˜, ์œ„์น˜ ๊ธฐ๋ฐ˜)
โœ… ๋ฐ˜๋ณต ์ผ์ • (๋งค์ผ, ๋งค์ฃผ, ๋งค์›”...)
โœ… ์บ˜๋ฆฐ๋” ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ๋ง
โœ… ์ฐธ์„์ž ๊ด€๋ฆฌ
โœ… iCloud ์ž๋™ ๋™๊ธฐํ™”

๐Ÿ” ๊ถŒํ•œ ์š”์ฒญ

PermissionManager.swift
import EventKit

class EventKitManager {
    static let shared = EventKitManager()
    let eventStore = EKEventStore()

    // ์บ˜๋ฆฐ๋” ๊ถŒํ•œ ์š”์ฒญ
    func requestCalendarAccess() async -> Bool {
        do {
            return try await eventStore.requestFullAccessToEvents()
        } catch {
            print("Calendar access error: \(error)")
            return false
        }
    }

    // ๋ฆฌ๋งˆ์ธ๋” ๊ถŒํ•œ ์š”์ฒญ
    func requestReminderAccess() async -> Bool {
        do {
            return try await eventStore.requestFullAccessToReminders()
        } catch {
            print("Reminder access error: \(error)")
            return false
        }
    }

    // ๊ถŒํ•œ ์ƒํƒœ ํ™•์ธ
    func checkAuthorizationStatus() -> EKAuthorizationStatus {
        return EKEventStore.authorizationStatus(for: .event)
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let hasAccess = await EventKitManager.shared.requestCalendarAccess()
if hasAccess {
    print("์บ˜๋ฆฐ๋” ์ ‘๊ทผ ํ—ˆ์šฉ๋จ")
}

๐Ÿ“† ์ด๋ฒคํŠธ ์ƒ์„ฑํ•˜๊ธฐ

CreateEvent.swift
import EventKit

extension EventKitManager {
    // ๊ธฐ๋ณธ ์ด๋ฒคํŠธ ์ƒ์„ฑ
    func createEvent(
        title: String,
        startDate: Date,
        endDate: Date,
        notes: String? = nil,
        location: String? = nil
    ) throws -> String {
        // ์ƒˆ ์ด๋ฒคํŠธ ์ƒ์„ฑ
        let event = EKEvent(eventStore: eventStore)
        event.title = title
        event.startDate = startDate
        event.endDate = endDate
        event.notes = notes
        event.location = location

        // ๊ธฐ๋ณธ ์บ˜๋ฆฐ๋”์— ์ €์žฅ
        event.calendar = eventStore.defaultCalendarForNewEvents

        // ์ €์žฅ
        try eventStore.save(event, span: .thisEvent)

        return event.eventIdentifier
    }

    // ์•Œ๋žŒ ํฌํ•จ ์ด๋ฒคํŠธ
    func createEventWithAlarm(
        title: String,
        startDate: Date,
        endDate: Date,
        alarmMinutesBefore: Int
    ) throws -> String {
        let event = EKEvent(eventStore: eventStore)
        event.title = title
        event.startDate = startDate
        event.endDate = endDate
        event.calendar = eventStore.defaultCalendarForNewEvents

        // ์•Œ๋žŒ ์ถ”๊ฐ€ (10๋ถ„ ์ „)
        let alarm = EKAlarm(relativeOffset: TimeInterval(-alarmMinutesBefore * 60))
        event.addAlarm(alarm)

        try eventStore.save(event, span: .thisEvent)

        return event.eventIdentifier
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date())!
let oneHourLater = Calendar.current.date(byAdding: .hour, value: 1, to: tomorrow)!

let eventID = try EventKitManager.shared.createEvent(
    title: "ํŒ€ ๋ฏธํŒ…",
    startDate: tomorrow,
    endDate: oneHourLater,
    notes: "Q1 ๊ฒฐ๊ณผ ๋ฆฌ๋ทฐ",
    location: "ํšŒ์˜์‹ค A"
)

print("์ด๋ฒคํŠธ ์ƒ์„ฑ๋จ: \(eventID)")

๐Ÿ” ๋ฐ˜๋ณต ์ด๋ฒคํŠธ ์ƒ์„ฑ

RecurringEvent.swift
import EventKit

extension EventKitManager {
    // ๋งค์ฃผ ๋ฐ˜๋ณต ์ด๋ฒคํŠธ
    func createWeeklyRecurringEvent(
        title: String,
        startDate: Date,
        endDate: Date
    ) throws {
        let event = EKEvent(eventStore: eventStore)
        event.title = title
        event.startDate = startDate
        event.endDate = endDate
        event.calendar = eventStore.defaultCalendarForNewEvents

        // ๋ฐ˜๋ณต ๊ทœ์น™ ์„ค์ • (๋งค์ฃผ ๋ฐ˜๋ณต)
        let recurrenceRule = EKRecurrenceRule(
            recurrenceWith: .weekly,
            interval: 1,
            end: nil  // ๋ฌดํ•œ ๋ฐ˜๋ณต
        )

        event.addRecurrenceRule(recurrenceRule)

        try eventStore.save(event, span: .thisEvent)
    }

    // ๋งค์ผ ๋ฐ˜๋ณต (10ํšŒ)
    func createDailyRecurringEvent(
        title: String,
        startDate: Date,
        endDate: Date,
        occurrenceCount: Int
    ) throws {
        let event = EKEvent(eventStore: eventStore)
        event.title = title
        event.startDate = startDate
        event.endDate = endDate
        event.calendar = eventStore.defaultCalendarForNewEvents

        // 10ํšŒ ๋ฐ˜๋ณต ํ›„ ์ข…๋ฃŒ
        let recurrenceEnd = EKRecurrenceEnd(occurrenceCount: occurrenceCount)
        let recurrenceRule = EKRecurrenceRule(
            recurrenceWith: .daily,
            interval: 1,
            end: recurrenceEnd
        )

        event.addRecurrenceRule(recurrenceRule)

        try eventStore.save(event, span: .thisEvent)
    }

    // ํŠน์ • ์š”์ผ๋งŒ ๋ฐ˜๋ณต (์›”, ์ˆ˜, ๊ธˆ)
    func createCustomRecurringEvent(
        title: String,
        startDate: Date,
        endDate: Date
    ) throws {
        let event = EKEvent(eventStore: eventStore)
        event.title = title
        event.startDate = startDate
        event.endDate = endDate
        event.calendar = eventStore.defaultCalendarForNewEvents

        // ์›”, ์ˆ˜, ๊ธˆ ๋ฐ˜๋ณต
        let daysOfWeek = [
            EKRecurrenceDayOfWeek(.monday),
            EKRecurrenceDayOfWeek(.wednesday),
            EKRecurrenceDayOfWeek(.friday)
        ]

        let recurrenceRule = EKRecurrenceRule(
            recurrenceWith: .weekly,
            interval: 1,
            daysOfTheWeek: daysOfWeek,
            daysOfTheMonth: nil,
            monthsOfTheYear: nil,
            weeksOfTheYear: nil,
            daysOfTheYear: nil,
            setPositions: nil,
            end: nil
        )

        event.addRecurrenceRule(recurrenceRule)

        try eventStore.save(event, span: .thisEvent)
    }
}

๐Ÿ” ์ด๋ฒคํŠธ ๊ฒ€์ƒ‰ํ•˜๊ธฐ

SearchEvents.swift
import EventKit

extension EventKitManager {
    // ํŠน์ • ๊ธฐ๊ฐ„์˜ ์ด๋ฒคํŠธ ๊ฒ€์ƒ‰
    func fetchEvents(from startDate: Date, to endDate: Date) -> [EKEvent] {
        let calendars = eventStore.calendars(for: .event)

        // Predicate ์ƒ์„ฑ
        let predicate = eventStore.predicateForEvents(
            withStart: startDate,
            end: endDate,
            calendars: calendars
        )

        // ์ด๋ฒคํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
        let events = eventStore.events(matching: predicate)
        return events
    }

    // ์˜ค๋Š˜์˜ ์ด๋ฒคํŠธ
    func fetchTodayEvents() -> [EKEvent] {
        let calendar = Calendar.current
        let startOfDay = calendar.startOfDay(for: Date())
        let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!

        return fetchEvents(from: startOfDay, to: endOfDay)
    }

    // ์ด๋ฒˆ ์ฃผ ์ด๋ฒคํŠธ
    func fetchThisWeekEvents() -> [EKEvent] {
        let calendar = Calendar.current
        let today = Date()

        guard let weekStart = calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: today)),
              let weekEnd = calendar.date(byAdding: .weekOfYear, value: 1, to: weekStart) else {
            return []
        }

        return fetchEvents(from: weekStart, to: weekEnd)
    }

    // ํŠน์ • ์บ˜๋ฆฐ๋”์˜ ์ด๋ฒคํŠธ๋งŒ
    func fetchEventsFromCalendar(calendarID: String, from: Date, to: Date) -> [EKEvent] {
        guard let calendar = eventStore.calendar(withIdentifier: calendarID) else {
            return []
        }

        let predicate = eventStore.predicateForEvents(
            withStart: from,
            end: to,
            calendars: [calendar]
        )

        return eventStore.events(matching: predicate)
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let todayEvents = EventKitManager.shared.fetchTodayEvents()
for event in todayEvents {
    print("\(event.title ?? "์ œ๋ชฉ ์—†์Œ") - \(event.startDate ?? Date())")
}

โœ๏ธ ์ด๋ฒคํŠธ ์ˆ˜์ • ๋ฐ ์‚ญ์ œ

ModifyEvent.swift
import EventKit

extension EventKitManager {
    // ์ด๋ฒคํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
    func getEvent(identifier: String) -> EKEvent? {
        return eventStore.event(withIdentifier: identifier)
    }

    // ์ด๋ฒคํŠธ ์ˆ˜์ •
    func updateEvent(identifier: String, newTitle: String) throws {
        guard let event = getEvent(identifier: identifier) else {
            throw NSError(domain: "Event not found", code: 404)
        }

        event.title = newTitle
        try eventStore.save(event, span: .thisEvent)
    }

    // ์ด๋ฒคํŠธ ์‚ญ์ œ
    func deleteEvent(identifier: String) throws {
        guard let event = getEvent(identifier: identifier) else {
            throw NSError(domain: "Event not found", code: 404)
        }

        try eventStore.remove(event, span: .thisEvent)
    }

    // ๋ฐ˜๋ณต ์ด๋ฒคํŠธ ์‚ญ์ œ (๋ชจ๋“  ๋ฐ˜๋ณต ํฌํ•จ)
    func deleteRecurringEvent(identifier: String) throws {
        guard let event = getEvent(identifier: identifier) else {
            throw NSError(domain: "Event not found", code: 404)
        }

        // .futureEvents: ์ด ์ด๋ฒคํŠธ์™€ ๋ฏธ๋ž˜ ๋ฐ˜๋ณต ๋ชจ๋‘ ์‚ญ์ œ
        try eventStore.remove(event, span: .futureEvents)
    }
}

๐Ÿ“ฑ SwiftUI ํ†ตํ•ฉ

CalendarView.swift
import SwiftUI
import EventKit

struct CalendarView: View {
    @State private var events: [EKEvent] = []
    @State private var hasAccess = false
    @State private var showingAddEvent = false

    let manager = EventKitManager.shared

    var body: some View {
        NavigationStack {
            Group {
                if hasAccess {
                    List {
                        ForEach(events, id: \.eventIdentifier) { event in
                            VStack(alignment: .leading, spacing: 4) {
                                Text(event.title ?? "์ œ๋ชฉ ์—†์Œ")
                                    .font(.headline)

                                HStack {
                                    Image(systemName: "clock")
                                    Text(event.startDate ?? Date(), style: .time)
                                }
                                .font(.caption)
                                .foregroundStyle(.secondary)

                                if let location = event.location {
                                    HStack {
                                        Image(systemName: "location")
                                        Text(location)
                                    }
                                    .font(.caption)
                                    .foregroundStyle(.secondary)
                                }
                            }
                            .padding(.vertical, 4)
                        }
                        .onDelete(deleteEvent)
                    }
                } else {
                    VStack(spacing: 20) {
                        Image(systemName: "calendar.badge.exclamationmark")
                            .font(.system(size: 60))
                            .foregroundStyle(.gray)

                        Text("์บ˜๋ฆฐ๋” ์ ‘๊ทผ ๊ถŒํ•œ ํ•„์š”")
                            .font(.title2)
                            .bold()

                        Button("๊ถŒํ•œ ์š”์ฒญ") {
                            Task {
                                await requestAccess()
                            }
                        }
                        .buttonStyle(.borderedProminent)
                    }
                }
            }
            .navigationTitle("์˜ค๋Š˜์˜ ์ผ์ •")
            .toolbar {
                ToolbarItem(placement: .primaryAction) {
                    Button(action: { showingAddEvent = true }) {
                        Image(systemName: "plus")
                    }
                }
            }
            .sheet(isPresented: $showingAddEvent) {
                AddEventView()
            }
        }
        .task {
            await requestAccess()
        }
    }

    func requestAccess() async {
        hasAccess = await manager.requestCalendarAccess()
        if hasAccess {
            loadEvents()
        }
    }

    func loadEvents() {
        events = manager.fetchTodayEvents()
    }

    func deleteEvent(at offsets: IndexSet) {
        for index in offsets {
            let event = events[index]
            try? manager.deleteEvent(identifier: event.eventIdentifier)
        }
        loadEvents()
    }
}

// ์ด๋ฒคํŠธ ์ถ”๊ฐ€ ํ™”๋ฉด
struct AddEventView: View {
    @Environment(\.dismiss) var dismiss

    @State private var title = ""
    @State private var startDate = Date()
    @State private var endDate = Date().addingTimeInterval(3600)
    @State private var location = ""
    @State private var notes = ""

    var body: some View {
        NavigationStack {
            Form {
                Section("๊ธฐ๋ณธ ์ •๋ณด") {
                    TextField("์ œ๋ชฉ", text: $title)
                    TextField("์œ„์น˜", text: $location)
                }

                Section("์‹œ๊ฐ„") {
                    DatePicker("์‹œ์ž‘", selection: $startDate)
                    DatePicker("์ข…๋ฃŒ", selection: $endDate)
                }

                Section("๋ฉ”๋ชจ") {
                    TextEditor(text: $notes)
                        .frame(height: 100)
                }
            }
            .navigationTitle("์ƒˆ ์ด๋ฒคํŠธ")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("์ทจ์†Œ") { dismiss() }
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("์ €์žฅ") {
                        saveEvent()
                    }
                    .disabled(title.isEmpty)
                }
            }
        }
    }

    func saveEvent() {
        do {
            _ = try EventKitManager.shared.createEvent(
                title: title,
                startDate: startDate,
                endDate: endDate,
                notes: notes.isEmpty ? nil : notes,
                location: location.isEmpty ? nil : location
            )
            dismiss()
        } catch {
            print("์ด๋ฒคํŠธ ์ €์žฅ ์‹คํŒจ: \(error)")
        }
    }
}

โœ… ๋ฆฌ๋งˆ์ธ๋” (Reminders)

Reminders.swift
import EventKit

extension EventKitManager {
    // ๋ฆฌ๋งˆ์ธ๋” ์ƒ์„ฑ
    func createReminder(
        title: String,
        dueDate: Date? = nil,
        priority: Int = 0
    ) throws -> String {
        let reminder = EKReminder(eventStore: eventStore)
        reminder.title = title
        reminder.calendar = eventStore.defaultCalendarForNewReminders()

        // ๊ธฐํ•œ ์„ค์ •
        if let dueDate = dueDate {
            reminder.dueDateComponents = Calendar.current.dateComponents(
                [.year, .month, .day, .hour, .minute],
                from: dueDate
            )
        }

        // ์šฐ์„ ์ˆœ์œ„ (0: ์—†์Œ, 1-4: ๋†’์Œ, 5: ๋ณดํ†ต, 6-9: ๋‚ฎ์Œ)
        reminder.priority = priority

        try eventStore.save(reminder, commit: true)

        return reminder.calendarItemIdentifier
    }

    // ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ๋ฆฌ๋งˆ์ธ๋” ๊ฐ€์ ธ์˜ค๊ธฐ
    func fetchIncompleteReminders() async -> [EKReminder] {
        let predicate = eventStore.predicateForIncompleteReminders(
            withDueDateStarting: nil,
            ending: nil,
            calendars: nil
        )

        return await withCheckedContinuation { continuation in
            eventStore.fetchReminders(matching: predicate) { reminders in
                continuation.resume(returning: reminders ?? [])
            }
        }
    }

    // ๋ฆฌ๋งˆ์ธ๋” ์™„๋ฃŒ ํ‘œ์‹œ
    func completeReminder(identifier: String) throws {
        guard let reminder = eventStore.calendarItem(withIdentifier: identifier) as? EKReminder else {
            throw NSError(domain: "Reminder not found", code: 404)
        }

        reminder.isCompleted = true
        reminder.completionDate = Date()

        try eventStore.save(reminder, commit: true)
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date())!
let reminderID = try EventKitManager.shared.createReminder(
    title: "์šฐ์œ  ์‚ฌ๊ธฐ",
    dueDate: tomorrow,
    priority: 1
)

// ๋ฏธ์™„๋ฃŒ ๋ฆฌ๋งˆ์ธ๋” ๊ฐ€์ ธ์˜ค๊ธฐ
let reminders = await EventKitManager.shared.fetchIncompleteReminders()
print("ํ•  ์ผ: \(reminders.count)๊ฐœ")

๐Ÿ”” ์œ„์น˜ ๊ธฐ๋ฐ˜ ์•Œ๋žŒ

LocationAlarm.swift
import EventKit
import CoreLocation

extension EventKitManager {
    // ์œ„์น˜ ๊ธฐ๋ฐ˜ ๋ฆฌ๋งˆ์ธ๋”
    func createLocationBasedReminder(
        title: String,
        latitude: Double,
        longitude: Double,
        radius: Double = 100  // ๋ฏธํ„ฐ
    ) throws {
        let reminder = EKReminder(eventStore: eventStore)
        reminder.title = title
        reminder.calendar = eventStore.defaultCalendarForNewReminders()

        // ์œ„์น˜ ์•Œ๋žŒ ์ƒ์„ฑ
        let location = CLLocation(latitude: latitude, longitude: longitude)
        let structuredLocation = EKStructuredLocation(title: "์•Œ๋ฆผ ์œ„์น˜")
        structuredLocation.geoLocation = location
        structuredLocation.radius = radius

        // ๋„์ฐฉ/์ถœ๋ฐœ ์•Œ๋ฆผ
        let alarm = EKAlarm()
        alarm.structuredLocation = structuredLocation
        alarm.proximity = .enter  // .enter (๋„์ฐฉ), .leave (์ถœ๋ฐœ)

        reminder.addAlarm(alarm)

        try eventStore.save(reminder, commit: true)
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ (์ง‘ ๊ทผ์ฒ˜ ๋„์ฐฉ ์‹œ ์•Œ๋ฆผ)
try EventKitManager.shared.createLocationBasedReminder(
    title: "ํ˜„๊ด€๋ฌธ ์—ด์‡  ํ™•์ธํ•˜๊ธฐ",
    latitude: 37.5665,
    longitude: 126.9780,
    radius: 200
)

๐Ÿ’ก HIG ๊ฐ€์ด๋“œ๋ผ์ธ

HIG ๊ถŒ์žฅ์‚ฌํ•ญ
โœ… DO
1. ๊ถŒํ•œ ์š”์ฒญ ์ „ ์ด์œ  ์„ค๋ช…
   - "์ผ์ •์„ ์บ˜๋ฆฐ๋”์— ์ €์žฅํ•˜๋ ค๋ฉด ์ ‘๊ทผ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค"

2. ๊ธฐ๋ณธ ์บ˜๋ฆฐ๋” ์‚ฌ์šฉ
   - defaultCalendarForNewEvents ์‚ฌ์šฉ
   - ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ๊ธฐ๋ณธ ์บ˜๋ฆฐ๋” ์กด์ค‘

3. ๋ช…ํ™•ํ•œ ์ด๋ฒคํŠธ ์ •๋ณด
   - ์ œ๋ชฉ, ์‹œ๊ฐ„, ์œ„์น˜๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ
   - notes์— ์ถ”๊ฐ€ ์ •๋ณด ์ œ๊ณต

4. ๋ฐ˜๋ณต ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ฃผ์˜
   - .thisEvent vs .futureEvents ๊ตฌ๋ถ„
   - ์‚ฌ์šฉ์ž์—๊ฒŒ ์„ ํƒ ์˜ต์…˜ ์ œ๊ณต

โŒ DON'T
1. ๊ถŒํ•œ ์—†์ด ์บ˜๋ฆฐ๋” ์ ‘๊ทผ ์‹œ๋„
2. ์‚ฌ์šฉ์ž ๋™์˜ ์—†์ด ์ด๋ฒคํŠธ ์ƒ์„ฑ
3. ๊ธฐ๋ณธ ์บ˜๋ฆฐ๋” ๋ฌด์‹œํ•˜๊ณ  ์ž„์˜ ์บ˜๋ฆฐ๋”์— ์ €์žฅ
4. ์•Œ๋ฆผ ์—†์ด ๊ธฐ์กด ์ด๋ฒคํŠธ ์‚ญ์ œ

๐Ÿ”ง ์‹ค๋ฌด ํ™œ์šฉ ํŒ

์‹ค์ „ ํŒจํ„ด
import EventKit

// 1. ์บ˜๋ฆฐ๋” ๋ณ€๊ฒฝ ๊ฐ์ง€
class CalendarObserver {
    let eventStore = EKEventStore()

    func observeChanges() {
        NotificationCenter.default.addObserver(
            forName: .EKEventStoreChanged,
            object: eventStore,
            queue: .main
        ) { _ in
            print("์บ˜๋ฆฐ๋” ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ๋จ")
            // UI ์—…๋ฐ์ดํŠธ
        }
    }
}

// 2. ์ฐธ์„์ž ์ถ”๊ฐ€
func createEventWithAttendees(
    title: String,
    startDate: Date,
    endDate: Date,
    attendeeEmails: [String]
) throws {
    let event = EKEvent(eventStore: EventKitManager.shared.eventStore)
    event.title = title
    event.startDate = startDate
    event.endDate = endDate

    // ์ฐธ์„์ž๋Š” iOS์—์„œ ์ง์ ‘ ์ถ”๊ฐ€ ๋ถˆ๊ฐ€ (์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅ)
    // Exchange ์„œ๋ฒ„๋‚˜ iCloud๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๊ฐ€๋Šฅ

    try EventKitManager.shared.eventStore.save(event, span: .thisEvent)
}

// 3. ์บ˜๋ฆฐ๋” ์ƒ‰์ƒ ๊ฐ€์ ธ์˜ค๊ธฐ
func getCalendarColor(event: EKEvent) -> UIColor {
    return UIColor(cgColor: event.calendar.cgColor)
}

๐Ÿ” Info.plist ์„ค์ •

Info.plist
<!-- ์บ˜๋ฆฐ๋” ๊ถŒํ•œ -->
<key>NSCalendarsUsageDescription</key>
<string>์•ฑ์—์„œ ์ผ์ •์„ ์บ˜๋ฆฐ๋”์— ์ €์žฅํ•˜๋ ค๋ฉด ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค</string>

<!-- ๋ฆฌ๋งˆ์ธ๋” ๊ถŒํ•œ -->
<key>NSRemindersUsageDescription</key>
<string>ํ•  ์ผ์„ ๋ฆฌ๋งˆ์ธ๋”์— ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค</string>

๐Ÿ’ก EventKit ํ•ต์‹ฌ
โœ… ์‹œ์Šคํ…œ ์บ˜๋ฆฐ๋”/๋ฆฌ๋งˆ์ธ๋” ์™„๋ฒฝ ํ†ตํ•ฉ
โœ… iCloud ์ž๋™ ๋™๊ธฐํ™”
โœ… ๋ฐ˜๋ณต ์ผ์ • ์ง€์›
โœ… ์œ„์น˜ ๊ธฐ๋ฐ˜ ์•Œ๋žŒ
โœ… ๋ชจ๋“  ์บ˜๋ฆฐ๋” ์•ฑ๊ณผ ํ˜ธํ™˜

๐Ÿ“ฆ ํ•™์Šต ์ž๋ฃŒ

๐Ÿ’ป
GitHub ํ”„๋กœ์ ํŠธ
๐Ÿ“–
Apple ๊ณต์‹ ๋ฌธ์„œ
๐ŸŽจ
HIG ๊ฐœ์ธ์ •๋ณด ๊ฐ€์ด๋“œ