iOS 17拒绝日历权限后仍显示EventKit事件编辑视图的问题解决
iOS 17中拒绝日历权限后仍显示EKEventEditViewController的问题修复
问题概述
在SwiftUI中基于EventKit开发添加日历功能时,点击"Add to Calendar"按钮会检查日历授权状态,未授权则发起权限请求。但用户拒绝权限后,iOS 16.4不会显示事件创建视图,iOS 17.0却依然弹出该视图,需要实现权限拒绝后自动关闭sheet,避免无效视图展示。
问题根源
当前代码的核心问题在于权限检查时机滞后:
EventEditView的makeUIViewController方法中,先创建并返回了EKEventEditViewController,之后才异步执行权限检查。这导致iOS 17中视图已被呈现,后续的权限拒绝结果无法及时撤销视图展示。- 权限通过后才初始化
eventStore,但返回控制器时已将未初始化的eventStore赋值给控制器,不同系统版本对这种情况的处理逻辑不一致,引发兼容性问题。
解决方案
最优方案是提前在按钮点击时完成权限检查,仅在权限允许(或用户同意授权)时才弹出sheet;权限拒绝时直接提示用户去设置开启权限,避免无效的sheet弹出。
修改后的完整代码
import SwiftUI import Foundation import EventKitUI import UIKit struct ContentView: View { @State private var openEventView: Bool = false @State private var showPermissionAlert: Bool = false var body: some View { NavigationStack { VStack { Button { Task { let canAccessCalendar = await checkCalendarAuthorization() if canAccessCalendar { openEventView = true } else { showPermissionAlert = true } } } label: { HStack { Spacer() Image(systemName: "calendar.badge.plus") .font(.body.bold()) .foregroundColor(.blue) .padding(.trailing, 5.0) Text("Add to Calendar") .font(.body.bold()) .foregroundColor(.blue) Spacer() } .frame(maxWidth: .infinity) .contentShape(Rectangle()) } .frame(width: UIScreen.main.bounds.width - 40.0, height: 44.0) .background(Color.blue.opacity(0.5)) .cornerRadius(10.0) .contentShape(Rectangle()) .listRowBackground(EmptyView()) .listRowInsets(EdgeInsets()) } .sheet(isPresented: $openEventView) { EventEditView() } .alert("需要日历权限", isPresented: $showPermissionAlert) { Button("取消", role: .cancel) {} Button("去设置") { guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } UIApplication.shared.open(settingsURL) } } message: { Text("请在设置中开启日历权限,以便添加事件到日历") } } } /// 检查日历授权状态 private func checkCalendarAuthorization() async -> Bool { let status = EKEventStore.authorizationStatus(for: .event) switch status { case .notDetermined: return await requestCalendarAccess() case .authorized, .fullAccess: return true case .restricted, .denied: return false default: return false } } /// 请求日历权限 private func requestCalendarAccess() async -> Bool { let eventStore = EKEventStore() do { if #available(iOS 17.0, *) { return try await eventStore.requestFullAccessToEvents() } else { return try await eventStore.requestAccess(to: .event) } } catch { return false } } } struct EventEditView: UIViewControllerRepresentable { func makeCoordinator() -> Coordinator { Coordinator(self) } @Environment(\.presentationMode) var presentationMode private let eventStore = EKEventStore() private let event: EKEvent? = nil func makeUIViewController(context: Context) -> EKEventEditViewController { let eventEditVC = EKEventEditViewController() eventEditVC.eventStore = eventStore eventEditVC.event = event eventEditVC.editViewDelegate = context.coordinator return eventEditVC } func updateUIViewController(_ uiViewController: EKEventEditViewController, context: Context) {} class Coordinator: NSObject, EKEventEditViewDelegate { let parent: EventEditView init(_ parent: EventEditView) { self.parent = parent } func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) { parent.presentationMode.wrappedValue.dismiss() if action != .canceled { // 处理事件保存/更新后的逻辑 } } } }
关键修改点
- 权限前置检查:将权限检查逻辑移到
ContentView的按钮点击事件中,异步执行后根据结果决定是否打开sheet,从根源避免无效视图弹出。 - 用户体验优化:权限拒绝时弹出提示弹窗,引导用户前往系统设置开启权限。
- 简化
EventEditView:由于已提前确保权限状态,EventEditView无需再做权限检查,直接初始化eventStore即可,代码更简洁。
备选方案(在EventEditView内处理)
如果必须在EventEditView中处理权限逻辑,可在权限检查返回拒绝时主动关闭sheet,需注意在主线程执行关闭操作:
func checkCalendarAuthorizationStatus() async -> Bool { let status = EKEventStore.authorizationStatus(for: .event) switch status { case .notDetermined: let granted = await requestAccessToCalendar() if !granted { DispatchQueue.main.async { self.presentationMode.wrappedValue.dismiss() } } return granted case .authorized, .fullAccess: if !eventStoreInitAfterAuth { eventStore = EKEventStore() } eventStoreInitAfterAuth = true return true case .restricted, .denied: DispatchQueue.main.async { self.presentationMode.wrappedValue.dismiss() } return false default: DispatchQueue.main.async { self.presentationMode.wrappedValue.dismiss() } return false } }
此方案可能会导致sheet短暂弹出后关闭,用户体验略差,优先推荐前置检查方案。
内容的提问来源于stack exchange,提问作者tech_human
相关产品推荐
相关产品推荐

