Một số chú ý khi code với iOS 13 (Phần 2)

UIWebView deprecated

Sau khi phát hành iOS 13.0, tài liệu mô tả về UIWebView đã chỉ ra UIWebView chỉ support từ iOS 2.0 đến 12.0. Như vậy, Apple đã chính thức deprecate UIWebView. Thực tế là UIWebView cũng tiềm ẩn nhiều bug và performance kém hơn nhiều so với WKWebView.

Kể từ giờ trở đi, nếu bạn update app có sử dụng UIWebView lên App Store thì sẽ nhận được một email phản hồi mã ITMS-90809, suggest rằng UIWebView đã bị removed. Từ bản cập nhật sau bạn sẽ phải loại bỏ UIWebView ra khỏi app.

Dear Developer, We identified one or more issues with a recent delivery for your app, "xxx". Your delivery was successful, but you may wish to correct the following issues in your next delivery: ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of apps that use UIWebView APIs . See https://developer.apple.com/documentation/uikit/uiwebview for more information. After you’ve corrected the issues, you can use Xcode or Application Loader to upload a new binary to App Store Connect. Best regards, The App Store Team

Solution: Sử dụng WKWebView để thay thế cho UIWebView đã lạc hậu.

UISearchDisplayController crash

Trước iOS 8.0, mỗi khi cần hiển thị dữ liệu dạng table view và có search bar, chúng ta thường sử dụng kết hợp UISearchDisplayControllerUISearchBar.

Sau iOS 8.0 thì Apple đã deprecate UISearchDisplayController và thay thế combo trên bằng UISearchController. Tuy nhiên kể từ iOS 13.0, thì UISearchDisplayController đã hoàn toàn bị xóa khỏi SDK.

Nếu bạn vẫn cố tình sử dụng thì app sẽ crash với log infomation sau:

*** Terminating app due to uncaught exception 'NSGenericException', reason: 'UISearchDisplayController is no longer supported when linking against this version of iOS. Please migrate your application to UISearchController.' 

Solution: Sử dụng UISearchController thay thế cho UISearchDisplayController.

MPMoviePlayerController deprecated

Class MPMoviePlayerController trong MediaPlayer.framework được sử dụng để play video từ trước iOS 9.0. Class này bị deprecate từ iOS 9.0 và đến nay iOS 13.0 thì cũng hoàn toàn bị xóa bỏ. Nếu sử dụng sẽ throw exception như sau:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'

Solution: Sử dụng AVPlayerViewController trong AVKit.

Bluetooth request permission description changed

Như tiêu đề thì từ iOS 13.0, Apple đã thay đổi field name của bluetooth permission description trong Info.plist từ NSBluetoothAlwaysUsageDescription thành NSBluetoothPeripheralUsageDescription.

For apps with a deployment target of iOS 13 and later, use NSBluetoothAlwaysUsageDescription instead.

Các bản update không implement đúng sẽ nhận được email warning mã ITMS-90683:

Dear Developer, We identified one or more issues with a recent delivery for your app, "xxx". Please correct the following issues, then upload again. ITMS-90683: Missing Purpose String in Info.plist-Your app's code references one or more APIs that access sensitive user data. The app's Info.plist file should contain a NSBluetoothAlwaysUsageDescription key with a user-facing purpose string explaining clearly and completely why > your app needs the data. Starting Spring 2019, all apps submitted to the App Store that access user data are required to include a purpose string. If you're using external libraries or SDKs, they may reference APIs that require a purpose string. While your app might not use these > APIs, a purpose string is still required. You can contact the developer of the library or SDK and request they release a version of their code that doesn't contain the APIs. Learn more (https://developer.apple.com/documentation/uikit/protecting_the_user_s_privacy). Best regards, The App Store Team

Solution: Sử dụng cả hai key NSBluetoothAlwaysUsageDescription và NSBluetoothPeripheralUsageDescription cho các app support cả những version trước 13.0.

For deployment targets earlier than iOS 13, add both NSBluetoothAlwaysUsageDescription and NSBluetoothPeripheralUsageDescription to your app's Information Property List file.

CNCopyCurrentNetworkInfo usage

Kể từ iOS 12.0, các funtion trong CNCopyCurrentNetworkInfo cần phải enable Access WiFi Information Entitlement để có thể lấy được value chính xác.

Nhưng từ iOS 13.0, việc này còn trở lên khó khăn hơn khi mà app cần phải đạt một trong các điều kiện sau:

  1. Sử dụng Core Location và grant được location service permission.
  2. Sử dụng NEHotspotConfiguration để config các settings của mạng Wi-fi.
  3. Đang sử dụng VPN.

Apple thay đổi như vậy nhằm mục đích bảo vệ sự an toàn của user. Bởi vì vị trí địa lý của user có thể dễ dàng bị track dựa trên MAC address. Tương tự, các device bluetooth cũng có MAC address nên Apple thêm các permission chặt chẽ hơn về việc sử dụng permission bluetooth.

Solution: Theo như những yêu cầu trên thì đơn giản và dễ nhất, chúng ta có thể sử dụng CoreLocation để grant location service permission và sử dụng các funtion của CNCopyCurrentNetworkInfo.

LaunchImage deprecated

Trước phiên bản iOS 8.0, chúng ta vẫn thường sử dụng LaunchImage để setting tập hợp các ảnh launching cho app. Trong LaunchImage có nhiều screen size tương ứng với các ảnh khác nhau. Mỗi khi có device có screen size mới, chỉ cần thêm ảnh mới phù hợp với nó. Việc làm này khá thủ công và với sự ra đời của iOS 8.0, Apple đã giới thiệu thêm khái niệm LaunchScreen, bạn có thể setup màn hình loading của app thông qua Storyboard, rất trực quan, dễ dàng và flexible với các screen size khác nhau.

Tuy nhiên trong section Modernizing Your UI for iOS 13 thì kể từ tháng 4 năm 2020, tất cả các app trên App Store đều phải bắt buộc LaunchScreen.storyboard, không còn chấp nhận LaunchImage nữa.

Solution: Sử dụng LaunchScreen.storyboard thay cho LaunchImage.

UISegmentedControl default style changed

Default style của UISegmentedControl đã thay đổi: chữ đen trên nền trắng, thay vì chữ trắng trên nền xanh.

Thay đổi tint color bằng property tintColor sẽ không hoạt động. Thay vào đó, hãy sử dụng property mới selectedSegmentTintColor.

Project created by Xcode 11 runs black screen on lower version devices

Những project tạo bằng Xcode 11 khi build app và chạy trên các device iOS < 13.0 thì sẽ hiển thị màn hình đen. Sở dĩ bug này xảy ra vì Xcode 11 sử dụng khái niêm UIScene mới, mặc định để quản lý các UIWindow.

Project mới tạo ngoài file AppDelegate thì sẽ có thêm file SceneDelegate.

Điều này để chuẩn bị cho việc implement multi-process, multi-screen trên iPadOS mới. UIWindow sẽ không còn là một property trong AppDelegate, mà là UIScene. Mà các iOS < 13.0 thì không có UIScene.

Solution: Trong header của AppDelegate thêm property:

@property (strong, nonatomic) UIWindow *window;

WKWebView default popup style crash

Như đã đề cập, từ iOS 13.0, default style của modal view đã thay đổi.

Nếu view controller có UIModalPresentationPageSheetstyle và sử dụng WKWebView để browse đến photos library bằng HTML:

[_webView loadHTMLString:@"<input accept='image/*' type='file'>" baseURL:nil];

Khi tap vào button select photo, app sẽ crash với log:

*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Your application has presented a UIDocumentMenuViewController (<UIDocumentMenuViewController: 0x101226860>). 
In its current trait environment, the modalPresentationStyle of a UIDocumentMenuViewController with this style is UIModalPresentationPopover. 
You must provide location information for this popover through the view controller's popoverPresentationController. 
You must provide either a sourceView and sourceRect or a barButtonItem.  
If this information is not known when you present the view controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.'

Nguyễn nhân của crash này là do khi tap vào button để browse ảnh, hệ thống sẽ present một UIDocumentMenuViewController với style mặc định UIModalPresentationPopover từ một UIViewController hiện tại thuộc loại non-full screen. Vì vậy cần define sourceViewsourceRect giống barButtonItem trên iPad.

Solution: Cách 1: Set sourceView, sourceRect khi present:

- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
    [self setUIDocumentMenuViewControllerSoureViewsIfNeeded:viewControllerToPresent];
    [super presentViewController:viewControllerToPresent animated:flag completion:completion];
}

- (void)setUIDocumentMenuViewControllerSoureViewsIfNeeded:(UIViewController *)viewControllerToPresent{
    if (@available(iOS 13, *)) {
        if([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone && [viewControllerToPresent isKindOfClass:UIDocumentMenuViewController.class]){
            viewControllerToPresent.popoverPresentationController.sourceView = self.webView;
            viewControllerToPresent.popoverPresentationController.sourceRect = CGRectMake(15, 5, 1, 1);
        }
    }
}

// Trường hợp present từ UINavigationController
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
    if([self.viewControllers.lastObject isKindOfClass:WKWebViewController.class]){
        WKWebViewController *viewController = self.viewControllers.lastObject;
        [viewController setUIDocumentMenuViewControllerSoureViewsIfNeeded:viewControllerToPresent];
    }
    [super presentViewController:viewControllerToPresent animated:flag completion:completion];
}

Cách 2: Present full-screen view controller:

- (UIModalPresentationStyle)modalPresentationStyle {
    return UIModalPresentationFullScreen;
}

Source article: https://juejin.im/post/5d8af88ef265da5b6e0a23ac


All Rights Reserved