IOS 13 native end adaptation Raiders

Contents

1. KVC access to private properties

2. Modal pop-up ViewController default style change

3. Dark mode adaptation

4. LaunchImage To be obsolete

5. Added permission to always use Bluetooth

6. Sign With Apple

7. Push Device Token adaptation

< p>8. UIKit control changes

9. StatusBar new style


1. KVC access to private attributes

 This iOS 13 system upgrade has the widest impact on KVC access to modify private attributes. Developers are directly prohibited from obtaining or directly setting private attributes. The original intention of KVC is to allow developers to directly access and modify the attribute value of the object through the Key name, which is the most typical of UITextField _placeholderLabel, UISearchBar Of _searchField. Impact: App crash error code under iOS 13:

// placeholderLabel private attribute access

< span style="color: #008080;">[textField setValue:[UIColor redColor] forKeyPath:@”_placeholderLabel.textColor”];

[textField setValue:[UIFont boldSystemFontOfSize:16] forKeyPath:@”_placeholderLabel.font”];

// searchField private attributes Access

UISearchBar*searchBar=[[UISearchBaralloc]init];

UITextField*searchTextField=[searchBar valueForKey:@”_searchField”];

Solution:  Use NSMutableAttributedString rich text instead KVC access to UITextField‘s _placeholderLabel

textField.attributedPlaceholder=[[NSAttributedStringalloc]initWithString:@ “placeholder” attributes:@{NSForegroundColorAttributeName:[UIColordarkGrayColor], NSFontAttributeName:[UIFontsy stemFontOfSize:13]}];

Therefore, you can create a Category for UITextFeild, specifically for processing and modifying placeHolder< /code>Property providing method

#import "UITextField+ChangePlaceholder.h"

< p>

@implementation UITextField(Change)

- (void)setPlaceholderFont:(UIFont*)font {

[self setPlaceholderColor :nil font:font];

}

- (void)setPlaceholderColor:(UIColor*)color{

[self setPlaceholderColor:color font:nil];< /p>

}

- (void)setPlaceholderColor:(nullable UIColor*) color font:(nullable UIFont*)font{

if([selfcheckPlaceholderEmpty]){

return;

NSMutableAttributedString*placeholderAttriString=[[NSMutableAttributedStringalloc]initWithString:self.placeholder];

if( color) {

[placeholderAttriString addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(0, self.placeholder.length)];

[placeholderAttriString span>

}

if (font) {

[placeholderAttriString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, self.placeholder.length)];

}

[self setAttributedPlaceholder:placeholderAttriString];

}

- (BOOL)checkPlaceholderEmpty{

return(self.placeholder==nil)||([[self.placeholder stringByTrimmingCharactersInSet :[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]== 0);

}

About UISearchBar , You can traverse all its subviews, find the subview of the specified UITextField type, and then modify the attributes through the rich text method according to the above UITextField.

#import "UISearchBar+ChangePrivateTextFieldSubview.h"

@implementation UISearchBar(ChangePrivateTextFieldSubview)

/// Modify the TextField that comes with the SearchBar system

- (void)changeSearchTextFieldWithCompletionBlock:(void(^)(UITextField*textField))completionBlock{

if (!completionBlock) {

return;

}

UITextField *textField = [self findTextFieldWithView:self];

if (textField) {

< p> completionBlock(textField );

}

}

/// Recursive Traverse the subviews of UISearchBar and find UITextField

- (UITextField*)findTextFieldWithView:(UIView*)view{ p>

for (UIView*subview in view.subviews) {

if ([subview isKindOfClass:[UITextField class]]) {

return (UITextField*)subview;

else if (subview.subviews.count> 0){

return [selffindTextFieldWithView:subview];

return nil;

}

@end

PS: For how to find out whether your App project uses private APIs, please refer to the iOS Finding Private API Articles


2. Modal pop-up ViewController default style changed

 Modal pop-up properties UIModalPresentationStyle is set by default under iOS 13 For the new feature of UIModalPresentationAutomatic, the display style is more cool, and the modal pop-up window can be closed with a pull-down gesture. If the modal pop-up window properties have been specified when the original modal pops up the ViewController, you can ignore the change. If you want to keep the original default modal pop-up effect in iOS 13. This can be achieved through the Method Swizzling method exchange of runtime.

#import"UIViewController+ChangeDefaultPresentStyle.h "

@implementation UIViewController (ChangeDefaultPresentStyle)

+ (void)load{

Static dispatch_once_t onceToken;

dispatch_once(&onceToken,^{

Class class = [self class];

//Replacement method

SEL originalSelector = @selector(presentViewController:animated:completion:);

SEL SEL newSelector = @selector(new_presentViewController:animated:com pletion:);

Method originalMethod=class_getInstanceMethod(class,originalSelector);

Method newMethod = class_getInstanceMethod(class, newSelector);;

BOOL DidAddMethod=

class_addMethod(class,

originalSelector,< /span>

method_getImplementation(newMethod),

method_getTypeEncoding(newMethod));

If (didAddMethod) {

class_replaceMethod(class,

newSelector,

method_getImplementation(originalMethod),

Method_getTypeEncoding(originalMethod));

else style{

"color: #008080;"> method_exchangeImplementations(originalMethod, newMethod);

});

}

- (void)new_presentViewController:(UIViewController*)viewControllerToPresentanimated:(BOOL )flagcompletion:(void(^)(void))completion{

< span style="color: #008080;"> viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen;

[self new_presentViewController:viewControllerToPresent animated:flag completion:completion];

}

@end

#import "UIViewController+ChangeDefaultPresentStyle.h"

< span style="color: #008080;">

@implementationUIViewController(ChangeDefaultPresentStyle)

+ (void)load{

static dispatch_once_tonceToken;

dispatch_once(&onceToken,^{

Class class=[self class ];

//Replacement method

SEL originalSelector = @selector(presentViewController:animated:completion:);

SELnewSelector=@selector(new_presentViewController:animated:completion:);

MethodoriginalMethod=class_getInstanceMethod(class , OriginalSelector);

Method newMethod = class_getInstanceMethod(class, newSelector);;

< span style="color: #008080;"> BOOL didAddMethod=

class_addMethod(class,

< p> OriginalSelector,

method_getImplement method

p>

method_getTypeEncoding(newMethod));

if (didAddMethod) {

Class_replaceMethod(class,

NewSelector,

#008080 ;"> Method_getImplementation(originalMethod),

methods(p>Methods)PoriginalMethod_getImplementation(originalMethod),

style="color: #008080;">else{

method_exchangeImplementations(originalMethod,newMethod);

>

}

});

}

- (void)new_presentViewController:(UIViewController*)viewControllerToPre sent animated:(BOOL)flagcompletion:(void(^)(void))completion{

viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen;

[selfnew_presentViewController:viewControllerToPresent animated:flagcompletion:completion];

}

@end


3. Dark mode adaptation

For the launch of dark mode, Apple officially recommends that all third-party apps adapt as soon as possible. Currently, there is no forced app to adapt to dark mode. Therefore, the dark mode adaptation range can now adopt the following three strategies:

  • Turn off dark mode globally

  • Turn off the dark mode on the specified page

  • Global adaptation dark mode

3.1. Turn off dark mode globally

Scheme 1: Add a content to the project Info.plist file, the Key is User Interface Style , set the value type to String and set it to Light.

Scheme 2: The code forces to turn off the dark mode and set the current window to the Light state.

if(@available(iOS 13.0,*)){

self.window.overrideUserInterfaceStyle= UIUserInterfaceStyleLight;

}

3.2 Specify page to turn off dark mode

Starting from Xcode 11 and iOS 13, UIViewController and View have added properties overrideUserInterfaceStyle. If the property of the View object is set to the specified mode, the object and sub-objects will be forced to use the specified mode Display, will not follow the system mode change.

  • Setting this property of ViewController will affect the view controller's view and child view controllers adopt this mode

  • Setting this attribute of View will affect the view and all its subviews to adopt this mode

  • Setting this attribute of Window will affect the All content adopts this style, including the root view controller and all controllers that display content in this window

3.3 Global adaptation to dark mode

Adapting to dark mode mainly starts from two aspects: image resource adaptation and color adaptation

Image resource adaptation

 Open the image resource management library Assets.xcassets, select the image material item that needs to be adapted, and open the Inspectors toolbar on the far right, Find the Appearances option and set it to Any, Dark mode. At this time, Dark Appearance will be added under the item, just drag the material in the dark mode into it. The loading of dark mode image resources is consistent with the normal method of loading images.

image.png

Color adaptation

iOS 13 starts UIColor Change to dynamic color, different colors can be set separately in Light Mode and Dark Mode. If the UIColor color value is managed, it is stored in Assets.xcassets like the image resource, and it is also adapted according to the above method. If the UIColor color value is not stored in Assets.xcassets, when customizing the dynamic UIColor, two methods have been added to the initialization method under iOS 13

+(UIColor*)colorWithDynamicProvider:(UIColor*(^)(UITraitCollection*))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

- (UIColor*)initWithDynamicProvider:(UIColor*(^)(UITraitCollection*))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos );

  • These two methods require a block to be passed, and the block will return a UITraitCollection class

  • When the system switches between dark mode and normal mode, the block callback sample code will be triggered:

UIColor*dynamicColor=[UIColorcolorWithDynamicProvider:^UIColor*_Nonnull(UITraitCollection*_NonnulltrainCollection){

if([trainCollectionuserInterfaceStyle] == UIUserInterfaceStyleLight) {

return [UIC olor whiteColor];

else{

return [UIColor blackColor];

}];

[self.view setBackgroundColor:dynamicColor];

Of course, the iOS 13 system also provides a set of basic dark mode UIColor dynamic colors by default. The specific statement is as follows:< /p>

@property (class, nonatomic, readonly) UIColor *systemBrownColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *systemIndigoColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);< /span>

@property (class, nonatomic, readonly) UIColor *systemGray2Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watcho s);

@property (class, nonatomic, readonly) UIColor *systemGray3Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos );

@property (class, nonatomic, readonly) UIColor *systemGray4Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos) ;

@property (class, nonatomic, readonly) UIColor *systemGray5Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *systemGray6Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);< /span>

@property (class, nonatomic, readonly) UIColor *labelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos) ;

@property (class, nonatomic, readonly) UIColor *secondaryLabelColor API _AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *tertiaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property(class, nonatomic, readonly ) UIColor *quaternaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property(class, nonatomic , Readonly) UIColor *linkColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property(class , Nonatomic, readonly) UIColor *placeholderTextColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *separatorColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property(class, nonatomic, readonly) UIColor *opaqueSeparatorColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *systemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property(class, nonatomic, readonly) UIColor *secondarySystemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property(class, nonatomic, readonly) UIColor *tertiarySystemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property(class, nonatomic, readonly) UIColor *systemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *seconda rySystemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property(class,nonatomic,readonly)UIColor*tertiarySystemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *systemFillColor API VAILABLE (ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property(class,nonatomic,readonly)UIColor*secondarySystemFillColor(API_AVAILABLE ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor*tertiarySystemFillColor(ios_A) (13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor*quaternarySystemFillColor*quaternarySystemFillColor(ios) API_AVAILABLE(ios (13.0)) API_UNAVAILABLE(tvos, watchos);

Switching of monitoring mode

When you need to monitor system mode changes and respond, you need to use the following ViewController Function

// Note: The parameter is the traitCollection before the change, and the function needs to be rewritten

-(void)traitCollectionDidChange:( UITraitCollection*)previousTraitCollection;

// Determine whether two UITraitCollection objects are different

- (BOOL)hasDifferentColorAppearanceComparedToTraitCollection:(UITraitCollection*)traitCollection;

Sample code:

- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection{

[supertraitCollectionDidChange:previousTraitCollection];

trait has Changed? p>

if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {

// do something...

}

}

System mode changes, custom redraw views

When the system mode changes, the system will notify all Views and ViewControllers that they need to update their styles, and the following methods will be triggered to execute (refer to Apple’s official adaptation Link):

NSView

- (void)updateLayer;

- (void)drawRect:(NSRect)dirtyRect;

- (void)layout;

- (void)updateConstraints ;

UIView

- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection;

-(void)layoutSubviews;

- (void)drawRect:(NSRect)dirtyRect;

- (void)updateConstraints;

- (void)tintColorDidChange;

UIViewController

- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection;

- (void)updateViewConstraints;

- (void)viewWillLayoutSubviews;

- (void)viewDidLayoutSubviews;

UIPresentationController

- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection;

- (void)containerViewWillLayoutSubviews;

- (void)containerViewDidLayoutSubviews;


4. LaunchImage is about to be obsolete

 Use LaunchImage to set the launch image, and you need to provide launch image adaptation for various screen sizes , This method varies with various devices The increase in the size of the equipment increases the extra unnecessary workload. In order to solve the shortcomings brought by LaunchImage, iOS 8 introduced LaunchScreen technology. Because it supports AutoLayout + SizeClass, LaunchScreen can easily solve the adaptation of current and future screen sizes. Apple has officially issued an announcement that starting from April 2020, all apps that use the iOS 13 SDK must provide LaunchScreen. Creating a LaunchScreen is also very simple (1) Create a LaunchScreen with New Files, create a new Image in the View under the created ViewController, and configure the image of the Image (2) Adjust the frame of the Image to fill the screen, and modify the Autoresizing of the Image as shown below , Complete

image.png

5. Added permission to always use Bluetooth

h1>

Before iOS13, Bluetooth can be used directly without the permission prompt window, but under iOS 13, a new permission application for using Bluetooth has been added. I will receive the following prompt when I upload the IPA package to the App Store recently.

image.png

Solution: just add the following entry in Info.plist:

NSBluetoothAlwaysUsageDescription Here enter what to do with Bluetooth


6. Sign With Apple

 In the iOS 13 system, Apple requires apps that provide third-party logins to also support "Sign With Apple". For specific practices, please refer to iOS Sign With Apple Practice


7. Push Device Token adaptation

Before iOS 13, getting Device Token is to return NSData The type data is directly converted into a string of NSString through the -(void)description; method. Get results before iOS 13:

image.png

Get results after iOS 13:

image.png

适配方案: 目的是要将系统返回 NSData< /code> 类型数据转换成字符串,再传给推送服务方。 -(void)description; 本身是用于为类调试提供相关的打印信息,严格来说,不应直接从该方法获取数据并应用于正式环境中。将 NSData 转换成 HexString,即可满足适配需求。

 

- (NSString *)getHexStringForData:(NSData *)data {

    NSUInteger length = [data length];

    char *chars = (char *)[data bytes];

    NSMutableString *hexString = [[NSMutableString alloc] init];

    for (NSUInteger i = 0; i < length; i++) {

        [hexString appendString:[NSString stringWithFormat:@"%0.2hhx", chars[i]]];

    }

    return hexString;

} 


8. UIKit 控件变化

主要还是参照了Apple官方的 UIKit 修改文档声明。 iOS 13 Release Notes

8.1. UITableView

iOS 13下设置 cell.contentView.backgroundColor 会直接影响 cell 本身 selected 与 highlighted 效果。建议不要对 contentView.backgroundColor 修改,而对 cell 本身进行设置。

8.2. UITabbar

Badge 文字大小变化

iOS 13之后,Badge 字体默认由13号变为17号。建议在初始化 TabbarController 时,显示 Badge 的 ViewController 调用 setBadgeTextAttributes:forState: 方法

if (@available(iOS 13, *)) {

    [viewController.tabBarItem setBadgeTextAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:13]} forState:UIControlStateNormal];

    [viewController.tabBarItem setBadgeTextAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:13]} forState:UIControlStateSelected];

}

8.2. UITabBarItem

加载gif需设置 scale 比例

NSData *data = [NSData dataWithContentsOfFile:path];

CGImageSourceRef gifSource = CGImageSourceCreateWithData(CFBridgingRetain(data), nil);

siz e_t gifCount = CGImageSourceGetCount(gifSource);

CGImageRef imageRef = CGImageSourceCreateImageAtIndex(gifSource, i,NULL);

 

//  iOS 13之前

UIImage *image = [UIImage imageWithCGImage:imageRef]

//  iOS 13之后添加scale比例(该imageView将展示该动图效果)

UIImage *image = [UIImage imageWithCGImage:imageRef scale:image.size.width / CGRectGetWidth(imageView.frame) orientation:UIImageOrientationUp];

CGImageRelease(imageRef);

无文字时图片位置调整

iOS 13下不需要调整 imageInsets,图片会自动居中显示,因此只需要针对iOS 13之前的做适配即可。

if (IOS_VERSION < 13.0) {

      viewController.tabBarItem.imageInsets = UIEdgeInsetsMake(5, 0, -5, 0);

  }

TabBarItem选中颜色异常

在 iOS 13下设置 tabbarItem 字体选中状态的颜色,在push到其它 ViewController 再返回时,选中状态的 tabbarItem 颜色会变成默认的蓝色。

设置 tabbar 的 tintColor 属性为原本选中状态的颜色即可。

self.tabBar.tintColor = [UIColor redColor];

8.3. 新增 Diffable DataSource

在 iOS 13下,对 UITableView 与 UICollectionView 新增了一套 Diffable DataSource API。为了更高效地更新数据源刷新列表,避免了原有粗暴的刷新方法 - (void)reloadData,以及手动调用控制列表刷新范围的api,很容易出现计算不准确造成 NSInternalInconsistencyException 而引发App crash。 api 官方链接


9. StatusBar新增样式

StatusBar 新增一种样式,默认的 default 由之前的黑色字体,变为根据系统模式自动选择展示 lightContent 或者 darkContent

1. KVC访问私有属性

2. 模态弹窗ViewController 默认样式改变

3. 黑暗模式的适配

4. LaunchImage即将废弃

5. 新增一直使用蓝牙的权限申请

6. Sign With Apple

7. 推送Device Token适配

8. UIKit 控件变化

9. StatusBar新增样式


1. KVC访问私有属性

 这次iOS 13系统升级,影响范围最广的应属KVC访问修改私有属性了,直接禁止开发者获取或直接设置私有属性。而KVC的初衷是允许开发者通过Key名直接访问修改对象的属性值,为其中最典型的 UITextField 的 _placeholderLabelUISearchBar 的 _searchField。造成影响:在iOS 13下App闪退 错误代码:

// placeholderLabel私有属性访问

[textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];

[textField setValue:[UIFont boldSystemFontOfSize:16] forKeyPath:@"_placeholderLabel.font"];

// searchField私有属性访问

UISearchBar *searchBar = [[UISearchBar alloc] init];

UITextField *searchTextField = [searchBar valueForKey:@"_searchField"];

解决方案:  使用 NSMutableAttributedString 富文本来替代KVC访问 UITextField 的 _placeholderLabel

textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"placeholder" attributes:@{NSForegroundColorAttributeName: [UIColor darkGrayColor], NSFontAttributeName: [UIFont systemFont OfSize:13]}];

因此,可以为UITextFeild创建Category,专门用于处理修改placeHolder属性提供方法

 

#import "UITextField+ChangePlaceholder.h"

 

@implementation UITextField (Change)

 

- (void)setPlaceholderFont:(UIFont *)font {

 

  [self setPlaceholderColor:nil font:font];

}

 

- (void)setPlaceholderColor:(UIColor *)color {

 

  [self setPlaceholderColor:color font:nil];

}

 

- (void)setPlaceholderColor:(nullable UIColor *)color font:(nullable UIFont *)font {

 

  if ([self checkPlaceholderEmpty]) {

      return;

  }

  

  NSMutableAttributedString *placeholderAttriString = [[NSMutableAttributedString alloc] initWithString:self.placeholder];

  if (color) {

      [placeholderAttriString addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(0, self.placeholder.length)];

  }

  if (font) {

      [placeholderAttr iString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, self.placeholder.length)];

  }

  [self setAttributedPlaceholder:placeholderAttriString];

}

 

- (BOOL)checkPlaceholderEmpty {

  return (self.placeholder == nil) || ([[self.placeholder stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0);

}

关于 UISearchBar,可遍历其所有子视图,找到指定的 UITextField 类型的子视图,再根据上述 UITextField 的通过富文本方法修改属性。

#import "UISearchBar+ChangePrivateTextFieldSubview.h"

 

@implementation UISearchBar (ChangePrivateTextFieldSubview)

 

/// 修改SearchBar系统自带的TextField

- (void)changeSearchTextFieldWithCompletionBlock:(void(^)(UITextField *textField))completionBlock {

    

    if (!completionBlock) {

        return;

    }

    UITextField *textField = [self findTextFieldWithView:self];

    if (textField) {

        completionBlock(textField);

    }

}

 

/// 递归遍历UISearchBar的子视图,找到UITextField

- (UITextField *)findTextFieldWithView:(UIView *)view {

 

    for (UIView *subview in view.subviews) {

        if ([subview isKindOfClass:[UITextField class]]) {

            return (UITextField *)subview;

        }else if (subview.subviews.count > 0) {

            return [self findTextFieldWithView:subview];

        }

    }

    return nil;

}

@end

PS:关于如何查找自己的App项目是否使用了私有api,可以参考 iOS查找私有API 文章


2. 模态弹窗 ViewController 默认样式改变

 模态弹窗属性 UIModalPresentationStyle 在 iOS 13 下默认被设置为 UIModalPresentationAutomatic新特性,展示样式更为炫酷,同时可用下拉手势关闭模态弹窗。若原有模态弹出 ViewController 时都已指定模态弹窗属性,则可以无视该改动。若想在 iOS 13 中继续保持原有默认模态弹窗效果。可以通过 runtime 的 Method Swizzling 方法交换来实现。

 

#import "UIViewController+ChangeDefaultPresentStyle.h"

 

@implementation UIViewController (ChangeDefaultPresentStyle)

 

+ (void)load {

 

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        Class class = [self class];

        //替换方法

        SEL originalSelector = @selector(presentViewController:animated:completion:);

        SEL newSelector = @selector(new_presentViewController:animated:completion:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);

        Method newMethod = class_getInstanceMethod(class, newSelector);;

        BOOL didAddMethod =

        class_addMethod(class,

                        originalSelector,

                        method_getImplementation(newMethod),

                        method_getTypeEncoding(newMethod));

                        

        if (didAddMethod) {

            class_replaceMethod(class,

                                newSelector,

                                method_getImplementation(originalMethod),

                                method_getTypeEncoding(originalMethod));

        } else {

            method_exchangeImplementations(originalMethod, newMethod);

        }

    });

}

 

- (void)new_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {

    

    viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen;

    [self new_presentViewController:viewControllerToPresent animated:flag completion:completion];

}

 

@end

#import "UIViewController+ChangeDefaultPresentStyle.h"

 

@implementation UIViewController (ChangeDefaultPresentStyle)

 

+ (void)load {

 

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        Class class = [self class];

        //替换方法

        SE L originalSelector = @selector(presentViewController:animated:completion:);

        SEL newSelector = @selector(new_presentViewController:animated:completion:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);

        Method newMethod = class_getInstanceMethod(class, newSelector);;

        BOOL didAddMethod =

        class_addMethod(class,

                        originalSelector,

                        method_getImplementation(newMethod),

                        method_getTypeEncoding(newMethod));

                        

        if (didAddMethod) {

            class_replaceMethod(class,

                                newSelector,

                                method_getImplementation(originalMethod),

                                method_getTypeEncoding(originalMethod));

        } else {

            method_exchangeImplementations(originalMethod, newMethod);

        }

    });

}

 

- (void)new_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {

    

    viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen;

    [self new_presentViewController:viewControllerToPresent animated:flag completion:completion];

}

 

@end


3. 黑暗模式的适配

针对黑暗模式的推出,Apple官方推荐所有三方App尽快适配。目前并没有强制App进行黑暗模式适配。因此黑暗模式适配范围现在可采用以下三种策略:

  • 全局关闭黑暗模式

  • 指定页面关闭黑暗模式

  • 全局适配黑暗模式

3.1. 全局关闭黑暗模式

方案一:在项目 Info.plist 文件中,添加一条内容,Key为 User Interface Style,值类型设置为String并设置为 Light 即可。

方案二:代码强制关闭黑暗模式,将当前 window 设置为 Light 状态。

if(@available(iOS 13.0,*)){

self.window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;

}

3.2 指定页面关闭黑暗模式

从Xcode 11、iOS 13开始,UIViewController与View新增属性 overrideUserInterfaceStyle,若设置View对象该属性为指定模式,则强制该对象以及子对象以指定模式展示,不会跟随系统模式改变。

  • 设置 ViewController 该属性, 将会影响视图控制器的视图以及子视图控制器都采用该模式

  • 设置 View 该属性, 将会影响视图及其所有子视图采用该模式

  • 设置 Window 该属性, 将会影响窗口中的所有内容都采用该样式,包括根视图控制器和在该窗口中显示内容的所有控制器

3.3 全局适配黑暗模式

适配黑暗模式,主要从两方面入手:图片资源适配与颜色适配

图片资源适配

 打开图片资源管理库 Assets.xcassets,选中需要适配的图片素材item,打开最右侧的 Inspectors 工具栏,找到 Appearances 选项,并设置为 Any, Dark模式,此时会在item下增加Dark Appearance,将黑暗模式下的素材拖入即可。关于黑暗模式图片资源的加载,与正常加载图片方法一致。

 

image.png

 

 

颜色适配

iOS 13开始UIColor变为动态颜色,在Light Mode与Dark Mode可以分别设置不同颜色。若UIColor色值管理,与图片资源一样存储于 Assets.xcassets 中,同样参照上述方法适配。若UIColor色值并没有存储于 Assets.xcassets 情况下,自定义动态UIColor时,在iOS 13下初始化方法增加了两个方法

+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

- (UIColor *)initWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

  • 这两个方法要求传一个block,block会返回一个 UITraitCollection 类

  • 当系统在黑暗模式与正常模式切换时,会触发block回调 示例代码:

UIColor *dynamicColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {

        if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {

            return [UIColor whit eColor];

        } else {

            return [UIColor blackColor];

        }

    }];

    

 [self.view setBackgroundColor:dynamicColor];

当然了,iOS 13系统也默认提供了一套基本的黑暗模式UIColor动态颜色,具体声明如下:

@property (class, nonatomic, readonly) UIColor *systemBrownColor        API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *systemIndigoColor       API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *systemGray2Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

< span style="color: #008080;">@property (class, nonatomic, readonly) UIColor *systemGray3Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *systemGray4Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *systemGray5Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *systemGray6Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *labelColor              API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *secondaryLabelColor     API_AVAILABLE(ios(13.0), tvo s(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *tertiaryLabelColor      API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *quaternaryLabelColor    API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *linkColor               API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *placeholderTextColor    API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *separatorColor          API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomi c, readonly) UIColor *opaqueSeparatorColor    API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

@property (class, nonatomic, readonly) UIColor *systemBackgroundColor                   API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *secondarySystemBackgroundColor          API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *tertiarySystemBackgroundColor           API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *systemGroupedBackgroundColor            API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *secondarySystemGroupedBackgroundColor   API _AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *tertiarySystemGroupedBackgroundColor    API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *systemFillColor                         API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *secondarySystemFillColor                API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *tertiarySystemFillColor                 API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

@property (class, nonatomic, readonly) UIColor *quaternarySystemFillColor               API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);

监听模式的切换

当需要监听系统模式发生变化并作出响应时,需要用到 ViewController 以下函数

// 注意:参数为变化前的traitCollection,改函数需要重写

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;

// 判断两个UITraitCollection对象是否不同

- (BOOL)hasDifferentColorAppearanceComparedToTraitCollection:(UITraitCollection *)traitCollection;

示例代码:

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {

    [super traitCollectionDidChange:previousTraitCollection];

    // trait has Changed?

    if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {

    // do something...

    }

    }

系统模式变更,自定义重绘视图

当系统模式变更时,系统会通知所有的 View以及 ViewController 需要更新样式,会触发以下方法执行(参考Apple官方适配链接):

NSView

- (void)updateLayer;

- (void)drawRect:(NSRect)dirtyRect;

- (void)layout;

- (void)updateConstraints;

UIView

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;

- (void)layoutSubviews;

- (void)drawRect:(NSRect)dirtyRect;

- (void)updateConstraints;

- (void)tintColorDidChange;

UIViewController

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;

- (void)updateViewConstraints;

- (void)viewWillLayoutSubviews;

- (void)viewDidLayoutSubviews;

UIPresentationController

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;

- (void)containerViewWillLayoutSubviews;

- (void)containerViewDidLayoutSubviews;


4. LaunchImage即将废弃

 使用 LaunchImage 设置启动图,需要提供各类屏幕尺寸的启动图适配,这种方式随着各类设备尺寸的增加,增加了额外不必要的工作量。为了解决 LaunchImage 带来的弊端,iOS 8引入了 LaunchScreen 技术,因为支持 AutoLayout + SizeClass,所以通过 LaunchScreen 就可以简单解决适配当下以及未来各种屏幕尺寸。 Apple官方已经发出公告,2020年4月开始,所有使用iOS 13 SDK 的App都必须提供 LaunchScreen。创建一个 LaunchScreen 也非常简单 (1)New Files创建一个 LaunchScreen,在创建的 ViewController 下 View 中新建一个 Image,并配置 Image 的图片 (2)调整 Image 的 frame 为占满屏幕,并修改 Image 的 Autoresizing 如下图,完成

image.png

5. 新增一直使用蓝牙的权限申请

在iOS13之前,无需权限提示窗即可直接使用蓝牙,但在iOS 13下,新增了使用蓝牙的权限申请。最近一段时间上传IPA包至App Store会收到以下提示。

 

image.png

 

 

解决方案:只需要在 Info.plist 里增加以下条目:

NSBluetoothAlwaysUsageDescription 这里输入使用蓝牙来做什么


6. Sign With Apple

 在iOS 13系统中,Apple要求提供第三方登录的App也要支持「Sign With Apple」,具体实践参考 iOS Sign With Apple实践


7. 推送Device Token适配

在iOS 13之前,获取Device Token 是将系统返回的 NSData 类型数据通过 -(void)description; 方法直接转换成 NSString 字符串。 iOS 13之前获取结果:

 

image.png

iOS 13之后获取结果:

 

image.png

适配方案: 目的是要将系统返回 NSData 类型数据转换成字符串,再传给推送服务方。 -(void)description; 本身是用于为类调试提供相关的打印信息,严格来说,不应直接从该方法获取数据并应用于正式环境中。将 NSData 转换成 HexString,即可满足适配需求。

 

- (NSString *)getHexStringForData:(NSData *)data {

    NSUInteger length = [data length];

    char *chars = (char *)[data bytes];

    NSMutableString *hexString = [[NSMutableString alloc] init];

    for (NSUInteger i = 0; i < length; i++) {

        [hexString appendString:[NSString stringWithFormat:@"%0.2hhx", chars[i]]];

    }

    return hexString;

} 


8. UIKit 控件变化

主要还是参照了Apple官方的 UIKit 修改文档声明。 iOS 13 Release Notes

8.1. UITableView

iOS 13下设置 cell.contentView.backgroundColor 会直接影响 cell 本身 selected 与 highlighted 效果。建议不要对 contentView.backgroundColor 修改,而对 cell 本身进行设置。

8.2. UITabbar

Badge 文字大小变化

iOS 13之后,Badge 字体默认由13号变为17号。建议在初始化 TabbarController 时,显示 Badge 的 ViewController 调用 setBadgeTextAttributes:forState: 方法

if (@available(iOS 13, *)) {

    [viewController.tabBarItem setBadgeTextAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:13]} forState:UIControlStateNormal];

    [viewController.tabBarItem setBadgeTextAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:13]} forState:UIControlStateSelected];

}

8.2. UITabBarItem

加载gif需设置 scale 比例

NSData *data = [NSData dataWithContentsOfFile:path];

CGImageSourceRef gifSource = CGImageSourceCreateWithData(CFBridgingRetain(data), nil);

size_t gifCo unt = CGImageSourceGetCount(gifSource);

CGImageRef imageRef = CGImageSourceCreateImageAtIndex(gifSource, i,NULL);

 

//  iOS 13之前

UIImage *image = [UIImage imageWithCGImage:imageRef]

//  iOS 13之后添加scale比例(该imageView将展示该动图效果)

UIImage *image = [UIImage imageWithCGImage:imageRef scale:image.size.width / CGRectGetWidth(imageView.frame) orientation:UIImageOrientationUp];

CGImageRelease(imageRef);

无文字时图片位置调整

iOS 13下不需要调整 imageInsets,图片会自动居中显示,因此只需要针对iOS 13之前的做适配即可。

if (IOS_VERSION < 13.0) {

      viewController.tabBarItem.imageInsets = UIEdgeInsetsMake(5, 0, -5, 0);

  }

TabBarItem选中颜色异常

在 iOS 13下设置 tabbarItem 字体选中状态的颜色,在push到其它 ViewController 再返回时,选中状态的 tabbarItem 颜色会变成默认的蓝色。

设置 tabbar 的 tintColor 属性为原本选中状态的颜色即可。

self.tabBar.tintColor = [UIColor redColor];

8.3. 新增 Diffable DataSource

在 iOS 13下,对 UITableView 与 UICollectionView 新增了一套 Diffable DataSource API。为了更高效地更新数据源刷新列表,避免了原有粗暴的刷新方法 - (void)reloadData,以及手动调用控制列表刷新范围的api,很容易出现计算不准确造成 NSInternalInconsistencyException 而引发App crash。 api 官方链接


9. StatusBar新增样式

StatusBar 新增一种样式,默认的 default 由之前的黑色字体,变为根据系统模式自动选择展示 lightContent 或者 darkContent

Leave a Comment

Your email address will not be published.