Skip to content

Commit 177d68c

Browse files
committed
Merge commit 'a006c3ddb27dc30fb0ad0685f34b2c5c4fae993e'
2 parents 86ec359 + a006c3d commit 177d68c

4 files changed

Lines changed: 212 additions & 35 deletions

File tree

QMUI/QMUIKit/UIKitExtensions/NSObject+QMUI.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@
3838
*/
3939
- (id)qmui_performSelectorToSuperclass:(SEL)aSelector withObject:(id)object;
4040

41+
/**
42+
使用 block 遍历当前实例的所有方法,父类的方法不包含在内
43+
*/
44+
- (void)qmui_enumrateInstanceMethodsUsingBlock:(void (^)(SEL selector))block;
45+
46+
/**
47+
使用 block 遍历指定的某个类的实例方法,该类的父类方法不包含在内
48+
* @param aClass 要遍历的某个类
49+
* @param block 遍历时使用的 block,参数为某一个方法
50+
*/
51+
+ (void)qmui_enumrateInstanceMethodsOfClass:(Class)aClass usingBlock:(void (^)(SEL selector))block;
52+
4153
/**
4254
遍历某个 protocol 里的所有方法
4355

QMUI/QMUIKit/UIKitExtensions/NSObject+QMUI.m

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#import "NSObject+QMUI.h"
1010
#import <objc/message.h>
11+
#import <objc/runtime.h>
1112

1213
@implementation NSObject (QMUI)
1314

@@ -48,6 +49,23 @@ - (id)qmui_performSelectorToSuperclass:(SEL)aSelector withObject:(id)object {
4849
return (*objc_superAllocTyped)(&mySuper, aSelector, object);
4950
}
5051

52+
- (void)qmui_enumrateInstanceMethodsUsingBlock:(void (^)(SEL))block {
53+
[NSObject qmui_enumrateInstanceMethodsOfClass:self.class usingBlock:block];
54+
}
55+
56+
+ (void)qmui_enumrateInstanceMethodsOfClass:(Class)aClass usingBlock:(void (^)(SEL selector))block {
57+
unsigned int methodCount = 0;
58+
Method *methods = class_copyMethodList(aClass, &methodCount);
59+
60+
for (unsigned int i = 0; i < methodCount; i++) {
61+
Method method = methods[i];
62+
SEL selector = method_getName(method);
63+
if (block) block(selector);
64+
}
65+
66+
free(methods);
67+
}
68+
5169
+ (void)qmui_enumerateProtocolMethods:(Protocol *)protocol usingBlock:(void (^)(SEL))block {
5270
unsigned int methodCount = 0;
5371
struct objc_method_description *methods = protocol_copyMethodDescriptionList(protocol, NO, YES, &methodCount);

QMUI/QMUIKit/UIKitExtensions/QMUISearchController.h

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,17 @@
1313
@class QMUISearchController;
1414

1515
/**
16-
* 配合QMUISearchController使用的protocol,主要负责两件事情:
16+
* 配合 QMUISearchController 使用的 protocol,主要负责两件事情:
1717
*
18-
* <ol>
19-
* <li>响应用户的输入,在搜索框内的文字发生变化后被调用,可在<i>searchController:updateResultsForSearchString:</i>方法内更新搜索结果的数据集,在里面请自行调用<i>[searchController.tableView reloadData]</i></li>
20-
* <li>渲染最终用于显示搜索结果的UITableView的数据,该tableView的delegate、dataSource均在这里实现</li>
21-
* </ol>
18+
* 1. 响应用户的输入,在搜索框内的文字发生变化后被调用,可在 searchController:updateResultsForSearchString: 方法内更新搜索结果的数据集,在里面请自行调用 [searchController.tableView reloadData]
19+
* 2. 渲染最终用于显示搜索结果的 UITableView 的数据,该 tableView 的 delegate、dataSource 均包含在这个 protocol 里
2220
*/
23-
@protocol QMUISearchControllerDelegate <UITableViewDataSource,UITableViewDelegate>
21+
@protocol QMUISearchControllerDelegate <UITableViewDataSource, UITableViewDelegate>
2422

2523
@required
2624
/**
27-
* 搜索框文字发生变化时的回调,请自行调用 `[tableView reloadData]` 来更新界面。
28-
* @warning 搜索框文字为空(例如第一次点击搜索框进入搜索状态时,或者文字全被删掉了,或者点击搜索框的×)也会走进来,此时参数searchString为@"",这是为了和系统的UISearchController保持一致
25+
* 搜索框文字发生变化时的回调,请自行调用 `[tableView reloadData]` 来更新界面。
26+
* @warning 搜索框文字为空(例如第一次点击搜索框进入搜索状态时,或者文字全被删掉了,或者点击搜索框的×)也会走进来,此时参数searchString为@"",这是为了和系统的UISearchController保持一致
2927
*/
3028
- (void)searchController:(QMUISearchController *)searchController updateResultsForSearchString:(NSString *)searchString;
3129

@@ -40,25 +38,41 @@
4038
@end
4139

4240
/**
43-
* 兼容iOS7及以后的版本的searchController,在iOS7下会使用UISearchDisplayController实现,在iOS8及以后会使用UISearchController实现。<br/>
44-
* 使用方法:
45-
* <ol>
46-
* <li>使用<i>initWithContentsViewController:</i>初始化</li>
47-
* <li>指定<i>searchResultsDelegate</i>属性并在其中实现<i>searchController:updateResultsForSearchString:</i>方法以更新搜索结果数据集</li>
48-
* <li>通过<i>searchBar</i>属性得到搜索框的引用并直接使用,例如 @code tableHeaderView = searchController.searchBar @endcode</li>
49-
* </ol>
41+
* 兼容 iOS 7 及以后的版本的 searchController,在 iOS7 下会使用 UISearchDisplayController 实现,在 iOS 8 及以后会使用 UISearchController 实现。
42+
* 支持在搜索文字为空时(注意并非“搜索结果为空”)显示一个界面,例如常见的“最近搜索”功能,具体请查看属性 launchView。
43+
* 使用方法:
44+
* 1. 使用 initWithContentsViewController: 初始化
45+
* 2. 通过 searchBar 属性得到搜索框的引用并直接使用,例如 `tableHeaderView = searchController.searchBar`
46+
* 3. 指定 searchResultsDelegate 属性并在其中实现 searchController:updateResultsForSearchString: 方法以更新搜索结果数据集
47+
*
48+
* @note QMUICommonTableViewController 内部自带 QMUISearchController,只需在 QMUITableViewDelegate shouldShowSearchBarInTableView: 方法里返回 YES 即可,无需自行初始化 QMUISearchController。
5049
*/
5150
@interface QMUISearchController : QMUICommonViewController
5251

5352
/**
54-
* 在某个指定的UIViewController上创建一个与其绑定的searchController
55-
* @param viewController 要在哪个viewController上添加搜索功能
53+
* 在某个指定的UIViewController上创建一个与其绑定的searchController
54+
* @param viewController 要在哪个viewController上添加搜索功能
5655
*/
5756
- (instancetype)initWithContentsViewController:(UIViewController *)viewController;
5857

59-
@property(nonatomic,weak) id<QMUISearchControllerDelegate> searchResultsDelegate;
58+
@property(nonatomic, weak) id<QMUISearchControllerDelegate> searchResultsDelegate;
59+
60+
/// 搜索框,在 iOS 7 下是 QMUISearchBar,在 iOS 8 及以后是 UISearchBar
61+
@property(nonatomic, strong, readonly) UISearchBar *searchBar;
62+
63+
/// 搜索结果列表,在 iOS 7 下是 UITableView,并且每次进行搜索时指针都会发生变化(系统如此),在 iOS 8 及以后是 QMUITableView
64+
@property(nonatomic, strong, readonly) UITableView *tableView;
65+
66+
/// 在搜索文字为空时会展示的一个 view,通常用于实现“最近搜索”之类的功能。launchView 最终会被布局为撑满搜索框以下的所有空间。
67+
@property(nonatomic, strong) UIView *launchView;
6068

61-
@property(nonatomic,strong,readonly) UISearchBar *searchBar;
62-
@property(nonatomic,strong,readonly) UITableView *tableView;
63-
@property(nonatomic,assign,readonly) BOOL active;
69+
/// 控制以无动画的形式进入/退出搜索状态
70+
@property(nonatomic, assign, getter=isActive) BOOL active;
71+
72+
/**
73+
* 控制进入/退出搜索状态
74+
* @param active YES 表示进入搜索状态,NO 表示退出搜索状态
75+
* @param animated 是否要以动画的形式展示状态切换
76+
*/
77+
- (void)setActive:(BOOL)active animated:(BOOL)animated;
6478
@end

QMUI/QMUIKit/UIKitExtensions/QMUISearchController.m

Lines changed: 147 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#import "QMUIEmptyView.h"
1515
#import "UISearchBar+QMUI.h"
1616
#import "UITableView+QMUI.h"
17+
#import "NSString+QMUI.h"
18+
#import "NSObject+QMUI.h"
19+
#import "UIView+QMUI.h"
1720

1821
BeginIgnoreDeprecatedWarning
1922

@@ -41,28 +44,136 @@ - (void)initTableView {
4144

4245
@end
4346

44-
@interface QMUISearchController ()<UISearchResultsUpdating,UISearchControllerDelegate,QMUISearchResultsTableViewControllerDelegate,UISearchDisplayDelegate>
47+
@interface QMUISearchDisplayController : UISearchDisplayController
4548

46-
// iOS8及以后使用这个
47-
@property(nonatomic,strong) UISearchController *searchController;
49+
@property(nonatomic, strong) UIView *customDimmingView;
50+
@property(nonatomic, copy) void (^dimmingViewVisibleChangedBlock)(BOOL visible);
51+
@end
52+
53+
@implementation QMUISearchDisplayController
54+
55+
- (void)setActive:(BOOL)visible animated:(BOOL)animated {
56+
[super setActive:visible animated:animated];
57+
if (self.customDimmingView.superview) {
58+
BOOL shouldChangeSize = !CGSizeEqualToSize(self.customDimmingView.frame.size, self.customDimmingView.superview.bounds.size);
59+
[UIView qmui_animateWithAnimated:animated duration:[CATransaction animationDuration] animations:^{
60+
self.customDimmingView.superview.alpha = visible ? 1 : 0;
61+
if (shouldChangeSize) {
62+
self.customDimmingView.frame = self.customDimmingView.superview.bounds;
63+
}
64+
}];
65+
}
66+
}
67+
68+
- (void)setCustomDimmingView:(UIView *)customDimmingView {
69+
if (_customDimmingView != customDimmingView) {
70+
[_customDimmingView removeFromSuperview];
71+
}
72+
_customDimmingView = customDimmingView;
73+
}
74+
75+
- (UIColor *)_dimmingViewColor {
76+
if (self.customDimmingView) {
77+
BeginIgnorePerformSelectorLeaksWarning
78+
UIView *containerView = [self performSelector:NSSelectorFromString(@"_containerView")];
79+
EndIgnorePerformSelectorLeaksWarning
80+
UIView *superviewOfDimmingView = containerView.subviews.lastObject;
81+
UIView *defaultDimmingView = superviewOfDimmingView.subviews.firstObject;
82+
if (defaultDimmingView) {
83+
defaultDimmingView.alpha = 1;
84+
self.customDimmingView.frame = defaultDimmingView.bounds;
85+
if (self.customDimmingView.superview != defaultDimmingView) {
86+
[defaultDimmingView addSubview:self.customDimmingView];
87+
}
88+
if (self.dimmingViewVisibleChangedBlock) {
89+
self.dimmingViewVisibleChangedBlock(self.isActive);
90+
}
91+
}
92+
}
93+
94+
return [self qmui_performSelectorToSuperclass:_cmd];
95+
}
96+
97+
@end
98+
99+
@interface QMUICustomSearchController : UISearchController
48100

49-
// iOS7及以前使用这个
50-
@property(nonatomic,strong) UISearchDisplayController *searchDisplayController;
101+
@property(nonatomic, strong) UIView *customDimmingView;
102+
@end
103+
104+
@implementation QMUICustomSearchController
105+
106+
- (void)setCustomDimmingView:(UIView *)customDimmingView {
107+
if (_customDimmingView != customDimmingView) {
108+
[_customDimmingView removeFromSuperview];
109+
}
110+
_customDimmingView = customDimmingView;
111+
112+
self.dimsBackgroundDuringPresentation = !_customDimmingView;
113+
if ([self isViewLoaded]) {
114+
[self addCustomDimmingView];
115+
}
116+
}
117+
118+
- (void)viewWillAppear:(BOOL)animated {
119+
[super viewWillAppear:animated];
120+
[self addCustomDimmingView];
121+
}
122+
123+
- (void)addCustomDimmingView {
124+
UIView *superviewOfDimmingView = self.searchResultsController.view.superview;
125+
if (self.customDimmingView && self.customDimmingView.superview != superviewOfDimmingView) {
126+
[superviewOfDimmingView insertSubview:self.customDimmingView atIndex:0];
127+
[self layoutCustomDimmingView];
128+
}
129+
}
130+
131+
- (void)layoutCustomDimmingView {
132+
UIView *searchBarContainerView = nil;
133+
for (UIView *subview in self.view.subviews) {
134+
if ([NSStringFromClass(subview.class) isEqualToString:@"_UISearchBarContainerView"]) {
135+
searchBarContainerView = subview;
136+
break;
137+
}
138+
}
139+
140+
self.customDimmingView.frame = CGRectInsetEdges(self.customDimmingView.superview.bounds, UIEdgeInsetsMake(searchBarContainerView ? CGRectGetMaxY(searchBarContainerView.frame) : 0, 0, 0, 0));
141+
}
142+
143+
- (void)viewDidLayoutSubviews {
144+
[super viewDidLayoutSubviews];
145+
146+
if (self.customDimmingView) {
147+
[UIView animateWithDuration:[CATransaction animationDuration] animations:^{
148+
[self layoutCustomDimmingView];
149+
}];
150+
}
151+
}
152+
153+
@end
154+
155+
@interface QMUISearchController () <UISearchResultsUpdating, UISearchControllerDelegate, QMUISearchResultsTableViewControllerDelegate, UISearchDisplayDelegate>
156+
157+
// iOS 8 及以后使用这个
158+
@property(nonatomic,strong) QMUICustomSearchController *searchController;
159+
160+
// iOS 7 及以前使用这个
161+
@property(nonatomic,strong) QMUISearchDisplayController *searchDisplayController;
51162
@end
52163

53164
@implementation QMUISearchController
54165

55166
- (instancetype)initWithContentsViewController:(UIViewController *)viewController {
56-
if (self = [self init]) {
167+
if (self = [self initWithNibName:nil bundle:nil]) {
57168
if (NSStringFromClass([UISearchController class])) {
58-
// 将definesPresentationContext置为YES有两个作用
169+
// 将 definesPresentationContext 置为 YES 有两个作用
59170
// 1、保证从搜索结果界面进入子界面后,顶部的searchBar不会依然停留在navigationBar上
60171
// 2、使搜索结果界面的tableView的contentInset.top正确适配searchBar
61172
viewController.definesPresentationContext = YES;
62173

63-
QMUISearchResultsTableViewController *viewController = [[QMUISearchResultsTableViewController alloc] init];
64-
viewController.delegate = self;
65-
self.searchController = [[UISearchController alloc] initWithSearchResultsController:viewController];
174+
QMUISearchResultsTableViewController *searchResultsViewController = [[QMUISearchResultsTableViewController alloc] init];
175+
searchResultsViewController.delegate = self;
176+
self.searchController = [[QMUICustomSearchController alloc] initWithSearchResultsController:searchResultsViewController];
66177
self.searchController.searchResultsUpdater = self;
67178
self.searchController.delegate = self;
68179
_searchBar = self.searchController.searchBar;
@@ -73,7 +184,7 @@ - (instancetype)initWithContentsViewController:(UIViewController *)viewControlle
73184
[self.searchBar qmui_styledAsQMUISearchBar];
74185
} else {
75186
_searchBar = [[QMUISearchBar alloc] init];
76-
self.searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:viewController];
187+
self.searchDisplayController = [[QMUISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:viewController];
77188
self.searchDisplayController.delegate = self;
78189
}
79190
}
@@ -96,14 +207,26 @@ - (void)setSearchResultsDelegate:(id<QMUISearchControllerDelegate>)searchResults
96207
}
97208
}
98209

99-
- (BOOL)active {
210+
- (BOOL)isActive {
100211
if (self.searchController) {
101212
return self.searchController.active;
102213
} else {
103214
return self.searchDisplayController.active;
104215
}
105216
}
106217

218+
- (void)setActive:(BOOL)active {
219+
[self setActive:active animated:NO];
220+
}
221+
222+
- (void)setActive:(BOOL)active animated:(BOOL)animated {
223+
if (self.searchController) {
224+
self.searchController.active = active;
225+
} else {
226+
[self.searchDisplayController setActive:active animated:animated];
227+
}
228+
}
229+
107230
- (UITableView *)tableView {
108231
if (self.searchController) {
109232
return ((QMUICommonTableViewController *)self.searchController.searchResultsController).tableView;
@@ -113,7 +236,7 @@ - (UITableView *)tableView {
113236
}
114237

115238
- (void)removeDefaultEmptyLabelInSearchDisplayController {
116-
// 移除UISearchDisplayController自带的“无结果”的label
239+
// 移除 UISearchDisplayController 自带的“无结果”的label
117240
for (UIView *subview in self.searchDisplayController.searchResultsTableView.subviews) {
118241
if ([subview isKindOfClass:[UILabel class]]) {
119242
[subview removeFromSuperview];
@@ -123,6 +246,16 @@ - (void)removeDefaultEmptyLabelInSearchDisplayController {
123246
}
124247
}
125248

249+
- (void)setLaunchView:(UIView *)dimmingView {
250+
_launchView = dimmingView;
251+
252+
if (self.searchController) {
253+
self.searchController.customDimmingView = _launchView;
254+
} else {
255+
self.searchDisplayController.customDimmingView = _launchView;
256+
}
257+
}
258+
126259
#pragma mark - QMUIEmptyView
127260

128261
- (void)showEmptyView {
@@ -156,7 +289,7 @@ - (void)showEmptyView {
156289

157290
- (BOOL)layoutEmptyView {
158291
if ([self.emptyView.superview isKindOfClass:[UITableView class]]) {
159-
// iOS7 UISearchDisplayController里,会把emptyView加到searchResultsTableView上,参照showEmptyView里的代码
292+
// iOS7 UISearchDisplayController 里,会把emptyView加到searchResultsTableView上,参照showEmptyView里的代码
160293
UITableView *tableView = (UITableView *)self.emptyView.superview;
161294
CGSize newEmptyViewSize = CGSizeMake(CGRectGetWidth(tableView.bounds) - UIEdgeInsetsGetHorizontalValue(tableView.contentInset), CGRectGetHeight(tableView.frame) - UIEdgeInsetsGetVerticalValue(tableView.contentInset));
162295
CGSize oldEmptyViewSize = self.emptyView.frame.size;

0 commit comments

Comments
 (0)