1. 需求与方案:为什么 Flutter 定位权限要单独聊
做 Flutter 开发的人基本都遇到过这种场景:AndroidManifest.xml里随手写一行权限,App 跑起来直接弹窗授权,顺风顺水。但换到 iOS 上,权限这套逻辑完全不是一回事。你写完了 Dart 代码,调起定位服务,结果模拟器上直接崩溃,真机上也是一脸懵——报错信息不明不白,甚至有时候按钮点了没反应,页面就是拿不到定位。
这个问题我一开始也踩过不少坑,后来把整个链路拆开看了几遍才彻底摸透。iOS 的权限体系核心在原生层,Flutter 只是一个框架,它没法绕过 iOS 本身的隐私规则。你在 Dart 层写location、geolocator,只是把代码调到了原生接口,但真正决定权限能不能弹窗、能不能返回位置数据的,是 Xcode 工程里的Info.plist配置、系统级的权限状态管理,以及你在什么时机去请求权限。
这篇内容适合三种人:一是 Flutter 新手,刚把环境跑通,准备做一个带定位功能的小项目,被 iOS 权限文档绕晕的;二是已经在写业务但总被测试反馈“iOS 定位弹窗不出现”的开发者;三是打算自己接原生层做权限定制的进阶选手。我会从最基础的原理开始讲,逐步到完整配置步骤、代码示例、常见报错和线上问题分析,尽量一篇讲清楚。
另外多说一句,这个题目虽然写着“定位权限”,但 iOS 权限配置的很多底层逻辑(比如Info.plist的键名规则、弹窗机制、状态管理)对整个权限体系都是通用的。这篇内容掌握好了,你后面再去接相机、相册、通知权限,思路会顺很多。
2. 定位权限的核心机制与 Info.plist 配置
2.1 iOS 权限请求的底层逻辑
iOS 从 8.0 开始强制要求隐私权限声明,到 iOS 10 之后更是把所有的权限描述文案统一收口到Info.plist。你如果不在这个文件里写对应的 key,系统直接会在运行时给你一个异常:This app has attempted to access privacy-sensitive data without a usage description,然后整个 App 崩溃或功能失效,没有任何商量余地。
这就是 Flutter 开发者最容易遇到的第一道坎:你在 Dart 层调用定位方法,Info.plist里没有声明NSLocationWhenInUseUsageDescription,结果刚打开页面就闪退。解决办法也非常简单——把对应权限描述加上去即可。
iOS 定位权限在系统层面对应两个核心 key:
NSLocationWhenInUseUsageDescription:使用应用期间允许获取定位,对应“使用 App 期间”授权选项NSLocationAlwaysAndWhenInUseUsageDescription:始终允许获取定位,对应“始终”授权选项
按照苹果文档的说法,如果你只申请“使用期间定位”,NSLocationWhenInUseUsageDescription是必须要写的。如果你要申请“始终定位”,那么两个 key 都得写——因为 iOS 认为“始终”权限是建立在“使用期间”基础之上的,这个逻辑在国内开发者看来很绕,但其实就是为了让用户在授权时清楚掌握 App 的定位使用边界。
还有一点值得注意:iOS 的定位权限并不是单纯的一个开关,它内部还分“精确位置”和“粗略位置”两档。从 iOS 14 开始,用户在权限弹窗里可以选择“大概位置”,这对应的是NSLocationTemporaryUsageDescriptionDictionary+NSLocationDefaultAccuracyReduced这套机制。如果你的业务强依赖精确坐标,需要在产品体验层做好提示,让用户明白了再开启“精确定位”。
2.2 在 Xcode 中配置权限描述的正确姿势
在 Flutter 工程里,ios/Runner/Info.plist是你的总入口。我用 Xcode 打开 Runner 工程后,一般直接在 Info 标签页里可视化添加,比手动改 XML 更稳,不容易写错结构。
具体操作很简单:
- 用 Xcode 打开
ios/Runner.xcworkspace(注意别开成.xcodeproj) - 左侧导航栏找到
Runner目录,点击Info.plist - 在列表空白处右键,选择 Add Row
- 输入 key 名称
NSLocationWhenInUseUsageDescription,Value 栏填你 App 要展示给用户的文案,比如“用于推荐附近的内容和商家信息” - 如果需要后台定位,再加上
NSLocationAlwaysAndWhenInUseUsageDescription,文案建议写清楚后台定位的场景
这里给一个我实际项目里的 plist 示例片段,你可以对照检查自己有没有漏:
<key>NSLocationWhenInUseUsageDescription</key> <string>需要获取您的位置信息,用于展示您附近的商户与活动</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>我们需要在后台持续获取位置信息,用于记录您的运动轨迹</string>如果你是通过 CI 打包或者用脚本自动注入权限描述,也可以直接用 PlistBuddy 来操作,这个在云构建场景下特别实用。示例命令:
/usr/libexec/PlistBuddy -c "Add :NSLocationWhenInUseUsageDescription string 需要获取您的位置信息,用于展示您附近的商户与活动" ios/Runner/Info.plist2.3 前台定位 vs 后台持续定位
权限描述只是第一步,真正决定定位能力的还有两个层级:Info.plist里的描述字符串,以及Capabilities里的后台模式配置。
如果你只做前台定位,比如打开 App 时获取一次当前位置,NSLocationWhenInUseUsageDescription就够了,不需要额外开启后台模式。但如果你的业务涉及导航、运动健康、儿童防走失这种后台持续跟踪场景,那么除了权限描述,还得在 Xcode 的Signing & Capabilities里开启Background Modes,勾选Location updates。
这个配置如果漏了,会出现一个很折磨人的现象:App 在锁屏或切后台后,定位数据会慢慢停下来,看起来像是代码写错了,但其实是系统把后台定位能力禁用了。
考虑到很多 Flutter 开发者并不熟悉 Xcode 的 Capabilities 操作,我给你一个查询命令,可以快速检查当前工程是否已经开启了后台定位:
plutil -p ios/Runner/Info.plist | grep UIBackgroundModes输出结果里如果包含location,说明后台定位模式已开启;如果没有,就需要在 Xcode 里手动加。
注意:App Store 审核时,后台定位模式必须有明确的使用场景说明,否则容易被拒。如果你只是想在应用内获取一次位置,千万别勾选 Background Modes,否则反而会增加审核风险。
3. Flutter 端权限请求:代码实现与状态管理
3.1 权限请求库的选择
Flutter 生态里操作定位权限的库不少,最主流的有两个:location和geolocator。这两个库底层都是调用 iOS 的CoreLocation框架,但 API 风格和权限处理逻辑有所差别。
location库的特点是接口简洁,适合快速开发,它内部已经帮你处理了一部分权限判断逻辑,使用起来很顺手。而geolocator功能更完整,除了定位权限之外,还有 GPS 开关检查、位置流订阅、计算距离等方法,是很多中大型项目的首选。
我在实际开发中一般用geolocator多一点,因为它在权限状态枚举上做得更细致,而且每年都会跟随 iOS 版本做适配更新。这里我以geolocator为例,给出完整流程。
先添加依赖:
dependencies: geolocator: ^10.1.0然后在 Dart 层实现权限请求前,建议先对 iOS 的设备做一次定位服务检查。这里要特别提醒:iOS 上定位服务是一个全局开关,有些用户会在系统设置里把它关掉,这个状态在权限 API 里并不会直接返回“拒绝”,而是返回“服务不可用”。如果不对这种情况做处理,你的 App 可能是静默失败的。
3.2 权限状态检查与首次请求
用geolocator请求定位权限,逻辑分为两步:先检查当前权限状态,再根据状态决定是重新请求还是直接取位置。
我通常会在页面的initState阶段封装一个方法,统一处理权限请求和位置获取。大致代码如下:
import 'package:geolocator/geolocator.dart'; Future<void> checkAndRequestPermission() async { // 先检查定位服务是否开启 bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { // 提示用户打开系统定位服务 return; } // 获取当前权限状态 LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { // 第一次弹出系统授权框 permission = await Geolocator.requestPermission(); } if (permission == LocationPermission.denied) { // 用户拒绝了首次弹窗,需要引导用户去设置页开启 return; } if (permission == LocationPermission.deniedForever) { // iOS 上对应“在设置中允许”,这种状态无法用 requestPermission 再次触发弹窗 return; } // 权限正常,可以获取位置了 Position position = await Geolocator.getCurrentPosition(); print(position.latitude); }这里有几个细节值得展开说说。第一点,iOS 上系统弹窗只会出现一次,用户选择“不允许”之后,再次调用requestPermission()并不会再次弹窗,权限状态会成为denied或者deniedForever,后者表示用户已经在设置里把开关关掉了。这个逻辑和 Android 6.0 以上动态权限的“拒绝一次后再次弹窗”机制完全不同,很多从 Android 转 iOS 开发的朋友在这块都会踩坑。
第二点,denied和deniedForever的处理策略需要区分。前者可以引导用户重新触发一次弹窗(比如通过界面按钮引导),后者则需要引导用户去系统设置的 App 隐私页手动开启。代码上可以用openAppSettings()方法跳转:
await Geolocator.openAppSettings();这个方法在 iOS 上跳转的是 App 自身的设置页,用户可以直接看到定位权限开关,体验比较流畅。
3.3 权限请求的适当时机
权限弹窗的时机选择,对最终授权率的影响比我一开始预想的大得多。iOS 系统的弹窗本身就比较克制,而且会伴随系统设置警告,用户如果在一个毫无准备的情况下突然看到权限弹窗,拒绝的概率很高。
基于用户行为心理,比较好的做法是:先展示一个 UI 引导页,告诉用户“我们需要你的位置才能为你推荐附近的选项”,用户同意后,再调用requestPermission()。这样用户的心理预期建立了,授权成功率会明显提升。
我自己的项目里就是用一个半屏弹窗,上面写清用途,底部放两个按钮:
Future<void> showLocationGuideDialog() async { bool? agreed = await showDialog<bool>( context: context, builder: (context) => AlertDialog( title: const Text('获取位置信息'), content: const Text('用于为您推荐附近的商户和活动,仅在使用过程中获取'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('暂不'), ), TextButton( onPressed: () => Navigator.pop(context, true), child: const Text('同意'), ), ], ), ); if (agreed == true) { await checkAndRequestPermission(); } }这个模式虽然看起来多了一道工序,但实际效果很好。特别是面向 C 端的产品,用户授权率对比“直接弹系统框”的方式,提升幅度还是很明显的。你也可以根据自己的业务场景设计引导文案,核心逻辑是一样的——在系统请求之前先做一个“软请求”。
3.4 监听定位状态变化:应对用户中途改设置
除了首次请求之外,还有一个常见场景:App 正在运行,用户切到系统设置里关闭了定位权限,或者从“使用期间”改成了“永不”。这种情况下,你的 App 不会收到任何通知,如果继续使用之前获取的位置数据,或者再次调用定位接口,都会失败。
要应对这类变化,我开始用stream来监听系统权限状态。Geolocator 库提供了onLocationSettingsChanged,但权限变化监听在不同 iOS 版本上行为不太一样,所以我一般直接用WidgetsBindingObserver在 App 生命周期回调里做一次状态再检查:
class LocationPermissionListener with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { // 从后台回前台,重新检查权限 checkPermissionAndUpdateUI(); } } }这么做的原因很简单:用户在设置里改完权限后返回 App,AppLifecycleState一定会经过resumed,在这个时间点重新检查权限状态,就能保证 UI 展示的权限描述和系统实际状态一致。这个方法算是成本低、收益高的稳妥方案,大家可以在自己的项目里直接用。
3.5 定位权限相关的几个常见状态枚举
用geolocator时,你会接触到以下枚举,我整理了一份对照表方便你理解:
| 枚举值 | 含义 | 处理建议 |
|---|---|---|
LocationPermission.whileInUse | App 使用期间可定位 | 正常使用 |
LocationPermission.always | 始终允许定位 | 正常使用,后台取位需要 |
LocationPermission.denied | 用户拒绝了授权 | 引导再次请求或去设置开启 |
LocationPermission.deniedForever | 用户永久拒绝(设置了“不允许”) | 必须跳转系统设置 |
LocationPermission.unableToDetermine | 无法确定权限状态 | 建议重试一次 |
这里有个细节:在 iOS 上,unableToDetermine通常出现在 App 刚安装、还没调用过任何权限接口的时候,这其实是一个“未知”状态。如果你在这个状态下直接去getCurrentPosition(),iOS 会自动弹窗。但从产品层的角度来说,我建议你把它当作“未请求过”来处理,主动走一遍引导流程,而不是依赖系统自动行为。
4. 常见问题与线上避坑实录
4.1 权限弹窗不出现的原因排查
弹窗不弹,是最多开发者的共性问题。我接到过的反馈里,十有八九都是Info.plist没配全,但也确实有几个比较隐蔽的原因,我把典型的排查路径写出来,希望对你有帮助。
优先检查顺序如下:
- 确认
Info.plist里NSLocationWhenInUseUsageDescription是否存在且有非空文案。如果有跳过这步没写,系统直接闪退,不会给你弹窗的机会。 - 确认你调用的权限请求 API 是在主线程。Flutter 的
Geolocator.requestPermission()内部已经封装好了线程处理,但如果混合开发时你在原生侧自己写了 Swift 代码,就要注意CLLocationManagerDelegate的回调是否在主线程执行。 - 检查是否误将
NSLocationAlwaysAndWhenInUseUsageDescription单独配置为前台定位。比如你只写了“始终”权限,iOS 会把“使用期间”当作其子集,但部分 iOS 版本表现不稳定,建议两个都写上。 - 如果用了善意的“用户引导弹窗”,可能你的自定义弹窗展示之后没有接着去调
requestPermission(),代码逻辑断掉了。
这里往往有一个容易被忽略的点:如果你把Info.plist改错了格式,比如把字符串写成了数组,Xcode 在编译时会直接跑出一个奇怪的报错,但并不会直接提示你“权限描述配置失败”。这时候你可以用plutil -lint ios/Runner/Info.plist检查格式,一条命令就能定位问题。
4.2 真机上定位失效,模拟器却是正常的
这种诡异的现象,对于不熟悉 iOS 权限的开发者来说非常困惑。模拟器上定位完全正常,权限弹窗流畅,但一装到真机上就各种故障,最常见的两个方向是配置问题和签名问题。
先说配置问题:在模拟器上,CoreLocation framework 默认是 enabled 状态的,模拟器会自动返回 Apple 提供的一个虚拟位置;真机上则要依赖真实的 GPS 和网络定位。如果你的 iPhone 定位服务没开,或者系统给了低精度模式,App 拿到的位置就是“大概区域”,不是精确坐标。
再说签名问题:如果证书和 Bundle ID 不匹配,或者 App ID 没有打开 Location Updates 权限,安装到真机上之后很多传感器功能是拿不到的,表现就是定位静默失败。这时候优先检查开发者中心里 App ID 的 Capabilities 是否勾选了 Location Updates。对了,开发调试和发布证书是两套,别只顾着调试套件。
如果你在本机遇到过一个问题:
I simultaneously have both and fixed it by adding the key to Info.plist,但实际上没有生效——建议先 Clean Build Folder,再删掉 App 重装。iOS 的权限状态会被保留在系统级,重装不一定能重置,真机上最稳妥的做法是去设置里把 App 的权限状态改一下再试。
4.3 “永不”权限状态的处理与提示策略
前面提到了deniedForever,对应 iOS 系统设置里的“永不”选项。这个状态下,用户无论怎么点 App 里的“重新请求”,系统都不会再弹窗了。唯一的出路是引导用户去系统设置里手动打开。
我在项目里的引导方式是:弹一个对话框,写明“您已在系统设置中关闭了定位权限,请在设置中开启”,然后给一个“去设置”按钮,点击后调用openAppSettings()。代码上的处理逻辑我前面已经写了,这里再补充一句:跳转系统设置之后,最好监听 App 回到前台的时机,做一次自动重查,免得用户回来后还要手动刷新。
再补充一个自己踩过比较多次的坑:iOS 的权限描述文案里最好不要直接写“如果不授权就无法使用”这种强制型话语。按苹果审核指南的描述,这种胁迫式文案容易被拒。换个说法,比如“用于为您推荐附近的内容”,会温和得多。
4.4 后台定位与省电模式的权衡
如果你的 App 确实需要后台定位,比如运动健身类应用,这里有几个经验之谈。
第一,后台定位的文件描述和用户提示要写清楚。App Store 审核员会逐字检查你的文案是否存在误导,曾经就有开发者写“仅在使用中获取定位”但后台模式却勾选了 location,被审核拒了。
第二,后台定位对电池寿命影响很大。iOS 系统会在后台对定位频率做一定程度的限制,如果你在代码里设置了过高频率的desiredAccuracy和distanceFilter,系统可能会拒绝响应,表现得就像权限出问题了一样。我一般设置distanceFilter为 30 米或更高,这样既保证了路线轨迹的完整性,又不会过度损耗电量。
第三,定位权限弹窗上,如果你只写“使用期间”权限,后来想升级为“始终”权限,iOS 会再次弹窗用户确认,这个弹窗由系统控制,你没法自定义样式。所以产品设计上要提前规划好你到底需要哪种定位级别,避免后期频繁让用户做二次选择,体验比较割裂。
4.5 后台定位权限配置的完整检查清单
我习惯把 Checklist 沉淀下来,每次项目迭代或接手新工程时,对着检查一遍,几分钟就搞定。
| 检查项 | 说明 | 完成情况 |
|---|---|---|
| Info.plist 中 WhenInUse 文案 | 必填,使用期间定位 | |
| Info.plist 中 Always 文案 | 如果需要始终定位则必填 | |
| Background Modes 勾选 Location updates | 仅后台定位需要 | |
| App ID 开启 Location Updates capability | 开发者中心配置,真机必查 | |
| 真机定位服务开关确认 | 用户级别系统开关,代码层无法控制 | |
| 权限状态监听 | 处理用户切后台改设置的情况 | |
| 用户拒绝后的引导 UI | 区分 denied 和 deniedForever |
按这个表逐项核对,一套 iOS 定位权限配置的完整闭环基本就覆盖了。从工程层面看,Info.plist的 key 写对、能力开关开齐、代码层处理好边界逻辑,权限部分就稳如老狗了。
5. Flutter 定位权限迁移与多平台联动注意点
5.1 Android 与 iOS 权限逻辑差异对照
写 Flutter 应用不可避免要跨端思考。虽然这篇重点是 iOS,但我还是想简单提一下 Android 的差异,因为很多同学习惯了 Android 的自由度,转 iOS 后容易不适应。
Android 6.0+ 的动态权限是在运行时弹窗,用户可以多次选择拒绝后,下次再请求仍然可以重新弹窗;而 iOS 的弹窗一次之后就“翻篇了”。所以在 Flutter 代码里,你写权限逻辑时不能一套代码草草复用,而要考虑两端的差异。
我在项目里的习惯是,把权限判断逻辑封装在一个独立的 Service 类里,针对 Android 和 iOS 分别写分支处理。这样虽然代码量多了一些,但每一端的行为都是可控的,调试起来也不会互相干扰。
class PermissionService { Future<bool> ensureLocationPermission() async { if (Platform.isIOS) { return _ensureIosPermission(); } return _ensureAndroidPermission(); } }5.2 权限位图与隐私合规的挑战
近两年的移动应用,光有技术方案还不够,隐私合规几乎是每个开发者绕不开的话题。iOS 端对权限的描述文案要求特别严格,苹果要求你列出的权限用途必须与实际使用行为一一对应,不能有模糊地带。
很多产品在功能设计时,把定位权限挂在主流程里,但用户其实只是想浏览内容,根本没必要启动定位。这种设计既影响用户体验,也容易被 App Store 审核挑战。我的建议是:把定位请求从主流程拆出来,作为一个可选的“增强功能”,用户主动触发时去申请,授权率反而更高,体验上也干净利落。
5.3 与 Flutter 内置数据库及后端同步的组合用法
聊个稍微拓展一点的话题。很多做 Flutter 的团队会在定位功能之上叠加本地缓存或后端同步的场景,比如记录用户轨迹后上传服务器,这种架构其实和权限配置有很强的关联。
获取到定位权限并拿到位置后,常见做法是把定位数据写入本地数据库(如 sqlite 或 hive),等网络恢复后再批量同步到后端。为了不阻塞主流程,可以借助path_provider获取本地文档目录,然后用 sqflite 写入定位记录:
final directory = await getApplicationDocumentsDirectory(); final dbPath = p.join(directory.path, 'location_records.db'); final database = await openDatabase(dbPath); Future<void> saveLocation(Position position) async { await database.insert('locations', { 'latitude': position.latitude, 'longitude': position.longitude, 'timestamp': DateTime.now().toIso8601String(), }); }这种做法我在实际项目里验证过,可靠性很好。特别是网速不稳定的场景,本地先入库再同步,避免了一次定位失败就丢数据的尴尬。
6. 实战案例:一个常见场景的完整实现
前面讲了很多原理和思路,这节我用一个完整的实战场景,把前面零散的知识串起来。场景是:做一个“附近的咖啡店推荐”页面,用户点击按钮,获取自己的定位,页面展示附近商家。
第一步,配置权限描述。在Info.plist中确认NSLocationWhenInUseUsageDescription已填好,文案是“用于推荐您附近的咖啡店”。
第二步,添加依赖。pubspec.yaml中加入geolocator。
第三步,业务代码里先做引导弹窗,用户点同意后调用权限请求,如果拒绝则引导去设置。核心逻辑前面已经给过了,这里我再结合这个具体场景展开一下完整过程:
Future<void> onEnterNearbyCafe() async { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { showSnackBar('请先在系统设置中打开定位服务'); return; } LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); } if (permission == LocationPermission.denied) { showSnackBar('定位权限被拒绝,无法推荐附近咖啡店'); return; } if (permission == LocationPermission.deniedForever) { openAppSettingsDialog(); return; } // 获取当前位置 try { Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, ); // 调用后端接口,拿附近咖啡店数据 final shops = await fetchNearbyShops( position.latitude, position.longitude, ); setState(() => _shops = shops); } catch (e) { // 定位过程抛异常,一般是硬件或系统问题 showSnackBar('定位失败,请稍后重试'); } }第四步,处理好状态监听。在页面initState注册WidgetsBindingObserver,从后台回到前台时刷新权限状态,避免用户改了设置后页面还是旧状态。
这个流程走完,业务闭环就通了。从用户点击按钮、看引导弹窗、系统鉴权、拿到定位、请求后端、渲染列表,每一步都有对应的异常处理,产品在线上真正跑起来的时候,才不容易出幺蛾子。
7. 权限弹窗背后的用户隐私心理与实际项目心得
普通开发者的视角经常停留在“如何让弹窗弹出来”,但我做了几个 C 端项目之后发现,真正决定 App 质量的其实是用户对权限请求的第一感受。
iOS 的权限弹窗本身是系统级的,界面固定,用户无法自定义。开发者能做的,是控制弹窗出现的时机和前提引导。我测试过很多次,如果把权限请求放在用户刚启动 App 的第一屏,用户对产品还没有任何认知,直接问他要隐私权限,拒绝率通常在六成以上;但如果用户已经完成了一两个关键操作、产生了真实的使用需求,这时候再弹窗,授权率可以稳定在八成以上。
这个规律在定位权限上特别明显。比如淘宝和地图类应用,冷启动时问定位是为业务刚需;但一个阅读类应用冷启动就要定位,用户会觉得莫名其妙。所以产品经理和开发者在需求阶段就要达成一致:定位是刚需,还是可选项?刚需可以考虑启动即请求;可选项则建议等业务触达时再申请。
另外从技术角度,我建议 Flutter 项目里把权限请求统一收敛到一个 service 中,不要多个页面各自重复写。这样未来如果要调整文案、加统计埋点、切换三方定位库,只需要改一处,维护成本大大降低。
我自己在实际项目中还体会到一件事:iOS 的开发者模式对权限调试有一定影响。如果你在 Xcode 里通过真机调试,装了应用但没过一会儿权限行为表现异常,先检查是否开启了开发者模式,或者重新信任证书。这类问题虽然不属于权限配置本身,但在联调阶段非常打断节奏。如果遇到奇怪现象,优先重启手机试试,很多时候问题就解决了。
定位权限本身并不是一项高深到无法掌握的技能,关键在于把 iOS 的系统规则吃透,把工程配置和代码逻辑对齐,把用户引导设计到位。只要这三个层面都做扎实,你的 Flutter 应用在 iOS 端定位权限的路就会走得很顺。