From b8dd3e254df973d94865e2d24c73a8bc1c47e137 Mon Sep 17 00:00:00 2001 From: ssig33 Date: Fri, 24 Oct 2025 23:30:27 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=A4=E3=83=99=E3=83=B3=E3=83=88=E8=A9=B3?= =?UTF-8?q?=E7=B4=B0=E8=A1=A8=E7=A4=BA=E3=81=A8=E6=97=A5=E4=BB=98=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E6=A4=9C=E7=9F=A5=E3=81=AB=E3=82=88=E3=82=8B=E3=82=AB?= =?UTF-8?q?=E3=83=AC=E3=83=B3=E3=83=80=E3=83=BC=E3=81=AEUX=E5=90=91?= =?UTF-8?q?=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - イベント行にポップアップによる詳細ビュー(EventDetailView)を追加し、イベントの場所・メモ・URLなどをリッチに表示 - ボタン化によりイベントタップ時の操作性を改善 - NotificationCenterでNSCalendarDayChangedに対応し、日付変更時にイベントリストを自動更新するよう拡張 - 日時や区切りの表示、日本語ロケールへの対応などイベント詳細情報のUIを最適化 --- Sources/CalendarService.swift | 13 ++++ Sources/ContentView.swift | 138 ++++++++++++++++++++++++++++------ 2 files changed, 130 insertions(+), 21 deletions(-) diff --git a/Sources/CalendarService.swift b/Sources/CalendarService.swift index 0f5cbf4..4763f4c 100644 --- a/Sources/CalendarService.swift +++ b/Sources/CalendarService.swift @@ -27,6 +27,14 @@ class CalendarService: ObservableObject { ) { [weak self] _ in self?.handleEventStoreChanged() } + + NotificationCenter.default.addObserver( + forName: .NSCalendarDayChanged, + object: nil, + queue: .main + ) { [weak self] _ in + self?.handleDayChanged() + } } @MainActor @@ -35,6 +43,11 @@ class CalendarService: ObservableObject { loadEvents() } + @MainActor + private func handleDayChanged() { + loadEvents() + } + @MainActor func requestAccess() async { do { diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index b1d3a3c..650f2f1 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -126,34 +126,45 @@ struct DayGroup { struct EventRow: View { let event: EKEvent + @State private var showingDetail = false var body: some View { - VStack(alignment: .leading, spacing: 4) { - HStack { - if event.isAllDay { - Text("終日") - .font(.caption) - .foregroundColor(.secondary) - } else { - Text(timeString(from: event.startDate, to: event.endDate)) + Button(action: { + showingDetail = true + }) { + VStack(alignment: .leading, spacing: 4) { + HStack { + if event.isAllDay { + Text("終日") + .font(.caption) + .foregroundColor(.secondary) + } else { + Text(timeString(from: event.startDate, to: event.endDate)) + .font(.caption) + .foregroundColor(.secondary) + } + if let calendar = event.calendar { + Circle() + .fill(Color(calendar.color)) + .frame(width: 8, height: 8) + } + } + Text(event.title ?? "無題のイベント") + .font(.body) + if let location = event.location, !location.isEmpty { + Text(location) .font(.caption) .foregroundColor(.secondary) } - if let calendar = event.calendar { - Circle() - .fill(Color(calendar.color)) - .frame(width: 8, height: 8) - } - } - Text(event.title ?? "無題のイベント") - .font(.body) - if let location = event.location, !location.isEmpty { - Text(location) - .font(.caption) - .foregroundColor(.secondary) } + .padding(.vertical, 4) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .popover(isPresented: $showingDetail) { + EventDetailView(event: event) } - .padding(.vertical, 4) } private func timeString(from start: Date, to end: Date) -> String { @@ -162,3 +173,88 @@ struct EventRow: View { return "\(formatter.string(from: start)) - \(formatter.string(from: end))" } } + +struct EventDetailView: View { + let event: EKEvent + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + HStack { + if let calendar = event.calendar { + Circle() + .fill(Color(calendar.color)) + .frame(width: 12, height: 12) + Text(calendar.title) + .font(.caption) + .foregroundColor(.secondary) + } + } + + Text(event.title ?? "無題のイベント") + .font(.headline) + + Divider() + + VStack(alignment: .leading, spacing: 8) { + if event.isAllDay { + HStack { + Image(systemName: "clock") + .foregroundColor(.secondary) + .frame(width: 20) + Text("終日") + } + } else { + HStack(alignment: .top) { + Image(systemName: "clock") + .foregroundColor(.secondary) + .frame(width: 20) + VStack(alignment: .leading, spacing: 2) { + Text(formatDateTime(event.startDate)) + Text("〜") + .foregroundColor(.secondary) + .font(.caption) + Text(formatDateTime(event.endDate)) + } + } + } + + if let location = event.location, !location.isEmpty { + HStack(alignment: .top) { + Image(systemName: "location") + .foregroundColor(.secondary) + .frame(width: 20) + Text(location) + } + } + + if let notes = event.notes, !notes.isEmpty { + HStack(alignment: .top) { + Image(systemName: "note.text") + .foregroundColor(.secondary) + .frame(width: 20) + Text(notes) + .textSelection(.enabled) + } + } + + if let url = event.url { + HStack(alignment: .top) { + Image(systemName: "link") + .foregroundColor(.secondary) + .frame(width: 20) + Link(url.absoluteString, destination: url) + } + } + } + } + .padding() + .frame(minWidth: 300, maxWidth: 400) + } + + private func formatDateTime(_ date: Date) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "M月d日(E) HH:mm" + formatter.locale = Locale(identifier: "ja_JP") + return formatter.string(from: date) + } +}