【iOS】iOS13 UISearchbar适配之路

最大的坑还是iOS13系统不让 KVC 的方式 来取或修改一些私有属性,目前是遇到 UISearchBar 的坑,其他还在测试中。

crash修复

2019.06.04 下载了Xcode_11_Beta,跑起来项目后,直接crash。

原本 searchbar 的样子:

生成 UISearchBar 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (void)initSearchBar
{
_searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 0, kSearchBarHeight)];
self.searchBar.delegate = self;
self.searchBar.enablesReturnKeyAutomatically = NO;
self.searchBar.layer.masksToBounds = YES;
self.searchBar.layer.cornerRadius = kSearchBarHeight / 2;
self.searchBar.backgroundColor = [UIColor colorWithRGBHex:0xF1F3F6];

// 设置bar的前景色(背景色), 不要使用backgroundColor, 表现不出来
self.searchBar.barTintColor = [UIColor colorWithRGBHex:0xF1F3F6];

// 设置字体颜色、默认字体颜色
UITextField *textField = [self.searchBar valueForKey:@"_searchField"];
textField.backgroundColor = [UIColor colorWithRGBHex:0xF1F3F6];
textField.textColor = [UIColor colorWithRGBHex:0x000000 alpha:0.85];
[textField setValue:[UIColor colorWithRGBHex:0x000000 alpha:0.35] forKeyPath:@"_placeholderLabel.textColor"];
textField.font = [UIFont systemFontOfSize:14.0];

// 修改放大镜
UIImage *image = LOAD_DEFAULT_ICON_USE_POOL(@"search_custom_bar_search_icon.png");
[self.searchBar setImage:image forSearchBarIcon:UISearchBarIconSearch state:UIControlStateNormal];
// 放大镜往左移一点,也得往下一点
[self.searchBar setPositionAdjustment:UIOffsetMake(-5, 1) forSearchBarIcon:UISearchBarIconSearch];
// 删除按钮往右移一点
[self.searchBar setPositionAdjustment:UIOffsetMake(7, 0) forSearchBarIcon:UISearchBarIconClear];

// 方法镜图片的大小是24px,结果系统给放大了,所以这里重设一下,这里设置的y不影响,系统会重新对y居中。
textField.leftView.frame = CGRectMake(textField.leftView.left, textField.leftView.top, image.size.width, image.size.height);

[self addSubview:self.searchBar];
}

crash原因:

1
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Access to UISearchBar's _searchField ivar is prohibited. This is an application bug'

问题代码:

1
UITextField *textField = [self.searchBar valueForKey:@"_searchField"];

看来iOS13不允许用 valueForKey 来取私有属性。那就改成遍历取值:

1
UITextField *textField = (UITextField*)[self.searchBar findSubview:@"UITextField" resursion:YES];

findSubview:resursion 是 UIView 的自定义分类实现的方法,实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (UIView *)findSubview:(NSString *)name resursion:(BOOL)resursion
{
Class class = NSClassFromString(name);
for (UIView *subview in self.subviews) {
if ([subview isKindOfClass:class]) {
return subview;
}
}

if (resursion) {
for (UIView *subview in self.subviews) {
UIView *tempView = [subview findSubview:name resursion:resursion];
if (tempView) {
return tempView;
}
}
}

return nil;
}

然后再跑,接着crash。

1
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Access to UITextField's _placeholderLabel ivar is prohibited. This is an application bug'
1
[textField setValue:[UIColor colorWithRGBHex:0x000000 alpha:0.35] forKeyPath:@"_placeholderLabel.textColor"];

给 textField 设置 placeholder 字体颜色也不行了,这就很尴尬。

原本设置placeholder的方法:

1
2
3
4
5
- (void)setPlaceholderStr:(NSString *)placeholderStr
{
_placeholderStr = [placeholderStr copy];
_searchBar.placeholder = _placeholderStr;
}

只好改为设置 UITextField@property(nullable, nonatomic,copy) NSAttributedString *attributedPlaceholder API_AVAILABLE(ios(6.0)); // default is nil 属性。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)setPlaceholderStr:(NSString *)placeholderStr
{
_placeholderStr = [placeholderStr copy];
if (_placeholderStr.length > 0)
{
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:_placeholderStr];
[attributedString setAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kSearchBarFontSize], NSForegroundColorAttributeName:[UIColor colorWithRGBHex:0x000000 alpha:0.35]} range:NSMakeRange(0, _placeholderStr.length)];
UITextField *textField = (UITextField*)[self.searchBar findSubview:@"UITextField" resursion:YES];
textField.attributedPlaceholder = attributedString;
}

}

至此,crash修复了。

背景色重叠问题

但是searchBar的背景色又出了问题。

看了下层级是 _UISearchBarSearchFieldBackgroundView ,尝试这个方案:

1
2
UIView *textFieldBackgroundView = (UIView *)[self.searchBar findSubview:@"_UISearchBarSearchFieldBackgroundView" resursion:YES];
[textFieldBackgroundView removeFromSuperview];

失败。取出来是nil。尝试另一种方案:

1
2
3
4
5
6
for (UIView *view in self.searchBar.subviews) {
if ([view isKindOfClass:NSClassFromString(@"UIView")] && view.subviews.count > 0) {
[[view.subviews objectAtIndex:0] removeFromSuperview];
break;
}
}

干是干掉了,但是直接crash了:

1
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Missing or detached view for search bar layout'

看来干掉后系统布局找不到这个view就挂了。

最后在layoutUI 布局的方法里面设置一下image

1
2
3
4
5
- (void)layoutUI
{
// ...上面计算searchBar宽度代码省略
[self.searchBar setSearchFieldBackgroundImage:[UIImage imageWithColor:[UIColor colorWithRGBHex:0x000000 alpha:0] size:CGSizeMake(self.searchBar.width, self.searchBar.height)] forState:UIControlStateNormal];
}

但这就出现了以前遇到的问题,searchbar的背景色通过设置 self.searchBar.barTintColor 来搞定的。但这样上下就会有条黑线:

改成设置backgroundImage。

1
[self.searchBar setBackgroundImage:[UIImage imageWithColor:[UIColor colorWithRGBHex:0xF1F3F6]]];

最后代码整体改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
- (void)initSearchBar
{
_searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 0, kSearchBarHeight)];
self.searchBar.delegate = self;
self.searchBar.enablesReturnKeyAutomatically = NO;
self.searchBar.layer.masksToBounds = YES;
self.searchBar.layer.cornerRadius = kSearchBarHeight / 2;

// 不要设置 self.searchBar.barTintColor ,上下会有黑线,直接 setBackgroundImage
[self.searchBar setBackgroundImage:[UIImage imageWithColor:[UIColor colorWithRGBHex:0xF1F3F6]]];

// 设置字体颜色、默认字体颜色
UITextField *textField = (UITextField*)[self.searchBar findSubview:@"UITextField" resursion:YES];
textField.textColor = [UIColor colorWithRGBHex:0x000000 alpha:0.85];
textField.font = [UIFont systemFontOfSize:kSearchBarFontSize];

// 修改放大镜
UIImage *image = LOAD_DEFAULT_ICON_USE_POOL(@"search_custom_bar_search_icon.png");
[self.searchBar setImage:image forSearchBarIcon:UISearchBarIconSearch state:UIControlStateNormal];
// 放大镜往左移一点,也得往下一点
[self.searchBar setPositionAdjustment:UIOffsetMake(-5, 1) forSearchBarIcon:UISearchBarIconSearch];
// 删除按钮往右移一点
[self.searchBar setPositionAdjustment:UIOffsetMake(7, 0) forSearchBarIcon:UISearchBarIconClear];

// 方法镜图片的大小是24px,结果系统给放大了,所以这里重设一下,这里设置的y不影响,系统会重新对y居中。
textField.leftView.frame = CGRectMake(textField.leftView.left, textField.leftView.top, image.size.width, image.size.height);

[self addSubview:self.searchBar];
}

- (void)setPlaceholderStr:(NSString *)placeholderStr
{
_placeholderStr = [placeholderStr copy];
if (_placeholderStr.length > 0)
{
// iOS13不能KVC设置颜色了,所以不能直接设置 self.searchBar.placeholder = _placeholderStr; 。需要通过下面方式来设置颜色
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:_placeholderStr];
[attributedString setAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kSearchBarFontSize], NSForegroundColorAttributeName:[UIColor colorWithRGBHex:0x000000 alpha:0.35]} range:NSMakeRange(0, _placeholderStr.length)];
UITextField *textField = (UITextField*)[self.searchBar findSubview:@"UITextField" resursion:YES];
textField.attributedPlaceholder = attributedString;
}

}

- (void)layoutUI
{
// ...上面计算searchBar宽度代码省略
[self.searchBar setSearchFieldBackgroundImage:[UIImage imageWithColor:[UIColor colorWithRGBHex:0x000000 alpha:0] size:CGSizeMake(self.searchBar.width, self.searchBar.height)] forState:UIControlStateNormal];
}

适配结束。

总结

  1. 最大的坑还是iOS13系统不让 KVC 的方式 来取或修改一些私有属性,目前是遇到 UISearchBar 的坑,其他还在测试中。
  2. UISearchBar 的背景色,内部 UITextField 的背景色,这几个设置起来都是组合比较多,坑也比较多。