You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 17拒绝日历权限后仍显示EventKit事件编辑视图的问题解决

iOS 17中拒绝日历权限后仍显示EKEventEditViewController的问题修复

问题概述

在SwiftUI中基于EventKit开发添加日历功能时,点击"Add to Calendar"按钮会检查日历授权状态,未授权则发起权限请求。但用户拒绝权限后,iOS 16.4不会显示事件创建视图,iOS 17.0却依然弹出该视图,需要实现权限拒绝后自动关闭sheet,避免无效视图展示。

问题根源

当前代码的核心问题在于权限检查时机滞后:

  1. EventEditView的makeUIViewController方法中,先创建并返回了EKEventEditViewController,之后才异步执行权限检查。这导致iOS 17中视图已被呈现,后续的权限拒绝结果无法及时撤销视图展示。
  2. 权限通过后才初始化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 {
                // 处理事件保存/更新后的逻辑
            }
        }
    }
}

关键修改点

  1. 权限前置检查:将权限检查逻辑移到ContentView的按钮点击事件中,异步执行后根据结果决定是否打开sheet,从根源避免无效视图弹出。
  2. 用户体验优化:权限拒绝时弹出提示弹窗,引导用户前往系统设置开启权限。
  3. 简化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 14:24:56