killapp/Assets/Plugins/iOS/WiFiHelper.mm
yuqucheng 9316f8f331 feat(iOS): 完善iOS平台WiFi配对与权限本地化适配
1. 新增iOSWiFiHelper类实现异步获取WiFi SSID,自动请求位置权限
2. 新增iOS权限描述本地化处理器,实现中英文权限文案自动切换
3. 更新所有iOS权限描述为更清晰的英文/中文说明
4. 优化iOS编译后处理脚本优先级,避免被其他插件覆盖
5. 在连接设备页面和WiFi设置页面自动填充当前WiFi名称
6. 更新应用版本号与构建号
2026-08-07 16:14:10 +08:00

249 lines
10 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#import <Foundation/Foundation.h>
#import <SystemConfiguration/CaptiveNetwork.h>
#import <NetworkExtension/NetworkExtension.h>
#import <CoreLocation/CoreLocation.h>
#import <string.h>
static CLLocationManager *locationManager = nil;
// UnitySendMessage 声明
extern void UnitySendMessage(const char *obj, const char *method, const char *msg);
// 授权变化回调目标(通过 UnitySendMessage 通知 C#
static const char *callbackGameObject = nil;
static const char *callbackMethod = nil;
// GetWiFiSSID 前向声明(定义在 extern "C" 块中)
extern "C" const char* GetWiFiSSID();
// 是否正在等待定位更新(收到第一个位置后立即获取 SSID
static BOOL pendingSSIDFetch = NO;
// 延迟获取 SSID 并回调 C#
static void FetchSSIDAndCallback() {
char *ssid = (char *)GetWiFiSSID();
if (callbackGameObject != nil && callbackMethod != nil) {
if (ssid != NULL) {
UnitySendMessage(callbackGameObject, callbackMethod, ssid);
free(ssid);
} else {
UnitySendMessage(callbackGameObject, callbackMethod, "");
}
}
}
@interface WiFiHelperLocationDelegate : NSObject <CLLocationManagerDelegate>
@end
@implementation WiFiHelperLocationDelegate
- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager {
if (@available(iOS 14.0, *)) {
// iOS 14+ 新回调:参数是 manager需自行查询授权状态
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if (status == kCLAuthorizationStatusAuthorizedWhenInUse ||
status == kCLAuthorizationStatusAuthorizedAlways) {
NSLog(@"[WiFiHelper] Location permission granted after request");
[WiFiHelperLocationDelegate startSSIDFetchFlow];
} else if (status == kCLAuthorizationStatusDenied ||
status == kCLAuthorizationStatusRestricted) {
NSLog(@"[WiFiHelper] Location permission denied after request");
if (callbackGameObject != nil && callbackMethod != nil) {
UnitySendMessage(callbackGameObject, callbackMethod, "");
}
}
}
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if (@available(iOS 14.0, *)) return; // iOS 14+ 由上面的新回调处理
// iOS 13 及以下用旧回调方法
if (status == kCLAuthorizationStatusAuthorizedWhenInUse ||
status == kCLAuthorizationStatusAuthorizedAlways) {
NSLog(@"[WiFiHelper] Location permission granted after request (legacy callback)");
[WiFiHelperLocationDelegate startSSIDFetchFlow];
} else if (status == kCLAuthorizationStatusDenied ||
status == kCLAuthorizationStatusRestricted) {
NSLog(@"[WiFiHelper] Location permission denied after request (legacy callback)");
if (callbackGameObject != nil && callbackMethod != nil) {
UnitySendMessage(callbackGameObject, callbackMethod, "");
}
}
}
// 收到第一个位置更新后再获取 SSIDNEHotspotNetwork.fetchCurrent 的隐式要求)
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
if (pendingSSIDFetch) {
NSLog(@"[WiFiHelper] First location update received, fetching SSID now");
pendingSSIDFetch = NO;
[manager stopUpdatingLocation];
FetchSSIDAndCallback();
}
}
// 定位失败时也兜底尝试获取一次
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(@"[WiFiHelper] Location manager failed: %@", error.localizedDescription);
if (pendingSSIDFetch) {
pendingSSIDFetch = NO;
[manager stopUpdatingLocation];
FetchSSIDAndCallback();
}
}
// 启动定位并等待第一个位置更新,超时则直接获取
+ (void)startSSIDFetchFlow {
if (![CLLocationManager locationServicesEnabled]) {
NSLog(@"[WiFiHelper] Location services disabled on device, fetching SSID anyway");
FetchSSIDAndCallback();
return;
}
if (locationManager == nil) {
locationManager = [[CLLocationManager alloc] init];
}
pendingSSIDFetch = YES;
[locationManager startUpdatingLocation];
// 超时兜底定位迟迟无响应如首次定位慢3 秒后直接获取
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
if (pendingSSIDFetch) {
NSLog(@"[WiFiHelper] Timeout waiting for location update, fetching SSID anyway");
pendingSSIDFetch = NO;
[locationManager stopUpdatingLocation];
FetchSSIDAndCallback();
}
});
}
@end
static WiFiHelperLocationDelegate *locationDelegate = nil;
extern "C" {
void RequestLocationPermission() {
if (locationManager != nil) return;
locationManager = [[CLLocationManager alloc] init];
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if (status == kCLAuthorizationStatusAuthorizedWhenInUse ||
status == kCLAuthorizationStatusAuthorizedAlways) {
NSLog(@"[WiFiHelper] Location permission already granted (status: %d)", (int)status);
// 位置权限已获得但还需要启动定位才能让WiFi API工作
[locationManager startUpdatingLocation];
// 短暂启动后停止(不需要持续定位,只是激活权限链)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[locationManager stopUpdatingLocation];
});
return;
}
if (status == kCLAuthorizationStatusNotDetermined) {
NSLog(@"[WiFiHelper] Requesting WhenInUse location permission");
[locationManager requestWhenInUseAuthorization];
} else {
NSLog(@"[WiFiHelper] Location permission denied/restricted (status: %d)", (int)status);
}
}
/// <summary>
/// 请求位置权限(如未授权),授权完成后自动获取 WiFi 名称并回调 C#。
/// 已授权时立即获取并回调。
/// </summary>
void RequestWiFiSSIDNative(const char *gameObjectName, const char *methodName) {
if (callbackGameObject != nil) {
free((void *)callbackGameObject);
}
if (callbackMethod != nil) {
free((void *)callbackMethod);
}
callbackGameObject = gameObjectName ? strdup(gameObjectName) : nil;
callbackMethod = methodName ? strdup(methodName) : nil;
if (locationManager == nil) {
locationManager = [[CLLocationManager alloc] init];
}
if (locationDelegate == nil) {
locationDelegate = [[WiFiHelperLocationDelegate alloc] init];
}
locationManager.delegate = locationDelegate;
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if (status == kCLAuthorizationStatusAuthorizedWhenInUse ||
status == kCLAuthorizationStatusAuthorizedAlways) {
NSLog(@"[WiFiHelper] Location already granted, fetching SSID directly");
[WiFiHelperLocationDelegate startSSIDFetchFlow];
return;
}
if (status == kCLAuthorizationStatusNotDetermined) {
NSLog(@"[WiFiHelper] Requesting location permission, will fetch SSID on grant");
[locationManager requestWhenInUseAuthorization];
} else {
NSLog(@"[WiFiHelper] Location permission denied/restricted (status: %d)", (int)status);
if (callbackGameObject != nil && callbackMethod != nil) {
UnitySendMessage(callbackGameObject, callbackMethod, "");
}
}
}
const char* GetWiFiSSID() {
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
BOOL locationEnabled = [CLLocationManager locationServicesEnabled];
NSLog(@"[WiFiHelper] GetWiFiSSID: auth=%d servicesEnabled=%d", (int)status, (int)locationEnabled);
__block NSString *resultSSID = nil;
__block BOOL finished = NO;
if (@available(iOS 14.0, *)) {
NSLog(@"[WiFiHelper] Using NEHotspotNetwork.fetchCurrent");
[NEHotspotNetwork fetchCurrentWithCompletionHandler:^(NEHotspotNetwork * _Nullable network) {
if (network && network.SSID.length > 0) {
resultSSID = network.SSID;
} else {
NSLog(@"[WiFiHelper] NEHotspotNetwork fetchCurrent returned nil");
}
finished = YES;
}];
// 用 CFRunLoop 等待,不阻塞主线程事件处理
NSTimeInterval deadline = [[NSDate date] timeIntervalSinceReferenceDate] + 3.0;
while (!finished && [[NSDate date] timeIntervalSinceReferenceDate] < deadline) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
if (resultSSID != nil) {
NSLog(@"[WiFiHelper] NEHotspotNetwork SSID: %@", resultSSID);
} else {
NSLog(@"[WiFiHelper] NEHotspotNetwork returned nil (finished=%d)", finished);
}
}
// Fallback
if (resultSSID == nil) {
NSLog(@"[WiFiHelper] Fallback to CNCopyCurrentNetworkInfo");
NSArray *interfaces = (__bridge_transfer NSArray *)CNCopySupportedInterfaces();
if (interfaces != nil) {
for (NSString *interfaceName in interfaces) {
NSDictionary *info = (__bridge_transfer NSDictionary *)CNCopyCurrentNetworkInfo((__bridge CFStringRef)interfaceName);
if (info && info[(NSString *)kCNNetworkInfoKeySSID]) {
resultSSID = info[(NSString *)kCNNetworkInfoKeySSID];
break;
}
}
}
}
if (resultSSID == nil) {
NSLog(@"[WiFiHelper] No WiFi SSID found.");
return NULL;
}
NSLog(@"[WiFiHelper] SSID: %@", resultSSID);
const char *utf8String = [resultSSID UTF8String];
size_t len = strlen(utf8String) + 1;
char *result = (char *)malloc(len);
if (result == NULL) return NULL;
memcpy(result, utf8String, len);
return result;
}
}