1.接入微信无法回调onresp IOS9.0之后废弃原本的两个方法
-(BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url;
-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation;
更改为:(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary*)options;
2.Ios 微信sdk接入
@1下载地址 :https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419319164&token=&lang=zh_CN
@2 导入sdk
@3项目配置 设置支持http请求 并且添加白名单weixin 和wechat
@4 关闭bitcode
@5设置URL types(url schemes 就是你申请的appid)
@6build phases 设置
@7 上代码
实现 .mm文件
#import "WeixinInterface.h" #import "AFNetworking.h" #import "UnityAppController.h" static WeixinInterface * s_shareApp=nil; static NSString *Urlkey=nil; static NSString *UrlValue=nil; #define WeiXinID @"你申请的aapid" #define WeiXinSecret @"secrest" #define GameObjectName "在unity'中回调的代码名字" #define MethodName "回调方法" @implementation WeixinInterface #pragma mark - LifeCycle +(instancetype)sharedManager { static dispatch_once_t onceToken; static WeixinInterface *instance; dispatch_once(&onceToken, ^{ instance = [[WeixinInterface alloc] init]; }); return instance; } -(void)destroy { if (s_shareApp) { s_shareApp=nil; } } @synthesize filePath; #pragma mark - View lifecycle - (void)OnLogin:(NSString *)js { NSLog(@"OnLogin"); SendAuthReq *req = [[SendAuthReq alloc] init]; req.scope = @"snsapi_userinfo"; //req.openID = URL_APPID; req.state = @"1245"; //appdelegate = [UIApplication sharedApplication].delegate; //appdelegate.wxDelegate = self; [WXApi sendReq:req]; [WXApi sendAuthReq:req viewController:UnityGetGLViewController() delegate:self]; } - (bool) isWXAppInstalled { return [WXApi isWXAppInstalled]; } - (bool) isWXAppSupportApi { return [WXApi isWXAppSupportApi]; } -(void)loginSuccessByCode:(NSString *)code { NSLog(@"code %@",code); AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; manager.requestSerializer = [AFJSONRequestSerializer serializer];//请求 manager.responseSerializer = [AFHTTPResponseSerializer serializer];//响应 manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"application/json", @"text/json",@"text/plain", nil]; //通过 appid secret 认证code . 来发送获取 access_token的请求 [manager GET:[NSString stringWithFormat:@"https://api.weixin.qq.com/sns/oauth2/access_token?appid=%@&secret=%@&code=%@&grant_type=authorization_code",WeiXinID,WeiXinSecret,code] parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) { } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { //获得access_token,然后根据access_token获取用户信息请求。 NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil]; NSLog(@"dic %@",dic); /* access_token 接口调用凭证 expires_in access_token接口调用凭证超时时间,单位(秒) refresh_token 用户刷新access_token openid 授权用户唯一标识 scope 用户授权的作用域,使用逗号(,)分隔 unionid 当且仅当该移动应用已获得该用户的userinfo授权时,才会出现该字段 */ NSString* accessToken=[dic valueForKey:@"access_token"]; NSString* openID=[dic valueForKey:@"openid"]; [self requestUserInfoByToken:accessToken andOpenid:openID]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"error %@",error.localizedFailureReason); }]; } - (BOOL)handleOpenURL:(NSURL *) url { //url:ruileiqipai://?key=roomid&value=965816&from=singlemessage&isappinstalled=0&reload=1 NSString *urlStr = [url absoluteString]; NSLog(@"url:%@",urlStr); [self GetURLParameters:urlStr]; return [WXApi handleOpenURL:url delegate:self]; } /*! 微信回调,不管是登录还是分享成功与否,都是走这个方法 @brief 发送一个sendReq后,收到微信的回应 * * 收到一个来自微信的处理结果。调用一次sendReq后会收到onResp。 * 可能收到的处理结果有SendMessageToWXResp、SendAuthResp等。 * @param resp具体的回应内容,是自动释放的 */ -(void) onResp:(BaseResp*)resp{ NSLog(@"resp %d",resp.errCode); /* enum WXErrCode { WXSuccess = 0, 成功 WXErrCodeCommon = -1, 普通错误类型 WXErrCodeUserCancel = -2, 用户点击取消并返回 WXErrCodeSentFail = -3, 发送失败 WXErrCodeAuthDeny = -4, 授权失败 WXErrCodeUnsupport = -5, 微信不支持 }; */ if ([resp isKindOfClass:[SendAuthResp class]]) { //授权登录的类。 if (resp.errCode == 0) { //成功。 //这里处理回调的方法 。 通过代理吧对应的登录消息传送过去。 if([resp isKindOfClass:[SendAuthResp class]]) // 登录授权 { SendAuthResp *resp2 = (SendAuthResp *)resp; //[[WeixinLogin getIntance] loginSuccessByCode:resp2.code]; [self loginSuccessByCode:resp2.code]; } }else{ //失败 NSLog(@"error %@",resp.errStr); UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@"登录失败" message:[NSString stringWithFormat:@"reason : //%@",resp.errStr] delegate:nil cancelButtonTitle:NSLocalizedString(@"Close(关闭)",nil) otherButtonTitles:nil]; [alerView show]; } } } -(void)requestUserInfoByToken:(NSString *)token andOpenid:(NSString *)openID{ NSLog(@"requestUserInfoByToken"); AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; manager.requestSerializer = [AFJSONRequestSerializer serializer]; manager.responseSerializer = [AFHTTPResponseSerializer serializer]; [manager GET:[NSString stringWithFormat:@"https://api.weixin.qq.com/sns/userinfo?access_token=%@&openid=%@",token,openID] parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) { } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSDictionary *dic = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil]; NSLog(@"dic ==== %@",dic); //NSError *jsonError; //NSString *responseData = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&jsonError]; NSError *error; NSData *jsStr = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&error]; NSString *jsonData= [[NSString alloc] initWithData:jsStr encoding:NSUTF8StringEncoding]; NSLog(@"#####responseData1 = %@",jsonData); //NSLog(@"#####responseData = %@",responseData); UnitySendMessage(GameObjectName, MethodName, [jsonData cStringUsingEncoding:NSUTF8StringEncoding]); if(Urlkey != nil && UrlValue != nil) { NSString *keyandValue=[NSString stringWithFormat:@"%@&%@",Urlkey,UrlValue]; NSLog(@"keyandValue:%@",keyandValue); UnitySendMessage(GameObjectName, GetURLKeyAndValue, [keyandValue cStringUsingEncoding:NSUTF8StringEncoding]); Urlkey = nil; UrlValue = nil; } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"error %ld",(long)error.code); }]; } - (void)didReceiveMemoryWarning { //[super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
@8在Appcontrl中实现app注册
#import "UnityAppController.h" #import "UnityAppController+ViewHandling.h" #import "UnityAppController+Rendering.h" #import "iPhone_Sensors.h" #import <CoreGraphics/CoreGraphics.h> #import <QuartzCore/QuartzCore.h> #import <QuartzCore/CADisplayLink.h> #import <Availability.h> #import <OpenGLES/EAGL.h> #import <OpenGLES/EAGLDrawable.h> #import <OpenGLES/ES2/gl.h> #import <OpenGLES/ES2/glext.h> #include <mach/mach_time.h> // MSAA_DEFAULT_SAMPLE_COUNT was moved to iPhone_GlesSupport.h // ENABLE_INTERNAL_PROFILER and related defines were moved to iPhone_Profiler.h // kFPS define for removed: you can use Application.targetFrameRate (30 fps by default) // DisplayLink is the only run loop mode now - all others were removed #include "CrashReporter.h" #include "UI/OrientationSupport.h" #include "UI/UnityView.h" #include "UI/Keyboard.h" #include "UI/SplashScreen.h" #include "Unity/InternalProfiler.h" #include "Unity/DisplayManager.h" #include "Unity/EAGLContextHelper.h" #include "Unity/GlesHelper.h" #include "Unity/ObjCRuntime.h" #include "PluginBase/AppDelegateListener.h" #include <assert.h> #include <stdbool.h> #include <sys/types.h> #include <unistd.h> #include <sys/sysctl.h> #import "Libraries/Plugins/iOS/WXApi.h" #import "Libraries/Plugins/iOS/Control/WXApiManager.h" #import "Libraries/Plugins/iOS/SDK/WeixinInterface.h" // we assume that app delegate is never changed and we can cache it, instead of re-query UIApplication every time UnityAppController* _UnityAppController = nil; // Standard Gesture Recognizers enabled on all iOS apps absorb touches close to the top and bottom of the screen. // This sometimes causes an ~1 second delay before the touch is handled when clicking very close to the edge. // You should enable this if you want to avoid that delay. Enabling it should not have any effect on default iOS gestures. #define DISABLE_TOUCH_DELAYS 1 // we keep old bools around to support "old" code that might have used them bool _ios42orNewer = false, _ios43orNewer = false, _ios50orNewer = false, _ios60orNewer = false, _ios70orNewer = false; bool _ios80orNewer = false, _ios81orNewer = false, _ios82orNewer = false, _ios83orNewer = false, _ios90orNewer = false, _ios91orNewer = false; bool _ios100orNewer = false, _ios101orNewer = false, _ios102orNewer = false, _ios103orNewer = false; bool _ios110orNewer = false, _ios111orNewer = false, _ios112orNewer = false; // was unity rendering already inited: we should not touch rendering while this is false bool _renderingInited = false; // was unity inited: we should not touch unity api while this is false bool _unityAppReady = false; // see if there's a need to do internal player pause/resume handling // // Typically the trampoline code should manage this internally, but // there are use cases, videoplayer, plugin code, etc where the player // is paused before the internal handling comes relevant. Avoid // overriding externally managed player pause/resume handling by // caching the state bool _wasPausedExternal = false; // should we skip present on next draw: used in corner cases (like rotation) to fill both draw-buffers with some content bool _skipPresent = false; // was app "resigned active": some operations do not make sense while app is in background bool _didResignActive = false; // was startUnity scheduled: used to make startup robust in case of locking device static bool _startUnityScheduled = false; bool _supportsMSAA = false; #if UNITY_SUPPORT_ROTATION // Required to enable specific orientation for some presentation controllers: see supportedInterfaceOrientationsForWindow below for details NSInteger _forceInterfaceOrientationMask = 0; #endif @implementation UnityAppController @synthesize unityView = _unityView; @synthesize unityDisplayLink = _displayLink; @synthesize rootView = _rootView; @synthesize rootViewController = _rootController; @synthesize mainDisplay = _mainDisplay; @synthesize renderDelegate = _renderDelegate; @synthesize quitHandler = _quitHandler; #if UNITY_SUPPORT_ROTATION @synthesize interfaceOrientation = _curOrientation; #endif - (id)init { if ((self = _UnityAppController = [super init])) { // due to clang issues with generating warning for overriding deprecated methods // we will simply assert if deprecated methods are present // NB: methods table is initied at load (before this call), so it is ok to check for override NSAssert(![self respondsToSelector: @selector(createUnityViewImpl)], @"createUnityViewImpl is deprecated and will not be called. Override createUnityView" ); NSAssert(![self respondsToSelector: @selector(createViewHierarchyImpl)], @"createViewHierarchyImpl is deprecated and will not be called. Override willStartWithViewController" ); NSAssert(![self respondsToSelector: @selector(createViewHierarchy)], @"createViewHierarchy is deprecated and will not be implemented. Use createUI" ); } return self; } - (void)setWindow:(id)object {} - (UIWindow*)window { return _window; } - (void)shouldAttachRenderDelegate {} - (void)preStartUnity {} - (void)startUnity:(UIApplication*)application { NSAssert(_unityAppReady == NO, @"[UnityAppController startUnity:] called after Unity has been initialized"); UnityInitApplicationGraphics(); // we make sure that first level gets correct display list and orientation [[DisplayManager Instance] updateDisplayListCacheInUnity]; UnityLoadApplication(); Profiler_InitProfiler(); [self showGameUI]; [self createDisplayLink]; UnitySetPlayerFocus(1); } extern "C" void UnityDestroyDisplayLink() { [GetAppController() destroyDisplayLink]; } extern "C" void UnityRequestQuit() { _didResignActive = true; if (GetAppController().quitHandler) GetAppController().quitHandler(); else exit(0); } #if UNITY_SUPPORT_ROTATION - (NSUInteger)application:(UIApplication*)application supportedInterfaceOrientationsForWindow:(UIWindow*)window { // No rootViewController is set because we are switching from one view controller to another, all orientations should be enabled if ([window rootViewController] == nil) return UIInterfaceOrientationMaskAll; // Some presentation controllers (e.g. UIImagePickerController) require portrait orientation and will throw exception if it is not supported. // At the same time enabling all orientations by returning UIInterfaceOrientationMaskAll might cause unwanted orientation change // (e.g. when using UIActivityViewController to "share to" another application, iOS will use supportedInterfaceOrientations to possibly reorient). // So to avoid exception we are returning combination of constraints for root view controller and orientation requested by iOS. // _forceInterfaceOrientationMask is updated in willChangeStatusBarOrientation, which is called if some presentation controller insists on orientation change. return [[window rootViewController] supportedInterfaceOrientations] | _forceInterfaceOrientationMask; } - (void)application:(UIApplication*)application willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation duration:(NSTimeInterval)duration { // Setting orientation mask which is requested by iOS: see supportedInterfaceOrientationsForWindow above for details _forceInterfaceOrientationMask = 1 << newStatusBarOrientation; } #endif #if !PLATFORM_TVOS - (void)application:(UIApplication*)application didReceiveLocalNotification:(UILocalNotification*)notification { AppController_SendNotificationWithArg(kUnityDidReceiveLocalNotification, notification); UnitySendLocalNotification(notification); } #endif #if UNITY_USES_REMOTE_NOTIFICATIONS - (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo { AppController_SendNotificationWithArg(kUnityDidReceiveRemoteNotification, userInfo); UnitySendRemoteNotification(userInfo); } - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { AppController_SendNotificationWithArg(kUnityDidRegisterForRemoteNotificationsWithDeviceToken, deviceToken); UnitySendDeviceToken(deviceToken); } #if !PLATFORM_TVOS - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler { AppController_SendNotificationWithArg(kUnityDidReceiveRemoteNotification, userInfo); UnitySendRemoteNotification(userInfo); if (handler) { handler(UIBackgroundFetchResultNoData); } } #endif - (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error { AppController_SendNotificationWithArg(kUnityDidFailToRegisterForRemoteNotificationsWithError, error); UnitySendRemoteNotificationError(error); // alas people do not check remote notification error through api (which is clunky, i agree) so log here to have at least some visibility ::printf("\nFailed to register for remote notifications:\n%s\n\n", [[error localizedDescription] UTF8String]); } #endif -(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:( NSDictionary *)options { return [WXApi handleOpenURL:url delegate:[WeixinInterface sharedManager]]; } - (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions { return YES; } - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { ::printf("-> applicationDidFinishLaunching()\n"); // send notfications #if !PLATFORM_TVOS if (UILocalNotification* notification = [launchOptions objectForKey: UIApplicationLaunchOptionsLocalNotificationKey]) UnitySendLocalNotification(notification); if ([UIDevice currentDevice].generatesDeviceOrientationNotifications == NO) [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; #endif UnityInitApplicationNoGraphics([[[NSBundle mainBundle] bundlePath] UTF8String]); [self selectRenderingAPI]; [UnityRenderingView InitializeForAPI: self.renderingAPI]; _window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds]; _unityView = [self createUnityView]; [DisplayManager Initialize]; _mainDisplay = [DisplayManager Instance].mainDisplay; [_mainDisplay createWithWindow: _window andView: _unityView]; [self createUI]; [self preStartUnity]; [WXApi registerApp:@"你申请的appid"]; // if you wont use keyboard you may comment it out at save some memory [KeyboardDelegate Initialize]; //#if !PLATFORM_TVOS && DISABLE_TOUCH_DELAYS //for (UIGestureRecognizer *g in _window.gestureRecognizers) //{ // g.delaysTouchesBegan = false; //} //#endif return YES; } - (void)applicationDidEnterBackground:(UIApplication*)application { ::printf("-> applicationDidEnterBackground()\n"); } - (void)applicationWillEnterForeground:(UIApplication*)application { ::printf("-> applicationWillEnterForeground()\n"); // applicationWillEnterForeground: might sometimes arrive *before* actually initing unity (e.g. locking on startup) if (_unityAppReady) { // if we were showing video before going to background - the view size may be changed while we are in background [GetAppController().unityView recreateRenderingSurfaceIfNeeded]; } } - (void)applicationDidBecomeActive:(UIApplication*)application { ::printf("-> applicationDidBecomeActive()\n"); [self removeSnapshotView]; if (_unityAppReady) { if (UnityIsPaused() && _wasPausedExternal == false) { UnityWillResume(); UnityPause(0); } if (_wasPausedExternal) { if (UnityIsFullScreenPlaying()) TryResumeFullScreenVideo(); } UnitySetPlayerFocus(1); } else if (!_startUnityScheduled) { _startUnityScheduled = true; [self performSelector: @selector(startUnity:) withObject: application afterDelay: 0]; } _didResignActive = false; } - (void)removeSnapshotView { // do this on the main queue async so that if we try to create one // and remove in the same frame, this always happens after in the same queue dispatch_async(dispatch_get_main_queue(), ^{ if (_snapshotView) { [_snapshotView removeFromSuperview]; _snapshotView = nil; } }); } - (void)applicationWillResignActive:(UIApplication*)application { ::printf("-> applicationWillResignActive()\n"); if (_unityAppReady) { UnitySetPlayerFocus(0); _wasPausedExternal = UnityIsPaused(); if (_wasPausedExternal == false) { // do pause unity only if we dont need special background processing // otherwise batched player loop can be called to run user scripts int bgBehavior = UnityGetAppBackgroundBehavior(); if (bgBehavior == appbgSuspend || bgBehavior == appbgExit) { // Force player to do one more frame, so scripts get a chance to render custom screen for minimized app in task manager. // NB: UnityWillPause will schedule OnApplicationPause message, which will be sent normally inside repaint (unity player loop) // NB: We will actually pause after the loop (when calling UnityPause). UnityWillPause(); [self repaint]; UnityPause(1); // this is done on the next frame so that // in the case where unity is paused while going // into the background and an input is deactivated // we don't mess with the view hierarchy while taking // a view snapshot (case 760747). dispatch_async(dispatch_get_main_queue(), ^{ // if we are active again, we don't need to do this anymore if (!_didResignActive) { return; } _snapshotView = [self createSnapshotView]; if (_snapshotView) [_rootView addSubview: _snapshotView]; }); } } } _didResignActive = true; } - (void)applicationDidReceiveMemoryWarning:(UIApplication*)application { ::printf("WARNING -> applicationDidReceiveMemoryWarning()\n"); UnityLowMemory(); } - (void)applicationWillTerminate:(UIApplication*)application { ::printf("-> applicationWillTerminate()\n"); Profiler_UninitProfiler(); UnityCleanup(); extern void SensorsCleanup(); SensorsCleanup(); } @end void AppController_SendNotification(NSString* name) { [[NSNotificationCenter defaultCenter] postNotificationName: name object: GetAppController()]; } void AppController_SendNotificationWithArg(NSString* name, id arg) { [[NSNotificationCenter defaultCenter] postNotificationName: name object: GetAppController() userInfo: arg]; } void AppController_SendUnityViewControllerNotification(NSString* name) { [[NSNotificationCenter defaultCenter] postNotificationName: name object: UnityGetGLViewController()]; } extern "C" UIWindow* UnityGetMainWindow() { return GetAppController().mainDisplay.window; } extern "C" UIViewController* UnityGetGLViewController() { return GetAppController().rootViewController; } extern "C" UIView* UnityGetGLView() { return GetAppController().unityView; } extern "C" ScreenOrientation UnityCurrentOrientation() { return GetAppController().unityView.contentOrientation; } bool LogToNSLogHandler(LogType logType, const char* log, va_list list) { NSLogv([NSString stringWithUTF8String: log], list); return true; } static void AddNewAPIImplIfNeeded(); // From https://stackoverflow.com/questions/4744826/detecting-if-ios-app-is-run-in-debugger static bool isDebuggerAttachedToConsole(void) // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). { int junk; int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); assert(junk == 0); // We're being debugged if the P_TRACED flag is set. return ((info.kp_proc.p_flag & P_TRACED) != 0); } void UnityInitTrampoline() { #if ENABLE_CRASH_REPORT_SUBMISSION SubmitCrashReportsAsync(); #endif InitCrashHandling(); _ios42orNewer = _ios43orNewer = _ios50orNewer = _ios60orNewer = _ios70orNewer = _ios80orNewer = true; NSString* version = [[UIDevice currentDevice] systemVersion]; #define CHECK_VER(s) [version compare: s options: NSNumericSearch] != NSOrderedAscending _ios81orNewer = CHECK_VER(@"8.1"), _ios82orNewer = CHECK_VER(@"8.2"), _ios83orNewer = CHECK_VER(@"8.3"); _ios90orNewer = CHECK_VER(@"9.0"), _ios91orNewer = CHECK_VER(@"9.1"); _ios100orNewer = CHECK_VER(@"10.0"), _ios101orNewer = CHECK_VER(@"10.1"), _ios102orNewer = CHECK_VER(@"10.2"), _ios103orNewer = CHECK_VER(@"10.3"); _ios110orNewer = CHECK_VER(@"11.0"), _ios111orNewer = CHECK_VER(@"11.1"), _ios112orNewer = CHECK_VER(@"11.2"); #undef CHECK_VER AddNewAPIImplIfNeeded(); #if !TARGET_IPHONE_SIMULATOR // Use NSLog logging if a debugger is not attached, otherwise we write to stdout. if (!isDebuggerAttachedToConsole()) UnitySetLogEntryHandler(LogToNSLogHandler); #endif } // sometimes apple adds new api with obvious fallback on older ios. // in that case we simply add these functions ourselves to simplify code static void AddNewAPIImplIfNeeded() { if (![[CADisplayLink class] instancesRespondToSelector: @selector(setPreferredFramesPerSecond:)]) { IMP CADisplayLink_setPreferredFramesPerSecond_IMP = imp_implementationWithBlock(^void(id _self, NSInteger fps) { typedef void (*SetFrameIntervalFunc)(id, SEL, NSInteger); UNITY_OBJC_CALL_ON_SELF(_self, @selector(setFrameInterval:), SetFrameIntervalFunc, (int)(60.0f / fps)); }); class_replaceMethod([CADisplayLink class], @selector(setPreferredFramesPerSecond:), CADisplayLink_setPreferredFramesPerSecond_IMP, CADisplayLink_setPreferredFramesPerSecond_Enc); } if (![[UIScreen class] instancesRespondToSelector: @selector(nativeScale)]) { IMP UIScreen_NativeScale_IMP = imp_implementationWithBlock(^CGFloat(id _self) { return ((UIScreen*)_self).scale; }); class_replaceMethod([UIScreen class], @selector(nativeScale), UIScreen_NativeScale_IMP, UIScreen_nativeScale_Enc); } if (![[UIScreen class] instancesRespondToSelector: @selector(maximumFramesPerSecond)]) { IMP UIScreen_MaximumFramesPerSecond_IMP = imp_implementationWithBlock(^NSInteger(id _self) { return 60; }); class_replaceMethod([UIScreen class], @selector(maximumFramesPerSecond), UIScreen_MaximumFramesPerSecond_IMP, UIScreen_maximumFramesPerSecond_Enc); } if (![[UIView class] instancesRespondToSelector: @selector(safeAreaInsets)]) { IMP UIView_SafeAreaInsets_IMP = imp_implementationWithBlock(^UIEdgeInsets(id _self) { return UIEdgeInsetsZero; }); class_replaceMethod([UIView class], @selector(safeAreaInsets), UIView_SafeAreaInsets_IMP, UIView_safeAreaInsets_Enc); } }