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

Xcode模拟器中Flutter应用相机权限自动被拒问题求助

Flutter iOS端二维码扫描问题排查与解决

问题描述

我是Flutter新手,用Android Studio开发了宾客签到二维码扫描应用,Android端运行正常,但iOS端遇到两个问题:

  • 点击「开始扫描」时,相机权限自动被拒,尝试通过Settings.bundle让应用显示在模拟器设置中,仍无法授予权限;
  • 移除permission_handler后,点击扫描会打开带方框的空白页面,抛出PlatformException错误,提示未注册平台视图类型<net.touchcapture.qr.flutterqr/qrview>。

相关代码与配置

main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'guest_list.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => GuestListProvider(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Guest Check-In App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: StartPage(),
    );
  }
}

class StartPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          Positioned.fill(
            child: Image.asset(
              'assets/logo.png',
              fit: BoxFit.cover,
            ),
          ),
          Center(
            child: ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => HomePage()),
                );
              },
              child: Text('Enter'),
            ),
          ),
        ],
      ),
    );
  }
}

guest_list.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:permission_handler/permission_handler.dart';

class GuestListProvider extends ChangeNotifier {
  final List<String> _guestList = [
    'John Doe',
    'Jane Smith',
    'Alice Johnson',
  ];

  final Set<String> _checkedInGuests = Set();

  List<String> get guestList => _guestList;

  bool isCheckedIn(String guest) {
    return _checkedInGuests.contains(guest);
  }

  void checkInGuest(String guest) {
    _checkedInGuests.add(guest);
    notifyListeners();
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Guest Check-In'),
      ),
      body: Stack(
        children: [
          Positioned.fill(
            child: Container(
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: AssetImage('assets/flowery_background.png'),
                  fit: BoxFit.cover,
                ),
                color: Colors.white30.withOpacity(0.3),
              ),
            ),
          ),
          Column(
            children: [
              Expanded(
                child: GuestListView(),
              ),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: ElevatedButton(
                  onPressed: () async {
                    PermissionStatus status = await _getCameraPermission();
                    if (status.isGranted) {
                      Navigator.push(
                        context,
                        MaterialPageRoute(builder: (context) => QRViewExample()),
                      );
                    } else if (status.isPermanentlyDenied) {
                      showDialog(
                        context: context,
                        builder: (context) => AlertDialog(
                          title: Text('Camera Permission Denied'),
                          content: Text('Camera permission is required to scan QR codes. Please enable it in the settings.'),
                          actions: [
                            TextButton(
                              onPressed: () {
                                openAppSettings();
                                Navigator.of(context).pop();
                              },
                              child: Text('Open Settings'),
                            ),
                            TextButton(
                              onPressed: () {
                                Navigator.of(context).pop();
                              },
                              child: Text('Cancel'),
                            ),
                          ],
                        ),
                      );
                    } else {
                      showDialog(
                        context: context,
                        builder: (context) => AlertDialog(
                          title: Text('Camera Permission Denied'),
                          content: Text('Camera permission is required to scan QR codes.'),
                          actions: [
                            TextButton(
                              onPressed: () {
                                Navigator.of(context).pop();
                              },
                              child: Text('OK'),
                            ),
                          ],
                        ),
                      );
                    }
                  },
                  child: Text('Start Scanning'),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Future<PermissionStatus> _getCameraPermission() async {
    var status = await Permission.camera.status;
    if (!status.isGranted) {
      final result = await Permission.camera.request();
      return result;
    } else {
      return status;
    }
  }
}

class GuestListView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<GuestListProvider>(
      builder: (context, guestListProvider, child) {
        return ListView.builder(
          itemCount: guestListProvider.guestList.length,
          itemBuilder: (context, index) {
            final guest = guestListProvider.guestList[index];
            return ListTile(
              title: Text(guest),
              trailing: guestListProvider.isCheckedIn(guest)
                  ? Icon(Icons.check, color: Colors.green)
                  : null,
            );
          },
        );
      },
    );
  }
}

class QRViewExample extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _QRViewExampleState();
}

class _QRViewExampleState extends State<QRViewExample> {
  final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
  QRViewController? controller;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Scan QR Code')),
      body: QRView(
        key: qrKey,
        onQRViewCreated: _onQRViewCreated,
        overlay: QrScannerOverlayShape(
          borderColor: Colors.red,
          borderRadius: 10,
          borderLength: 30,
          borderWidth: 10,
          cutOutSize: 300,
        ),
      ),
    );
  }

  void _onQRViewCreated(QRViewController controller) {
    setState(() {
      this.controller = controller;
    });
    controller.scannedDataStream.listen((scanData) {
      final guestName = scanData.code;
      final guestListProvider = Provider.of<GuestListProvider>(context, listen: false);
      if (guestName != null) {
        if (guestListProvider.guestList.contains(guestName)) {
          if (guestListProvider.isCheckedIn(guestName)) {
            showDialog(
              context: context,
              builder: (context) => AlertDialog(
                title: Text('Already Checked In'),
                content: Text('$guestName has already been checked in.'),
                actions: [
                  TextButton(
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                    child: Text('OK'),
                  ),
                ],
              ),
            );
          } else {
            guestListProvider.checkInGuest(guestName);
            showDialog(
              context: context,
              builder: (context) => AlertDialog(
                title: Text('Guest Checked In'),
                content: Text('$guestName has been checked in.'),
                actions: [
                  TextButton(
                    onPressed: () {
                      Navigator.of(context).pop();
                      Navigator.of(context).pop();
                    },
                    child: Text('OK'),
                  ),
                ],
              ),
            );
          }
        } else {
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              title: Text('Guest Not Found'),
              content: Text('$guestName is not on the guest list.'),
              actions: [
                TextButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                    Navigator.of(context).pop();
                  },
                  child: Text('OK'),
                ),
              ],
            ),
          );
        }
      }
    });
  }

  @override
  void dispose() {
    controller?.dispose();
    super.dispose();
  }
}

错误信息

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(unregistered_view_type, A UIKitView widget is trying to create a PlatformView with an unregistered type: < net.touchcapture.qr.flutterqr/qrview >, If you are the author of the PlatformView, make sure registerViewFactory is invoked.
See: https://docs.flutter.dev/development/platform-integration/platform-views#on-the-platform-side-1 for more details.
If you are not the author of the PlatformView, make sure to call GeneratedPluginRegistrant.register., null)
#0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:648:7)
#1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:334:18)
<asynchronous suspension>
#2      PlatformViewsService.initUiKitView (package:flutter/src/services/platform_views.dart:243:5)
<asynchronous suspension>
#3      _DarwinViewState._createNewUiKitView (package:flutter/src/widgets/platform_view.dart:918:36)
<asynchronous suspension>

AppDelegate.swift

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Info.plist配置

<key>NSCameraUsageDescription</key>
<string>This app requires camera access to scan QR codes.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app requires microphone access for scanning QR codes.</string>

解决方案

一、相机权限自动拒绝问题处理

  1. 使用真机测试:iOS模拟器不支持真实相机硬件,权限请求会直接返回拒绝,必须使用iOS物理设备测试相机扫描功能。
  2. 完善Settings.bundle配置:若要让应用在系统设置中显示相机权限开关,需在Settings.bundle/Root.plist中添加如下配置项:
    <array>
      <!-- 其他配置项 -->
      <dict>
        <key>Type</key>
        <string>PSToggleSwitchSpecifier</string>
        <key>Title</key>
        <string>相机权限</string>
        <key>Key</key>
        <string>camera_permission</string>
        <key>DefaultValue</key>
        <true/>
        <key>FooterText</key>
        <string>允许应用访问相机以扫描二维码</string>
      </dict>
    </array>
    
  3. 确认permission_handler配置:在ios/Podfile中确保iOS版本≥13.0,执行pod install更新依赖:
    platform :ios, '13.0'
    

二、平台视图未注册问题处理

  1. 清理并重新安装依赖:
    • 执行flutter clean清除缓存
    • 删除ios/Podfile.lock文件
    • 进入iOS目录执行pod deintegrate && pod install
  2. 启用平台视图预览:在ios/Runner/Info.plist中添加以下配置,允许Flutter加载iOS平台视图:
    <key>io.flutter.embedded_views_preview</key>
    <true/>
    
  3. 验证插件版本兼容性:检查pubspec.yaml中qr_code_scanner的版本,建议使用最新稳定版,避免与当前Flutter版本不兼容。
  4. 确认插件注册:确保AppDelegate.swift中已调用GeneratedPluginRegistrant.register(with: self),该代码会自动注册所有Flutter插件的平台视图。

内容的提问来源于stack exchange,提问作者smurfymo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 10:47:03