iOS 13 問(wèn)題解決以及蘋果登錄,暗黑模式

本文對(duì)應(yīng)github地址iOS 13 問(wèn)題解決以及蘋果登錄,如果由于github調(diào)整導(dǎo)致資源找不到或細(xì)節(jié)更改笼蛛,請(qǐng)?jiān)L問(wèn)github

本文掘金地址

本文直接搬磚,隨便看看就行

iOS 13 (Xcode11編譯時(shí))問(wèn)題解決以及蘋果登錄

  • KVC修改私有屬性可能Crash(不是所有鲤竹,不是所有闰蚕,不是所有)泵督,需要用別的姿勢(shì)替代皮迟。

* ##### UITextField的私有屬性_placeholderLabel的字體顏色,

如 ``` [textField setValue:color forKeyPath:@"_placeholderLabel.textColor"]; ``` 會(huì)crash辅搬。

那么該怎么辦呢唯沮?下面提供幾種姿勢(shì)

###### 姿勢(shì)一:采用富文本形式 

``` 
_textField.attributedPlaceholder = [[NSMutableAttributedString alloc] initWithString:placeholder attributes:@{NSForegroundColorAttributeName : color}];
```

###### 姿勢(shì)二:new方式創(chuàng)建一個(gè)新label(太low不建議用)

```
// 必須new創(chuàng)建,如果alloc-init創(chuàng)建還是crash(兩種方式差別自行g(shù)oogle堪遂,不是BD)
UILabel * placeholderLabel = [UILabel new];
placeholderLabel.text = @"666";
placeholderLabel.textColor = [UIColor blueColor];
[_textField setValue: placeholderLabel forKey:@"_placeholderLabel"];//new創(chuàng)建這里并沒(méi)有crash
```

###### 姿勢(shì)三:Runtime

```
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
UILabel *placeholderLabel = object_getIvar(_textField, ivar);
placeholderLabel.textColor = color;
```

* ##### [searchBar valueForKey:@"_searchField"]; 取值崩潰

```
- (UITextField *)ddy_SearchField {
    #ifdef __IPHONE_13_0
    if (@available(iOS 13.0, *)) {
        return self.searchTextField;
    }
    #endif
    return [self valueForKey:@"_searchField"];
}
```

所以修改UISearchBar占位字符可以把上面的結(jié)合使用
  • 模態(tài)彈出時(shí) modalPresentationStyle 改變了默認(rèn)值

    • 在iOS13之前的版本中, UIViewController的UIModalPresentationStyle屬性默認(rèn)是UIModalPresentationFullScreen介蛉,而在iOS13中變成了UIModalPresentationPageSheet。
    • 我們需要在presentViewController時(shí)蚤氏,設(shè)置一下UIModalPresentationStyle甘耿,就可以達(dá)到舊的效果。
    • 如果PageSheet想控制下拉dismiss竿滨,modalInPresentation可以控制

    該分類所在github工程

    UIViewController+DDYPresent.h下載該文件

    /// 一個(gè)一個(gè)改浪費(fèi)時(shí)間佳恬,適合版本迭代中逐步替換;
    /// 直接重寫-modalPresentationStyle 侵入性太大于游,造成系統(tǒng)彈出也被重置毁葱,或者某個(gè)控制器想改變樣式都不能,不太友好
    /// 所以用一個(gè)類方法控制全局贰剥,一個(gè)實(shí)例方法控制具體某個(gè)控制器實(shí)例樣式倾剿。
        
    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface UIViewController (DDYPresent)
    
    /// 自動(dòng)調(diào)整模態(tài)彈出樣式時(shí)要排除的控制器(如果未設(shè)置則使用內(nèi)置)
    /// @param controllerNameArray 模態(tài)彈出的控制器名稱數(shù)組
    + (void)ddy_ExcludeControllerNameArray:(NSArray<NSString *> *)controllerNameArray;
    
    /// 是否自動(dòng)調(diào)整模態(tài)彈出全屏樣式
    /// NO:表示不自動(dòng)調(diào)整,保持默認(rèn)蚌成,可能全屏樣式也可能其他樣式
    /// YES:表示調(diào)整為全屏樣式
    /// 如果是排除的控制器數(shù)組包含的控制器則默認(rèn)NO
    /// 如果不在排除的控制器數(shù)組內(nèi)包含則默認(rèn)YES
    @property (nonatomic, assign) BOOL ddy_AutoSetModalPresentationStyleFullScreen;
    
    @end
    
    NS_ASSUME_NONNULL_END
        
    

    UIViewController+DDYPresent.m下載該文件

```
#import "UIViewController+DDYPresent.h"
#import <objc/runtime.h>

@implementation UIViewController (DDYPresent)

static NSArray *excludeControllerNameArray;

+ (void)changeOriginalSEL:(SEL)orignalSEL swizzledSEL:(SEL)swizzledSEL {
    Method originalMethod = class_getInstanceMethod([self class], orignalSEL);
    Method swizzledMethod = class_getInstanceMethod([self class], swizzledSEL);
    if (class_addMethod([self class], orignalSEL, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
        class_replaceMethod([self class], swizzledSEL, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSEL = @selector(presentViewController:animated:completion:);
        SEL swizzledSEL = @selector(ddy_PresentViewController:animated:completion:);
        [self changeOriginalSEL:originalSEL swizzledSEL:swizzledSEL];
    });
}

- (void)ddy_PresentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
    if (@available(iOS 13.0, *)) {
        if (viewControllerToPresent.ddy_AutoSetModalPresentationStyleFullScreen) {
            viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen;
        }
    }
    [self ddy_PresentViewController:viewControllerToPresent animated:flag completion:completion];
}

- (void)setDdy_AutoSetModalPresentationStyleFullScreen:(BOOL)ddy_AutoSetModalPresentationStyleFullScreen {
    objc_setAssociatedObject(self, @selector(ddy_AutoSetModalPresentationStyleFullScreen), @(ddy_AutoSetModalPresentationStyleFullScreen), OBJC_ASSOCIATION_ASSIGN);
}

- (BOOL)ddy_AutoSetModalPresentationStyleFullScreen {
    NSNumber *obj = objc_getAssociatedObject(self, @selector(ddy_AutoSetModalPresentationStyleFullScreen));
    return obj ? [obj boolValue] : ![UIViewController ddy_IsExcludeSetModalPresentationStyleFullScreen:NSStringFromClass(self.class)];
}

// MARK: - 類方法
// MARK: 全局設(shè)置排除的控制器
+ (void)ddy_ExcludeControllerNameArray:(NSArray<NSString *> *)controllerNameArray {
    excludeControllerNameArray = controllerNameArray;
}

// MARK: 如果沒(méi)有外部設(shè)置則使用內(nèi)置的排除數(shù)組
+ (NSArray<NSString *> *)ddy_InnerExcludeControllerNameArray {
    NSMutableArray *nameArray = [NSMutableArray array];
    [nameArray addObject:@"UIImagePickerController"];
    [nameArray addObject:@"UIAlertController"];
    [nameArray addObject:@"UIActivityViewController"];
    [nameArray addObject:@"UIDocumentInteractionController"];
    [nameArray addObject:@"SLComposeViewController"]; //  #import <Social/Social.h>
    [nameArray addObject:@"SLComposeServiceViewController"]; // #import <Social/Social.h>
    [nameArray addObject:@"UIMenuController"];
    [nameArray addObject:@"SFSafariViewController"]; // API_AVAILABLE(ios(9.0)) #import <SafariServices/SafariServices.h>
    [nameArray addObject:@"SKStoreReviewController"]; // API_AVAILABLE(ios(10.3), macos(10.14)) #import <StoreKit/StoreKit.h>
    [nameArray addObject:@"SKStoreProductViewController"]; // API_AVAILABLE(ios(6.0)) #import <StoreKit/StoreKit.h>
    return nameArray;
}

// MARK: 是否是要排除自動(dòng)設(shè)置的控制器
+ (BOOL)ddy_IsExcludeSetModalPresentationStyleFullScreen:(NSString *)className {
    NSArray *nameArray = excludeControllerNameArray ?: [UIViewController ddy_InnerExcludeControllerNameArray];
    return [nameArray containsObject:className];
}

@end    
```
  • 獲取DeviceToken姿勢(shì)改變

    iOS13之前
    NSString *myToken = [deviceToken description];
    myToken = [myToken stringByReplacingOccurrencesOfString: @"<" withString: @""];
    myToken = [myToken stringByReplacingOccurrencesOfString: @">" withString: @""];
    myToken = [myToken stringByReplacingOccurrencesOfString: @" " withString: @""];
    
    iOS13之后(不建議這樣寫)

    為什么不建議這樣寫APNs device tokens are of variable length. Do not hard-code their size

    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
        if (![deviceToken isKindOfClass:[NSData class]]) return;
        const unsigned *tokenBytes = [deviceToken bytes];
        NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
                              ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
                              ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
                              ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
        NSLog(@"deviceToken:%@",hexToken);
    }
    
##### 推薦的寫法


```
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    if (!deviceToken || ![deviceToken isKindOfClass:[NSData class]] || deviceToken.length==0) {
        return;
    }
    NSString *(^getDeviceToken)(void) = ^() {
        if (@available(iOS 13.0, *)) {
            const unsigned char *dataBuffer = (const unsigned char *)deviceToken.bytes;
            NSMutableString *myToken  = [NSMutableString stringWithCapacity:(deviceToken.length * 2)];
            for (int i = 0; i < deviceToken.length; i++) {
                [myToken appendFormat:@"%02x", dataBuffer[i]];
            }
            return (NSString *)[myToken copy];
        } else {
            NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"<>"];
            NSString *myToken = [[deviceToken description] stringByTrimmingCharactersInSet:characterSet];
            return [myToken stringByReplacingOccurrencesOfString:@" " withString:@""];
        }
    };
    NSString *myToken = getDeviceToken();
    NSLog(@"%@", myToken);
}
```
  • UIWebView

    • 蘋果已經(jīng)從iOS13禁止UIWebView方式了前痘,需要更換WKWebView(過(guò)渡期仍可用,只是郵件警告担忧,目前不影響審核)
    • UIWebView 無(wú)縫轉(zhuǎn)WKWebView 正在開發(fā)中芹缔,請(qǐng)?zhí)砑雨P(guān)注隨時(shí)發(fā)布!!!
  • 即將廢棄的 LaunchImage

    • 從iOS 8 蘋果引入了 LaunchScreen,我們可以設(shè)置LaunchScreen來(lái)作為啟動(dòng)頁(yè)瓶盛。當(dāng)然最欠,現(xiàn)在你還可以使用LaunchImage來(lái)設(shè)置啟動(dòng)圖。不過(guò)使用LaunchImage的話惩猫,要求我們必須提供各種屏幕尺寸的啟動(dòng)圖芝硬,來(lái)適配各種設(shè)備,隨著蘋果設(shè)備尺寸越來(lái)越多轧房,這種方式顯然不夠 Flexible拌阴。而使用 LaunchScreen的話,情況會(huì)變的很簡(jiǎn)單锯厢, LaunchScreen是支持AutoLayout+SizeClass的皮官,所以適配各種屏幕都不在話下脯倒。
    • 從2020年4月開始实辑,所有使? iOS13 SDK的 App將必須提供 LaunchScreen捺氢,LaunchImage即將退出歷史舞臺(tái)。
  • MPMoviePlayerController 被禁止

    • 這個(gè)用的人應(yīng)該不多了剪撬,如果是則更換姿勢(shì)摄乒,如用AVPlayer
  • UITableViewCell中cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;方框問(wèn)題

    • 低版本Xcode(如Xcode10)編譯運(yùn)行在iOS13上則會(huì)出現(xiàn)方框,如果用Xcode11編譯則不會(huì)出現(xiàn)
  • 增加蘋果登錄(可選)

  1. 開發(fā)者網(wǎng)站 在 Sign in with Apple 開啟功能

  2. Xcode 里面 Signing & Capabilities 開啟 Sign in with Apple 功能

  3. 利用 ASAuthorizationAppleIDButton

    ASAuthorizationAppleIDButton *button = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeSignIn style:ASAuthorizationAppleIDButtonStyleWhite];
    [button addTarget:self action:@selector(signInWithApple) forControlEvents:UIControlEventTouchUpInside];
    button.center = self.view.center;
    button.bounds = CGRectMake(0, 0, 40, 40); // 寬度過(guò)小就沒(méi)有文字了残黑,只剩圖標(biāo)
    [self.view addSubview:button];
    
  4. ASAuthorizationControllerPresentationContextProviding

    • ASAuthorizationControllerPresentationContextProviding 就一個(gè)方法馍佑,主要是告訴 ASAuthorizationController 展示在哪個(gè) window 上
    #pragma mark - ASAuthorizationControllerPresentationContextProviding
    
    - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0))
    {
        return self.view.window;
    }
    
  5. Authorization 發(fā)起授權(quán)登錄請(qǐng)求

    - (void)signInWithApple API_AVAILABLE(ios(13.0)) {
        ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc] init];
        ASAuthorizationAppleIDRequest *request = [provider createRequest];
        request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
        
        ASAuthorizationController *vc = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
        vc.delegate = self;
        vc.presentationContextProvider = self;
        [vc performRequests];
    }
    
    • ASAuthorizationAppleIDProvider 這個(gè)類比較簡(jiǎn)單,頭文件中可以看出梨水,主要用于創(chuàng)建一個(gè) ASAuthorizationAppleIDRequest 以及獲取對(duì)應(yīng) userID 的用戶授權(quán)狀態(tài)拭荤。在上面的方法中我們主要是用于創(chuàng)建一個(gè) ASAuthorizationAppleIDRequest ,用戶授權(quán)狀態(tài)的獲取后面會(huì)提到疫诽。
    • 給創(chuàng)建的 request 設(shè)置 requestedScopes 舅世,這是個(gè) ASAuthorizationScope 數(shù)組,目前只有兩個(gè)值奇徒,ASAuthorizationScopeFullName 和 ASAuthorizationScopeEmail 雏亚,根據(jù)需求去設(shè)置即可。
    • 然后摩钙,創(chuàng)建 ASAuthorizationController 罢低,它是管理授權(quán)請(qǐng)求的控制器,給其設(shè)置 delegate 和 presentationContextProvider 胖笛,最后啟動(dòng)授權(quán) performRequests
  6. 授權(quán)回調(diào)處理

    #pragma mark - ASAuthorizationControllerDelegate
    
    - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0))
    {
        if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]])       {
            ASAuthorizationAppleIDCredential *credential = authorization.credential;
            
            NSString *state = credential.state;
            NSString *userID = credential.user;
            NSPersonNameComponents *fullName = credential.fullName;
            NSString *email = credential.email;
            NSString *authorizationCode = [[NSString alloc] initWithData:credential.authorizationCode encoding:NSUTF8StringEncoding]; // refresh token
            NSString *identityToken = [[NSString alloc] initWithData:credential.identityToken encoding:NSUTF8StringEncoding]; // access token
            ASUserDetectionStatus realUserStatus = credential.realUserStatus;
            
            NSLog(@"state: %@", state);
            NSLog(@"userID: %@", userID);
            NSLog(@"fullName: %@", fullName);
            NSLog(@"email: %@", email);
            NSLog(@"authorizationCode: %@", authorizationCode);
            NSLog(@"identityToken: %@", identityToken);
            NSLog(@"realUserStatus: %@", @(realUserStatus));
        }
    }
    
    - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0))
    {
        NSString *errorMsg = nil;
        switch (error.code) {
            case ASAuthorizationErrorCanceled:
                errorMsg = @"用戶取消了授權(quán)請(qǐng)求";
                break;
            case ASAuthorizationErrorFailed:
                errorMsg = @"授權(quán)請(qǐng)求失敗";
                break;
            case ASAuthorizationErrorInvalidResponse:
                errorMsg = @"授權(quán)請(qǐng)求響應(yīng)無(wú)效";
                break;
            case ASAuthorizationErrorNotHandled:
                errorMsg = @"未能處理授權(quán)請(qǐng)求";
                break;
            case ASAuthorizationErrorUnknown:
                errorMsg = @"授權(quán)請(qǐng)求失敗未知原因";
                break;
        }
        NSLog(@"%@", errorMsg);
    }
    
    • User ID: Unique, stable, team-scoped user ID网持,蘋果用戶唯一標(biāo)識(shí)符,該值在同一個(gè)開發(fā)者賬號(hào)下的所有 App 下是一樣的长踊,開發(fā)者可以用該唯一標(biāo)識(shí)符與自己后臺(tái)系統(tǒng)的賬號(hào)體系綁定起來(lái)功舀。

    • Verification data: Identity token, code,驗(yàn)證數(shù)據(jù)之斯,用于傳給開發(fā)者后臺(tái)服務(wù)器日杈,然后開發(fā)者服務(wù)器再向蘋果的身份驗(yàn)證服務(wù)端驗(yàn)證本次授權(quán)登錄請(qǐng)求數(shù)據(jù)的有效性和真實(shí)性,詳見(jiàn) Sign In with Apple REST API佑刷。如果驗(yàn)證成功莉擒,可以根據(jù) userIdentifier 判斷賬號(hào)是否已存在,若存在瘫絮,則返回自己賬號(hào)系統(tǒng)的登錄態(tài)涨冀,若不存在,則創(chuàng)建一個(gè)新的賬號(hào)麦萤,并返回對(duì)應(yīng)的登錄態(tài)給 App鹿鳖。

    • Account information: Name, verified email扁眯,蘋果用戶信息,包括全名翅帜、郵箱等姻檀。

    • Real user indicator: High confidence indicator that likely real user,用于判斷當(dāng)前登錄的蘋果賬號(hào)是否是一個(gè)真實(shí)用戶涝滴,取值有:unsupported绣版、unknown、likelyReal歼疮。

    • 失敗情況會(huì)走 authorizationController:didCompleteWithError: 這個(gè)方法杂抽,具體看代碼吧

  7. 其他情況的處理

    • 用戶終止 App 中使用 Sign in with Apple 功能
    • 用戶在設(shè)置里注銷了 AppleId

    這些情況下,App 需要獲取到這些狀態(tài)韩脏,然后做退出登錄操作缩麸,或者重新登錄。
    我們需要在 App 啟動(dòng)的時(shí)候赡矢,通過(guò) getCredentialState:completion: 來(lái)獲取當(dāng)前用戶的授權(quán)狀態(tài)

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        
        if (@available(iOS 13.0, *)) {
            NSString *userIdentifier = 鑰匙串中取出的 userIdentifier;
            if (userIdentifier) {
                ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
                [appleIDProvider getCredentialStateForUserID:userIdentifier
                                                  completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState,
                                                               NSError * _Nullable error)
                {
                    switch (credentialState) {
                        case ASAuthorizationAppleIDProviderCredentialAuthorized:
                            // The Apple ID credential is valid
                            break;
                        case ASAuthorizationAppleIDProviderCredentialRevoked:
                            // Apple ID Credential revoked, handle unlink
                            break;
                        case ASAuthorizationAppleIDProviderCredentialNotFound:
                            // Credential not found, show login UI
                            break;
                    }
                }];
            }
        }
        
        return YES;
    }
    

    ASAuthorizationAppleIDProviderCredentialState 解析如下:

    • ASAuthorizationAppleIDProviderCredentialAuthorized 授權(quán)狀態(tài)有效杭朱;
    • ASAuthorizationAppleIDProviderCredentialRevoked 上次使用蘋果賬號(hào)登錄的憑據(jù)已被移除,需解除綁定并重新引導(dǎo)用戶使用蘋果登錄济竹;
    • ASAuthorizationAppleIDProviderCredentialNotFound 未登錄授權(quán)痕檬,直接彈出登錄頁(yè)面,引導(dǎo)用戶登錄
  8. 還可以通過(guò)通知方法來(lái)監(jiān)聽(tīng) revoked 狀態(tài)送浊,可以添加 ASAuthorizationAppleIDProviderCredentialRevokedNotification 這個(gè)通知

    - (void)observeAppleSignInState {
        if (@available(iOS 13.0, *)) {
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(handleSignInWithAppleStateChanged:)
                                                         name:ASAuthorizationAppleIDProviderCredentialRevokedNotification
                                                       object:nil];
        }
    }
    
    - (void)handleSignInWithAppleStateChanged:(NSNotification *)notification {
        // Sign the user out, optionally guide them to sign in again
        NSLog(@"%@", notification.userInfo);
    }
    
  9. One more thing

    蘋果還把 iCloud KeyChain password 集成到了這套 API 里梦谜,我們?cè)谑褂玫臅r(shí)候,只需要在創(chuàng)建 request 的時(shí)候袭景,多創(chuàng)建一個(gè) ASAuthorizationPasswordRequest 唁桩,這樣如果 KeyChain 里面也有登錄信息的話,可以直接使用里面保存的用戶名和密碼進(jìn)行登錄耸棒。代碼如下

    - (void)perfomExistingAccountSetupFlows API_AVAILABLE(ios(13.0))
    {
        ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
        ASAuthorizationAppleIDRequest *authAppleIDRequest = [appleIDProvider createRequest];
        ASAuthorizationPasswordRequest *passwordRequest = [[ASAuthorizationPasswordProvider new] createRequest];
        
        NSMutableArray <ASAuthorizationRequest *>* array = [NSMutableArray arrayWithCapacity:2];
        if (authAppleIDRequest) {
            [array addObject:authAppleIDRequest];
        }
        if (passwordRequest) {
            [array addObject:passwordRequest];
        }
        NSArray <ASAuthorizationRequest *>* requests = [array copy];
        
        ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests];
        authorizationController.delegate = self;
        authorizationController.presentationContextProvider = self;
        [authorizationController performRequests];
    }
    
    #pragma mark - ASAuthorizationControllerDelegate
    
    - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0))
    {
        if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
            ASPasswordCredential *passwordCredential = authorization.credential;
            NSString *userIdentifier = passwordCredential.user;
            NSString *password = passwordCredential.password;
            
            NSLog(@"userIdentifier: %@", userIdentifier);
            NSLog(@"password: %@", password);
        }
    }
    
  10. 官方Demo

  • Flutter1.9.1+hotfix2 Dart2.5 在iOS13真機(jī)上啟動(dòng)不了

    錯(cuò)誤信息 Device doesn't support wireless sync. AMDeviceStartService(device, CFSTR("com.apple.debugserver"), &gdbfd, NULL)

    解決方案

    • 姿勢(shì)一:

    更新Flutter

    flutter upgrade
    
    • 姿勢(shì)二:

    暫時(shí)切換到dev或master

    flutter channel dev
    // 或下面
    // flutter channel master
    // 然后執(zhí)行
    flutter doctor
    // dart2.6 flutter1.10
    
  • 獲取不到wifiSSID(wifi名)

    Dear Developer,
    
    As we announced at WWDC19, we're making changes to further protect user privacy and prevent unauthorized location tracking. Starting with iOS 13, the CNCopyCurrentNetworkInfo API will no longer return valid Wi-Fi SSID and BSSID information. Instead, the information returned by default will be: 
    
    SSID: “Wi-Fi” or “WLAN” (“WLAN" will be returned for the China SKU)
    BSSID: "00:00:00:00:00:00" 
    
    If your app is using this API, we encourage you to adopt alternative approaches that don’t require Wi-Fi or network information. Valid SSID and BSSID information from CNCopyCurrentNetworkInfo will still be provided to VPN apps, apps that have used NEHotspotConfiguration to configure the current Wi-Fi network, and apps that have obtained permission to access user location through Location Services. 
    
    Test your app on the latest iOS 13 beta to make sure it works properly. If your app requires valid Wi-Fi SSID and BSSID information to function, you can do the following:
    For accessory setup apps, use the NEHotSpotConfiguration API, which now has the option to pass a prefix of the SSID hotspot your app expects to connect to.
    For other types of apps, use the CoreLocation API to request the user’s consent to access location information.
    
    Learn more by reading the updated documentation or viewing the the Advances in Networking session video from WWDC19. You can also submit a TSI for code-level support. 
    
    Best regards,
    Apple Developer Relations
    

    蘋果為了所謂隱私安全不讓直接獲取到wifiSSID了荒澡,然后還告知,如果是使用 NEHotspotConfiguration 的app可以獲取,另外其他類型app需要用CoreLocation請(qǐng)求位置權(quán)限,用戶同意后才可以獲取与殃。

    • 使用NEHotspotConfiguration的app
    // 連接WiFi
    NEHotspotConfiguration *config = [[NEHotspotConfiguration alloc] initWithSSID:@"wifi名" passphrase:@"密碼" isWEP:NO];
    NEHotspotConfigurationManager *manager = [NEHotspotConfigurationManager sharedManager];
    [manager applyConfiguration: config completionHandler:^(NSError * _Nullable error) {
        NSLog(@"error :%@",error);
    }];
    
    // 獲取wifiSSID 
    - (NSString *)wifiSSID {
        NSString *wifiSSID = nil;
        
        CFArrayRef wifiInterfaces = CNCopySupportedInterfaces();
        
        if (!wifiInterfaces) {
            return nil;
        }
        
        NSArray *interfaces = (__bridge NSArray *)wifiInterfaces;
        
        for (NSString *interfaceName in interfaces) {
            CFDictionaryRef dictRef = CNCopyCurrentNetworkInfo((__bridge CFStringRef)(interfaceName));
            if (dictRef) {
                NSDictionary *networkInfo = (__bridge NSDictionary *)dictRef;
                wifiSSID = [networkInfo objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID];
                CFRelease(dictRef);
            }
        }
        CFRelease(wifiInterfaces);
        return wifiName;
    }
    
    • 請(qǐng)求位置權(quán)限征求用戶同意后獲取wifiSSID

    推薦使用封裝好的請(qǐng)求權(quán)限方式https://github.com/RainOpen/DDYAuthManager

    #import <SystemConfiguration/CaptiveNetwork.h>
    
    - (void)ddy_wifiSSID:(void (^)(NSString *wifiSSID))wifiSSID {
        
        void (^callBack)(NSString *) = ^(NSString *wifiName) {
            if (wifiSSID) {
                wifiSSID(nil);
            }
        };
        
        void (^getWifiName)(void) = ^(){
            CFArrayRef wifiInterfaces = CNCopySupportedInterfaces();
            if (!wifiInterfaces) {
                callBack(nil);
                return;
            }
        
            NSString *wifiName = nil;
            for (NSString *interfaceName in (__bridge_transfer NSArray *)wifiInterfaces) {
                CFDictionaryRef dictRef = CNCopyCurrentNetworkInfo((__bridge CFStringRef)(interfaceName));
                if (dictRef) {
                    NSDictionary *networkInfo = (__bridge NSDictionary *)dictRef;
                    wifiName = [networkInfo objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID];
                    CFRelease(dictRef);
                }
            }
            CFRelease(wifiInterfaces);
            callBack(wifiName);
        };
        
        if ([CLLocationManager locationServicesEnabled]) {
            [DDYAuthManager ddy_LocationAuthType:DDYCLLocationTypeAuthorized alertShow:YES success:^{
                getWifiName();
            } fail:^(CLAuthorizationStatus authStatus) {
                NSLog(@"定位服務(wù)被拒絕单山,彈窗告訴無(wú)法獲取wifiSSID,請(qǐng)到設(shè)置開啟定位權(quán)限");
                callBack(nil);
            }];
        } else {
            NSLog(@"定位服務(wù)不可用");
            callBack(nil);
        }
    }
    
    • VPN舊版應(yīng)用
  • Xcode10往iOS13上編譯運(yùn)行提示 Could not find Developer Disk Image

    1. 下載開發(fā)包
    2. 強(qiáng)制退出Xcode(必須退出干凈)
    3. 前往"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport"粘貼解壓縮文件(以自己實(shí)際路徑實(shí)際名稱)
  • iOS 13 UITabBar上分割線呢操作

    原來(lái)設(shè)置分割線的方式失效了

    [[UITabBar appearance] setBackgroundImage:[UIImage new]];
    [[UITabBar appearance] setShadowImage:[UIImage new]];
    

    最新更改TabBar上細(xì)線方式實(shí)例幅疼,利用蘋果提供的新API米奸,為所欲為(改圖片,改顏色)

    // OC
    if (@available(iOS 13, *)) {
        #ifdef __IPHONE_13_0
        UITabBarAppearance *appearance = [self.tabBar.standardAppearance copy];
        appearance.backgroundImage = [UIImage new];
        appearance.shadowImage = [UIImage imageNamed:@"Dotted_Line"];
        appearance.shadowColor = [UIColor clearColor];
        self.tabBar.standardAppearance = appearance;
        #endif
    } else {
        self.tabBar.backgroundImage = [UIImage new];
        self.tabBar.shadowImage = [UIImage imageNamed:@"Dotted_Line"];
    }
    
    // Swift
    if #available(iOS 13, *) {
        let appearance = self.tabBar.standardAppearance.copy()
        appearance.backgroundImage = UIImage()
        appearance.shadowImage = UIImage()
        appearance.shadowColor = .clear
        self.tabBar.standardAppearance = appearance
    } else {
        self.tabBar.shadowImage = UIImage()
        self.tabBar.backgroundImage = UIImage()
    }
    
  • iOS 13 Push后Pop回來(lái)tabbar選中文字顏色變系統(tǒng)藍(lán)色(OC代碼爽篷,swift一個(gè)樣)

    • 姿勢(shì)一
    self.tabBar.tinColor = color;
    
    • 姿勢(shì)二
    if (@available(iOS 10.0, *)) {
        self.tabBar.unselectedItemTintColor = color;
    }
    
  • 暗黑模式

  • library not found for -l stdc++.6.0.9
    • Xcode10開始去除了C++6.0.9

    • 如果非用不可铡溪,下載文件

    // 文件夾 1漂辐、2、3棕硫、4 中的文件分別對(duì)應(yīng)復(fù)制到Xcode10中的以下4個(gè)目錄中即可(Xcode11目錄可能有變更)
    // 假設(shè)默認(rèn)安裝目錄且Xcode.app命名
    
    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/
    
    /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/
    
    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/
    
    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/
    

    更新Xcode11目錄變更

    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/
    ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 變更為 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/
    
    • 換個(gè)姿勢(shì)試試
    1. TARGETS–Build Phases–Link Binary With Libraries髓涯,刪除6.0.9依賴,
    2. 需要的話對(duì)應(yīng)添加libc++.tdb饲帅、libstdc++.tdb
    3. TARGETS–Build Settings–Other Linker Flags复凳,刪除 -l”stdc++.6.0.9”
    4. 如果是第三庫(kù)引用了C++6.0.9庫(kù)瘤泪,那就只能聯(lián)系服務(wù)方修改了
    
  • Multiple commands produce 'xxx/Info.plist'

    • Xcode10開始變更編譯系統(tǒng)灶泵,如果項(xiàng)目所在空間存在多個(gè)Info.plist則報(bào)錯(cuò)
    xcworkspace項(xiàng)目: Xcode左上角菜單欄 File –> Workspace Settings –> Build System – >Legacy Build System
    xcodeproj項(xiàng)目:Xcode左上角菜單欄 –> File –> Project Settings –> Build System –> Legacy Build System
    
  • 升級(jí)Xcode后xib報(bào)錯(cuò) Failed to find or create execution context for description ...

    • 可以萬(wàn)能重啟,還可以对途。赦邻。。
    sudo killall -9 com.apple.CoreSimulator.CoreSimulatorService
    
    # 將你xcode中Developer文件夾位置放進(jìn)來(lái)
    sudo xcode-select -s  /Applications/Xcode.app/Contents/Developer
    
    xcrun simctl erase all
    
  • 友盟導(dǎo)致崩潰 +[_LSDefaults sharedInstance]

    // 本工地大工沒(méi)實(shí)際驗(yàn)證。膳犹。恬吕。
    @implementation NSObject (DDYExtension)
    
    + (void)changeOriginalSEL:(SEL)orignalSEL swizzledSEL:(SEL)swizzledSEL {
        Method originalMethod = class_getInstanceMethod([self class], orignalSEL);
        Method swizzledMethod = class_getInstanceMethod([self class], swizzledSEL);
        if (class_addMethod([self class], orignalSEL, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
            class_replaceMethod([self class], swizzledSEL, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            SEL originalSEL = @selector(doesNotRecognizeSelector:);
            SEL swizzledSEL = @selector(ddy_doesNotRecognizeSelector:);
            [self changeOriginalSEL:originalSEL swizzledSEL:swizzledSEL];
        });
    }
    
    + (void)ddy_doesNotRecognizeSelector:(SEL)aSelector{
        // 處理 _LSDefaults 崩潰問(wèn)題
        if([[self description] isEqualToString:@"_LSDefaults"] && (aSelector == @selector(sharedInstance))){
            //冷處理...
            return;
        }
        [self ddy_doesNotRecognizeSelector:aSelector];
    }
    
  • UITextField的leftView和rightView設(shè)置UIImageView或UIButton等被系統(tǒng)強(qiáng)(變)奸(窄)了。须床。铐料。

    • 姿勢(shì)一:臨時(shí)解決方案
      • 交換leftView/rightView的getter、setter豺旬,
      • 然后包裝一個(gè)containerView父視圖钠惩,并將containerView給了相應(yīng)左右視圖
      • 取視圖則先取出containerView,從containerView中取出真正想要的視圖
      • 注意處理在containerView上的位置族阅。篓跛。。
    • 姿勢(shì)二:通用型方案
      • 想全局更改坦刀,交換 -leftViewRectForBounds: (最好留出控制屬性以在外部隨意更改玩轉(zhuǎn))
      • 如果采用子類愧沟,重寫 -leftViewRectForBounds:
      - (CGRect)leftViewRectForBounds:(CGRect)bounds {
          CGRect iconRect = [super leftViewRectForBounds:bounds];
          iconRect.origin.x = 3; // 可用屬性控制 
          iconRect.size.width = 6; // 可用屬性控制 
          return iconRect;
      }   
      
  • UIScrollView滾動(dòng)條指示器偏移

    // 屏幕旋轉(zhuǎn)可能會(huì)觸發(fā)系統(tǒng)對(duì)滾動(dòng)條的自動(dòng)修正,如果沒(méi)有修改需求鲤遥,關(guān)閉該特性即可
    #ifdef __IPHONE_13_0
    if (@available(iOS 13.0, *)) {
       self.automaticallyAdjustsScrollIndicatorInsets = NO;
    }
    #endif
    
  • WKWebView 中測(cè)量頁(yè)面內(nèi)容高度的方式變更

    iOS 13以前 document.body.scrollHeight
    iOS 13開始 document.documentElement.scrollHeight

  • CBCenterManager 藍(lán)牙使用權(quán)限

    iOS13之前不用申請(qǐng)權(quán)限沐寺,iOS13開始需要申請(qǐng)權(quán)限

    <key>NSBluetoothAlwaysUsageDescription</key> 
    <string>App想使用藍(lán)牙,是否同意</string>`
    
  • Xcode10的過(guò)時(shí)問(wèn)題渴频,蟲洞傳送門

  • Xcode11 創(chuàng)建新工程在AppDelegate.m中設(shè)置window不生效

    • Xcode11把入口交給了scene(為SwiftUI多場(chǎng)景打基礎(chǔ)的)
    • 想要換回AppDelegate入口
    • 打開Info.plist點(diǎn)擊減號(hào)刪除Application Scene Mainfest
    • 再刪除SceneDelegate兩個(gè)文件
    • 刪除AppDelegate.m中UISceneSession lifecycle兩個(gè)方法
    • 最后AppDelegate.h中添加 @property (strong, nonatomic) UIWindow *window;
參考
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末芽丹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子卜朗,更是在濱河造成了極大的恐慌拔第,老刑警劉巖咕村,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蚊俺,居然都是意外死亡懈涛,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門泳猬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)批钠,“玉大人描滔,你說(shuō)我怎么就攤上這事曲掰。” “怎么了柑司?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵忙上,是天一觀的道長(zhǎng)拷呆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)疫粥,這世上最難降的妖魔是什么茬斧? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮梗逮,結(jié)果婚禮上项秉,老公的妹妹穿的比我還像新娘。我一直安慰自己慷彤,他們只是感情好娄蔼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瞬欧,像睡著了一般贷屎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上艘虎,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天唉侄,我揣著相機(jī)與錄音,去河邊找鬼野建。 笑死属划,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的候生。 我是一名探鬼主播同眯,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼唯鸭!你這毒婦竟也來(lái)了须蜗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎明肮,沒(méi)想到半個(gè)月后菱农,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柿估,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年循未,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秫舌。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡的妖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出足陨,到底是詐尸還是另有隱情嫂粟,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布钠右,位于F島的核電站赋元,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏飒房。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一媚值、第九天 我趴在偏房一處隱蔽的房頂上張望狠毯。 院中可真熱鬧,春花似錦褥芒、人聲如沸嚼松。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)献酗。三九已至,卻和暖如春坷牛,著一層夾襖步出監(jiān)牢的瞬間罕偎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工京闰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颜及,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓蹂楣,卻偏偏與公主長(zhǎng)得像俏站,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痊土,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容