本文對(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可以控制
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)
-
增加蘋果登錄(可選)
去開發(fā)者網(wǎng)站 在 Sign in with Apple 開啟功能
Xcode 里面 Signing & Capabilities 開啟 Sign in with Apple 功能
-
利用 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];
-
ASAuthorizationControllerPresentationContextProviding
- ASAuthorizationControllerPresentationContextProviding 就一個(gè)方法馍佑,主要是告訴 ASAuthorizationController 展示在哪個(gè) window 上
#pragma mark - ASAuthorizationControllerPresentationContextProviding - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)) { return self.view.window; }
-
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
-
授權(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è)方法杂抽,具體看代碼吧
-
其他情況的處理
- 用戶終止 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)用戶登錄
-
還可以通過(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); }
-
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); } }
-
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
- 下載開發(fā)包
- 強(qiáng)制退出Xcode(必須退出干凈)
- 前往"/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; }
-
-
暗黑模式
- 關(guān)于暗黑模式也是開發(fā)者可選擇性適配的內(nèi)容悴晰,這里不贅述了,提供個(gè)文章參考
- 不想適配暗黑則在Info.plist中添加Key:User Interface Style,值String的Light
- QiShare iOS13 DarkMode適配
- iOS13 暗黑模式(Dark Mode)適配之OC版
-
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]
- 更新版本
- 如果暫時(shí)沒(méi)法更新(理由实檀,我擦惶洲,屎山),臨時(shí)方案宇宙中轉(zhuǎn)站→Saylor_lone博客:iOS 13 適配 ING...
// 本工地大工沒(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;
參考