系統原生的UISearchBar在iOS 11經歷了一次變革,高度由原來的44變成了56 (使用默認高度的估計都被坑了),樣式也發生了些微的變化,好比在未輸入狀態下圓角變化,放大鏡圖標和文本的文字再也不居中而是靠左了。具體看圖 git
一些主流App也常見在導航欄嵌入searchBar,以網易雲音樂和知乎爲例,左邊是主頁,右邊是搜索頁面 (注意光標)。 github
爲何上面的titleView說是button不是其餘的?其餘的固然也能夠實現。button自帶imageView和titleLabel,只須要設置偏移量更容易達到咱們想要的,並且視圖層級更少,在流暢性方面更有保證些。app
- (instancetype)initWithFrame:(CGRect)frame placeholder:(NSString *)placeholder textFieldLeftView:(UIImageView *)leftView showCancelButton:(BOOL)showCancelButton tintColor:(UIColor *)tintColor {
if (self = [super initWithFrame:frame]) {
self.frame = frame;
self.tintColor = tintColor; //光標顏色
self.barTintColor = [UIColor whiteColor];
self.placeholder = placeholder;
self.showsCancelButton = showCancelButton;
self.leftView = leftView; // 用來代替左邊的放大鏡
[self setImage:[UIImage imageNamed:@"clear"] forSearchBarIcon:UISearchBarIconClear state:UIControlStateNormal]; // 替換輸入過程當中右側的clearIcon
}
return self;
}
複製代碼
新建一個首頁OHHomeViewController,設置導航欄的titleView和rightBarButtonide
// navigation buttom
UIButton *messageButton = [UIButton buttonWithType:UIButtonTypeSystem];
[messageButton setImage:[UIImage imageNamed:@"msg"] forState:UIControlStateNormal];
messageButton.bounds = CGRectMake(0, 0, 30, 30);
UIBarButtonItem *messageBarButton = [[UIBarButtonItem alloc] initWithCustomView:messageButton];
self.navigationItem.rightBarButtonItem = messageBarButton;
// search bar
UIImageView *leftView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"scan"]];
leftView.bounds = CGRectMake(0, 0, 24, 24);
self.ohSearchBar = [[OHSearchBar alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, 44)
placeholder:@"點擊我跳轉"
textFieldLeftView:leftView
showCancelButton:NO
tintColor:[UIColor clearColor]];
self.navigationItem.titleView = self.ohSearchBar;
複製代碼
讓咱們來看下效果,左邊爲iOS 9,右邊iOS 11 ui
這時候能夠看到幾處差別atom
解決方法: 第一和第二個問題,判斷設備是不是iOS 11,如果則設置其高度,不是則讓其placeholder居左。關鍵代碼以下spa
if ([[UIDevice currentDevice] systemVersion].doubleValue >= 11.0) {
[[self.heightAnchor constraintEqualToConstant:44.0] setActive:YES];
} else {
[self setLeftPlaceholder];
}
- (void)setLeftPlaceholder {
SEL centerSelector = NSSelectorFromString([NSString stringWithFormat:@"%@%@", @"setCenter", @"Placeholder:"]);
if ([self respondsToSelector:centerSelector]) {
BOOL centeredPlaceholder = NO;
NSMethodSignature *signature = [[UISearchBar class] instanceMethodSignatureForSelector:centerSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[invocation setSelector:centerSelector];
[invocation setArgument:¢eredPlaceholder atIndex:2];
[invocation invoke];
}
}
複製代碼
對於第三和第四個問題,用KVC獲取textField,並對其進行定製。令textField位置、大小、圓角一致。3d
- (void)layoutSubviews{
[super layoutSubviews];
// search field
UITextField *searchField = [self valueForKey:@"searchField"];
searchField.backgroundColor = DARK_BLUE_COLOR;
searchField.textColor = [UIColor whiteColor];
searchField.font = [UIFont systemFontOfSize:16];
searchField.leftView = self.leftView;
searchField.frame = CGRectMake(0, 8, SCREEN_WIDTH, 28);
searchField.layer.cornerRadius = 5;
searchField.layer.masksToBounds = YES;
[searchField setValue:[UIColor whiteColor] forKeyPath:@"placeholderLabel.textColor"];
[self setValue:searchField forKey:@"searchField"];
self.searchTextPositionAdjustment = (UIOffset){10, 0}; // 光標偏移量
}
複製代碼
一樣的,先看下運行效果 代理
本來覺得這下是沒什麼問題的,結果簡直是坑 code
textFild的長度、位置、圓角都不同 解釋下這裏出現的問題
觀察上方圖片上方的searchBar,會發現textField左邊是圓角,右邊是直角,說明是被截取的。導航欄titleView的範圍就劃分到了那個部分,而下邊的searchBar連rightBarButton都不放過,直接搶佔了位置。推測這是因爲iOS 11導航欄視圖層級變化產生的,能夠這裏瞭解下www.jianshu.com/p/352f101d6…,或者自行科普,不詳細展開。因此對於searchBar的size設置要當心了,儘可能控制在合適的範圍。
textField的圓角是不一致的,自定義圓角大小時,取消其自己的圓角樣式
searchField.borderStyle = UITextBorderStyleNone;
複製代碼
查看視圖層級會發現,iOS 11如下,設置titleView,x的默認座標竟然是12,而iOS 11是0。因此設置textField的x座標的話,在iOS 11下必須多出12纔會是一致的位置。
修改代碼上面的代碼
- (void)layoutSubviews{
[super layoutSubviews];
// search field
UITextField *searchField = [self valueForKey:@"searchField"];
searchField.backgroundColor = DARK_BLUE_COLOR;
searchField.textColor = [UIColor whiteColor];
searchField.font = [UIFont systemFontOfSize:16];
searchField.leftView = self.leftView;
if (@available(iOS 11.0, *)) {
// 查看視圖層級,在iOS 11以前searchbar的x是12
searchField.frame = CGRectMake(12, 8, SCREEN_WIDTH*0.8, 28);
} else {
searchField.frame = CGRectMake(0, 8, SCREEN_WIDTH*0.8, 28);
}
searchField.borderStyle = UITextBorderStyleNone;
searchField.layer.cornerRadius = 5;
searchField.layer.masksToBounds = YES;
[searchField setValue:[UIColor whiteColor] forKeyPath:@"placeholderLabel.textColor"];
[self setValue:searchField forKey:@"searchField"];
self.searchTextPositionAdjustment = (UIOffset){10, 0}; // 光標偏移量
}
複製代碼
這時候就是咱們想要的結果了。
首頁暫時告一段落,接着開始咱們的搜索頁面。與首頁不一樣的是須要searchBar與searchController配合使用。新建一個OHSearchController類 添加一個屬性
@property (nonatomic, strong) OHSearchBar *ohSearchBar;
複製代碼
初始化代碼
- (instancetype)initWithSearchResultsController:(UIViewController *)searchResultsController searchBarFrame:(CGRect)searchBarFrame placeholder:(NSString *)placeholder textFieldLeftView:(UIImageView *)leftView showCancelButton:(BOOL)showCancelButton barTintColor:(UIColor *)barTintColor{
if (self = [super initWithSearchResultsController:searchResultsController]) {
self.ohSearchBar = [[OHSearchBar alloc] initWithFrame:searchBarFrame
placeholder:placeholder
textFieldLeftView:leftView
showCancelButton:YES
tintColor:barTintColor];
UIButton *button = [self.ohSearchBar valueForKey:@"cancelButton"];
button.tintColor = [UIColor whiteColor];
[button setTitle:@"取消" forState:UIControlStateNormal];
[self.ohSearchBar setValue:button forKey:@"cancelButton"];
}
return self;
}
複製代碼
接着是咱們的視圖控制器OHSearchViewController
UIImageView *leftView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"search"]];
leftView.bounds = CGRectMake(0, 0, 24, 24);
self.ohSearchController = [[OHSearchController alloc] initWithSearchResultsController:self
searchBarFrame:CGRectMake(0, 0, SCREEN_WIDTH, 44)
placeholder:@"請輸入搜索內容進行搜索"
textFieldLeftView:leftView
showCancelButton:YES
barTintColor:BASE_BLUE_COLOR];
[self.ohSearchController.ohSearchBar becomeFirstResponder];
self.ohSearchController.ohSearchBar.delegate = self;
[self.ohSearchController.ohSearchBar setLeftPlaceholder];
self.navigationItem.titleView = self.ohSearchController.ohSearchBar;
self.navigationItem.hidesBackButton = YES;
複製代碼
完成這一步後到了交互環節了,點擊首頁的searchBar跳轉搜索頁面,點擊搜索頁面的取消按鈕返回到首頁。 首頁設置searchbar的代理,並完成一下代理方法
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
OHSearchViewController *ohSearchViewController = [[OHSearchViewController alloc] init];
[self.navigationController pushViewController:ohSearchViewController animated:NO];
return YES;
}
複製代碼
搜索頁設置searchbar的代理,並完成一下代理方法
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
[self.navigationController popViewControllerAnimated:NO];
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[self.ohSearchController.ohSearchBar resignFirstResponder];
// 讓取消按鈕一直處於激活狀態
UIButton *cancelBtn = [searchBar valueForKey:@"cancelButton"];
cancelBtn.enabled = YES;
}
複製代碼
這時候問題又出現了,點擊搜索頁面的取消按鈕,沒有跳回首頁而是還在這個頁面。可是能夠看到屏幕的閃動。經過打印消息發現,點了取消按鈕,執行了首頁的- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar方法。 仔細推敲以後想明白了緣由是沒有取消第一響應者,加上導航欄的交互機制,pop到上個頁面的時候並不會進行頁面刷新致使了這個問題。 解決辦法在首頁要push搜索頁面的時候取消第一響應者
- (void)viewWillDisappear:(BOOL)animated {
[self.ohSearchBar resignFirstResponder];
}
複製代碼
到此,便大功告成了。能夠看下源碼加深理解。 項目源碼傳送門: HasjOH/OHSearchBarInNaviBar