您好,登錄后才能下訂單哦!
系統原生的UISearchBar在iOS 11經歷了一次變革,高度由原來的44變成了56 (使用默認高度的估計都被坑了),樣式也發生了些微的變化,比如在未輸入狀態下圓角變化,放大鏡圖標和文本的文字不再居中而是靠左了。具體看圖
一些主流App也常見在導航欄嵌入searchBar,以網易云音樂和知乎為例,左邊是主頁,右邊是搜索頁面 (注意光標)。
實現思路與案例
核心思想是設置導航欄的titleView和左右的barButtonItem。主要有3種方式
為什么上面的titleView說是button不是其他的?其他的當然也可以實現。button自帶imageView和titleLabel,只需要設置偏移量更容易達到我們想要的,而且視圖層級更少,在流暢性方面更有保證些。
案例
網易云音樂首頁和搜索頁面的導航欄視圖層級,titleView都使用MCSearchBar來實現,并且設置了導航欄左右兩邊的按鈕 。這類似上文所說的第二種思路。
圖中可以清楚看到知乎首頁導航欄由2個button組成,搜索頁面使用了textField,這類似上文提到的第三種思路。
實戰
通過自定義SearchBar實現一個如下樣式的導航欄
先自定義一個UISearchBar的初始化方法,觀察一下首頁和搜索頁的異同,像searchField的大小背景色是一致的,可以這部分可以直接給定,而placeholder是不一樣的,所以應該在調用的時候提供。以此類推,新建一個OHSearchBar類,一個初始化方法
- (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和rightBarButton
// 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
這時候可以看到幾處差異
解決方法: 第一和第二個問題,判斷設備是否是iOS 11,若是則設置其高度,不是則讓其placeholder居左。關鍵代碼如下
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位置、大小、圓角一致。
- (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}; // 光標偏移量 }
同樣的,先看下運行效果
原本以為這下是沒什么問題的,結果簡直是坑
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]; }
到此,便大功告成了。可以看下源碼加深理解。希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。