From ba393f52665337162cc1d1a4b007611aac62b4b6 Mon Sep 17 00:00:00 2001 From: yuqucheng <> Date: Mon, 22 Jun 2026 17:05:53 +0800 Subject: [PATCH] =?UTF-8?q?ios=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Editor/FirebaseIosPodfilePostProcessor.cs | 81 -------- .../FirebaseIosPodfilePostProcessor.cs.meta | 11 - Assets/Firebase/Editor/AppDependencies.xml | 3 - Assets/Firebase/Editor/AuthDependencies.xml | 3 - .../Firebase/Editor/DatabaseDependencies.xml | 3 - Assets/Plugins/iOS/NativeQRScanner.mm | 190 ++++++++++++++++++ Assets/Plugins/iOS/NativeQRScanner.mm.meta | 33 +++ .../UI/Pages/ConnectDevice/NativeQRScanner.cs | 7 +- 8 files changed, 226 insertions(+), 105 deletions(-) delete mode 100644 Assets/Editor/FirebaseIosPodfilePostProcessor.cs delete mode 100644 Assets/Editor/FirebaseIosPodfilePostProcessor.cs.meta create mode 100644 Assets/Plugins/iOS/NativeQRScanner.mm create mode 100644 Assets/Plugins/iOS/NativeQRScanner.mm.meta diff --git a/Assets/Editor/FirebaseIosPodfilePostProcessor.cs b/Assets/Editor/FirebaseIosPodfilePostProcessor.cs deleted file mode 100644 index 980ea66..0000000 --- a/Assets/Editor/FirebaseIosPodfilePostProcessor.cs +++ /dev/null @@ -1,81 +0,0 @@ -#if UNITY_IOS -using UnityEditor; -using UnityEditor.Callbacks; -using System.IO; -using System.Diagnostics; - -public class FirebaseIosPodfilePostProcessor -{ - [PostProcessBuild(100)] // 优先级设为 100,在其他后处理之后执行 - public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject) - { - if (target != BuildTarget.iOS) return; - - string podfilePath = Path.Combine(pathToBuiltProject, "Podfile"); - - // 生成 Podfile - string podfileContent = @"source 'https://cdn.cocoapods.org/' - -platform :ios, '15.0' - -target 'UnityFramework' do - pod 'Firebase/Core', '12.4.0' - pod 'Firebase/Auth', '12.4.0' - pod 'Firebase/Database', '12.4.0' - pod 'Firebase/Messaging', '12.4.0' -end -target 'Unity-iPhone' do -end -use_frameworks! :linkage => :static -"; - - File.WriteAllText(podfilePath, podfileContent); - UnityEngine.Debug.Log("[FirebaseIosPostProcessor] Podfile 已生成: " + podfilePath); - - // 尝试自动运行 pod install - TryRunPodInstall(pathToBuiltProject); - } - - private static void TryRunPodInstall(string projectPath) - { - try - { - ProcessStartInfo startInfo = new ProcessStartInfo - { - FileName = "pod", - Arguments = "install", - WorkingDirectory = projectPath, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; - - using (Process process = Process.Start(startInfo)) - { - string stdout = process.StandardOutput.ReadToEnd(); - string stderr = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - if (process.ExitCode == 0) - { - UnityEngine.Debug.Log("[FirebaseIosPostProcessor] pod install 成功"); - } - else - { - UnityEngine.Debug.LogWarning("[FirebaseIosPostProcessor] pod install 失败:\n" + stderr); - UnityEngine.Debug.LogWarning("[FirebaseIosPostProcessor] 请手动在 " + projectPath + " 目录下执行 pod install"); - } - } - } - catch (System.Exception e) - { - UnityEngine.Debug.LogWarning("[FirebaseIosPostProcessor] 未找到 CocoaPods,请手动执行:\n" + - " 1. 安装 CocoaPods: sudo gem install cocoapods\n" + - " 2. cd " + projectPath + "\n" + - " 3. pod install\n" + - " 错误: " + e.Message); - } - } -} -#endif diff --git a/Assets/Editor/FirebaseIosPodfilePostProcessor.cs.meta b/Assets/Editor/FirebaseIosPodfilePostProcessor.cs.meta deleted file mode 100644 index 09976c2..0000000 --- a/Assets/Editor/FirebaseIosPodfilePostProcessor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3865f2d3232564d5eac2422f14cdf6e3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Firebase/Editor/AppDependencies.xml b/Assets/Firebase/Editor/AppDependencies.xml index 7a25ede..709e7d8 100644 --- a/Assets/Firebase/Editor/AppDependencies.xml +++ b/Assets/Firebase/Editor/AppDependencies.xml @@ -4,9 +4,6 @@ FirebaseApp iOS and Android Dependencies. --> - - - diff --git a/Assets/Firebase/Editor/AuthDependencies.xml b/Assets/Firebase/Editor/AuthDependencies.xml index ca7fae9..344709a 100644 --- a/Assets/Firebase/Editor/AuthDependencies.xml +++ b/Assets/Firebase/Editor/AuthDependencies.xml @@ -4,9 +4,6 @@ FirebaseAuth iOS and Android Dependencies. --> - - - diff --git a/Assets/Firebase/Editor/DatabaseDependencies.xml b/Assets/Firebase/Editor/DatabaseDependencies.xml index 2af53e2..1589dfa 100644 --- a/Assets/Firebase/Editor/DatabaseDependencies.xml +++ b/Assets/Firebase/Editor/DatabaseDependencies.xml @@ -4,9 +4,6 @@ FirebaseDatabase iOS and Android Dependencies. --> - - - diff --git a/Assets/Plugins/iOS/NativeQRScanner.mm b/Assets/Plugins/iOS/NativeQRScanner.mm new file mode 100644 index 0000000..4b1d54d --- /dev/null +++ b/Assets/Plugins/iOS/NativeQRScanner.mm @@ -0,0 +1,190 @@ +#import +#import + +// ──── 前向声明 ──── +static void presentQRScannerWithName(NSString *goName); + +@interface QRScannerViewController : UIViewController +@property (nonatomic, strong) AVCaptureSession *session; +@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer; +@property (nonatomic, copy) NSString *gameObjectName; +@property (nonatomic, assign) BOOL hasScanned; +@end + +@implementation QRScannerViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor blackColor]; + + self.session = [[AVCaptureSession alloc] init]; + + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if (!device) { + [self sendResultToUnity:@"ERROR:NO_CAMERA"]; + return; + } + + NSError *error = nil; + AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; + if (!input) { + [self sendResultToUnity:[NSString stringWithFormat:@"ERROR:CAMERA_INPUT:%@", error.localizedDescription]]; + return; + } + + [self.session addInput:input]; + + AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init]; + [self.session addOutput:output]; + [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; + output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode]; + + self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session]; + self.previewLayer.frame = self.view.bounds; + self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + [self.view.layer addSublayer:self.previewLayer]; + + // 半透明遮罩 + 扫描框 + CGFloat boxSize = self.view.bounds.size.width * 0.65; + CGRect boxRect = CGRectMake( + (self.view.bounds.size.width - boxSize) / 2, + (self.view.bounds.size.height - boxSize) / 2 - 60, + boxSize, boxSize + ); + + CAShapeLayer *mask = [CAShapeLayer layer]; + UIBezierPath *fullPath = [UIBezierPath bezierPathWithRect:self.view.bounds]; + [fullPath appendPath:[[UIBezierPath bezierPathWithRoundedRect:boxRect cornerRadius:4] bezierPathByReversingPath]]; + mask.path = fullPath.CGPath; + mask.fillColor = [UIColor colorWithWhite:0 alpha:0.5].CGColor; + [self.view.layer addSublayer:mask]; + + CAShapeLayer *border = [CAShapeLayer layer]; + border.path = [UIBezierPath bezierPathWithRoundedRect:boxRect cornerRadius:4].CGPath; + border.strokeColor = [UIColor greenColor].CGColor; + border.lineWidth = 2; + border.fillColor = UIColor.clearColor.CGColor; + [self.view.layer addSublayer:border]; + + UILabel *hint = [[UILabel alloc] init]; + hint.text = @"将二维码放入框内,即可自动扫描"; + hint.textColor = UIColor.whiteColor; + hint.font = [UIFont systemFontOfSize:14]; + hint.textAlignment = NSTextAlignmentCenter; + hint.frame = CGRectMake(20, boxRect.origin.y + boxRect.size.height + 20, + self.view.bounds.size.width - 40, 30); + [self.view addSubview:hint]; + + UIButton *cancel = [UIButton buttonWithType:UIButtonTypeSystem]; + [cancel setTitle:@"关闭" forState:UIControlStateNormal]; + [cancel setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; + cancel.titleLabel.font = [UIFont systemFontOfSize:17]; + cancel.frame = CGRectMake(0, self.view.bounds.size.height - 100, + self.view.bounds.size.width, 50); + [cancel addTarget:self action:@selector(onCancel) forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:cancel]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self.session startRunning]; + }); +} + +- (void)onCancel { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self.session stopRunning]; + }); + [self dismissViewControllerAnimated:YES completion:^{ + [self sendResultToUnity:@"CANCEL"]; + }]; +} + +- (void)sendResultToUnity:(NSString *)result { + const char *go = [self.gameObjectName UTF8String]; + const char *msg = [result UTF8String]; + UnitySendMessage((char *)go, "OnScanResult", (char *)msg); +} + +#pragma mark - AVCaptureMetadataOutputObjectsDelegate + +- (void)captureOutput:(AVCaptureOutput *)output +didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects + fromConnection:(AVCaptureConnection *)connection { + + if (self.hasScanned) return; + if (metadataObjects.count == 0) return; + + AVMetadataMachineReadableCodeObject *code = metadataObjects.firstObject; + if (![code isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) return; + + self.hasScanned = YES; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self.session stopRunning]; + }); + + [self dismissViewControllerAnimated:YES completion:^{ + [self sendResultToUnity:code.stringValue ?: @"ERROR:EMPTY_RESULT"]; + }]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self.session stopRunning]; + }); +} + +@end + +// ──── C 函数实现 ──── + +static void presentQRScannerWithName(NSString *goName) { + dispatch_async(dispatch_get_main_queue(), ^{ + UIViewController *rootVC = UnityGetGLViewController(); + if (!rootVC) { + UnitySendMessage((char *)[goName UTF8String], + "OnScanResult", + "ERROR:NO_ROOT_VC"); + return; + } + + QRScannerViewController *scanner = [[QRScannerViewController alloc] init]; + scanner.gameObjectName = goName; + scanner.modalPresentationStyle = UIModalPresentationFullScreen; + [rootVC presentViewController:scanner animated:YES completion:nil]; + }); +} + +extern "C" { + + void _StartScanIOS(const char *gameObjectName) + { + NSString *goName = [NSString stringWithUTF8String:gameObjectName]; + + dispatch_async(dispatch_get_main_queue(), ^{ + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + + if (status == AVAuthorizationStatusNotDetermined) { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { + if (granted) { + presentQRScannerWithName(goName); + } else { + UnitySendMessage((char *)[goName UTF8String], + "OnScanResult", + "ERROR:PERMISSION_DENIED"); + } + }]; + return; + } + + if (status == AVAuthorizationStatusAuthorized) { + presentQRScannerWithName(goName); + } else { + UnitySendMessage((char *)[goName UTF8String], + "OnScanResult", + "ERROR:PERMISSION_DENIED"); + } + }); + } + +} diff --git a/Assets/Plugins/iOS/NativeQRScanner.mm.meta b/Assets/Plugins/iOS/NativeQRScanner.mm.meta new file mode 100644 index 0000000..f27f7e3 --- /dev/null +++ b/Assets/Plugins/iOS/NativeQRScanner.mm.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 7adc8635ee20a497c8ff442ae14a56f7 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/UI/Pages/ConnectDevice/NativeQRScanner.cs b/Assets/Scripts/UI/Pages/ConnectDevice/NativeQRScanner.cs index 5ece7b5..6291dfb 100644 --- a/Assets/Scripts/UI/Pages/ConnectDevice/NativeQRScanner.cs +++ b/Assets/Scripts/UI/Pages/ConnectDevice/NativeQRScanner.cs @@ -29,14 +29,13 @@ namespace Kill.UI.Pages currentActivity.Call("startActivityForResult", intent, 0); } #elif UNITY_IOS - // iOS 需要写原生插件 - _StartScanIOS(); - #endif + _StartScanIOS(gameObject.name); +#endif } #if UNITY_IOS [System.Runtime.InteropServices.DllImport("__Internal")] - private static extern void _StartScanIOS(); + private static extern void _StartScanIOS(string gameObjectName); #endif ///