在接到这个需求的时候,搜索页面—— 热门搜索和历史记录,第一反应就想到了之前看到的 PYSearch 的轮子,其中基本功能还是很明确的,但是和我们的需求还是有点不符合,于是只能自己来先写一个Demo啦,当然其中 PYSearch 有很多是可以借鉴的。
在这过程中遇到可以探讨的问题:
- 1、类似排行榜布局,是直接用一个 View 上放多个 UIButton 布局呢?还是用 UITableViewCell 呢?
如果用 UITableViewCell 如何巧妙的对半分呢? - 2、如何根据文字的长短紧凑的排序布局?
- 3、一般是 Masonry 布局,还是 用 Frame 搭 UI ?
- 4、如何显示每个UILabel 上的取消按钮,方案多种,但是哪一种更优呢?
- 5、由于我是通过设计 Cell 布局的,如何确定高度呢?
1、类似排行榜布局,是直接用一个 View 上放多个 UIButton 布局呢?还是用 UITableViewCell 呢?
我整体是用 UITableView 的,分为两个 Section 去完成的,Cell 和 Header 一起用。但是类似于上部分 热词排行,是用 UITableViewCell 展示,还是用 直接在 UITableViewCell 上添加逐个呢? 我后来是采取的后一种方式,逐一添加:
import "SearchHotRankCell.h"
static const CGFloat kSearchHotRankSubViewHeight = 35.0f;
static const NSInteger kSearchHotRangContentViewTag = 1300;
@interface SearchHotRankCell ()
@property (nonatomic, strong) NSArray<NSString *> *hotArray;
@end
@implementation SearchHotRankCell
#pragma mark - Init
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.selectionStyle = UITableViewCellSelectionStyleNone;
}
return self;
}
#pragma mark - Method
- (void)setHotViewWithArray:(NSArray *)hotArray {
self.hotArray = hotArray;
[self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
for (int i = 0; i < hotArray.count; i++) {
// 整体内容
UIView *backView = [[UIView alloc] initWithFrame:CGRectMake(i%2 * (SCREEN_WIDTH/2.0), i/2 * kSearchHotRankSubViewHeight, SCREEN_WIDTH/2.0, kSearchHotRankSubViewHeight)];
[self.contentView addSubview:backView];
// 标签排名
UILabel *rankLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, 10, 15, 15)];
rankLabel.textAlignment = NSTextAlignmentCenter;
rankLabel.font = [UIFont systemFontOfSize:10];
rankLabel.layer.cornerRadius = 3;
rankLabel.text = [NSString stringWithFormat:@"%d", i + 1];
switch (i) {
case 0: // 第一名
rankLabel.backgroundColor = [UIColor redColor];
rankLabel.textColor = [UIColor whiteColor];
break;
case 1: // 第二名
rankLabel.backgroundColor = [UIColor orangeColor];
rankLabel.textColor = [UIColor whiteColor];
break;
case 2: // 第三名
rankLabel.backgroundColor = [UIColor yellowColor];
rankLabel.textColor = [UIColor whiteColor];
break;
default: // 其他
rankLabel.backgroundColor = [UIColor lightGrayColor];
rankLabel.textColor = [UIColor colorWithRed:113/255.0 green:113/255.0f blue:113/255.0f alpha:1.0];
break;
}
[backView addSubview:rankLabel];
// 标签内容
UILabel *contentLabel = [[UILabel alloc]initWithFrame:CGRectMake(40, 0, SCREEN_WIDTH/2.0 - 55, kSearchHotRankSubViewHeight)];
contentLabel.text = hotArray[i];
contentLabel.userInteractionEnabled = YES;
[contentLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tagDidClick:)]];
contentLabel.tag = kSearchHotRangContentViewTag + i;
contentLabel.textColor = [UIColor grayColor];
contentLabel.font = [UIFont systemFontOfSize:14];
[backView addSubview:contentLabel];
}
}
- (void)tagDidClick:(UITapGestureRecognizer *)tapGesture {
UILabel *clickLabel = (UILabel *)tapGesture.view;
NSInteger index = clickLabel.tag - kSearchHotRangContentViewTag;
NSLog(@"index click == %lu",index);
if (self.searchHotRanckTapSelect) {
self.searchHotRanckTapSelect(self.hotArray[index]);
}
}
@end
2、如何根据文字的长短紧凑的排序布局?
类似于历史纪录下部分是如何布局的呢?需要计算其宽度的,并且紧凑在一起...
#import "SearchHistoryCell.h"
#import "CanCancelLabel.h"
static const CGFloat kSearchHistorySubViewHeight = 35.0f; // Item 高度
static const CGFloat kSearchHistorySubViewTopSpace = 10.0f; // 上下间距
static const NSInteger kSearchHistoryContentViewTag = 1400;
static NSString *const kSearchHistoryRowKeyIden = @"kSearchHistoryRowKeyIden";
@interface SearchHistoryCell ()
@property (nonatomic, strong) NSArray *historyArray;
@end
@implementation SearchHistoryCell
#pragma mark - 高度
+ (CGFloat)historyCellHeightWithData:(NSArray *)historyArray {
NSInteger countRow = 0; // 第几行数
countRow = [[NSUserDefaults standardUserDefaults] integerForKey:kSearchHistoryRowKeyIden];
countRow = (historyArray.count > 0) ? (countRow + 1) : 0;
return countRow * (kSearchHistorySubViewHeight + kSearchHistorySubViewTopSpace) + kSearchHistorySubViewTopSpace;
}
#pragma mark - Init
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.selectionStyle = UITableViewCellSelectionStyleNone;
}
return self;
}
#pragma mark - Method
- (void)setHistroyViewWithArray:(NSArray *)historyArray {
self.historyArray = historyArray;
[self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
// 计算位置
CGFloat leftSpace = 15.0f; // 左右空隙
CGFloat topSpace = 10.f; // 上下空隙
CGFloat margin = 15.0f; // 两边的间距
CGFloat currentX = margin; // X
CGFloat currentY = 0; // Y
NSInteger countRow = 0; // 第几行数
CGFloat lastLabelWidth = 0; // 记录上一个宽度
for (int i = 0; i < historyArray.count; i++) {
// 最多显示10个
if (i > 9) {
break;
}
/** 计算Frame */
CGFloat nowWidth = [self textWidth:historyArray[i]];
if (i == 0) {
currentX = currentX + lastLabelWidth;
}
else {
currentX = currentX + leftSpace + lastLabelWidth;
}
currentY = countRow * kSearchHistorySubViewHeight + (countRow + 1) * topSpace;
// 换行
if (currentX + leftSpace + margin + nowWidth >= SCREEN_WIDTH) {
countRow++;
currentY = currentY + kSearchHistorySubViewHeight + topSpace;
currentX = margin;
}
lastLabelWidth = nowWidth;
// 文字内容
CanCancelLabel *contentLabel = [[CanCancelLabel alloc] initWithFrame:CGRectMake(currentX, currentY, nowWidth, kSearchHistorySubViewHeight)];
/** Label 具体显示 */
contentLabel.titleContent = historyArray[i];
[contentLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tagDidClick:)]];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressClick:)];
longPress.minimumPressDuration = 1.5f;
[contentLabel addGestureRecognizer:longPress];
contentLabel.tag = kSearchHistoryContentViewTag + i;
[self.contentView addSubview:contentLabel];
}
[[NSUserDefaults standardUserDefaults] setInteger:countRow forKey:kSearchHistoryRowKeyIden];
}
- (CGFloat)textWidth:(NSString *)text {
CGFloat width = [text boundingRectWithSize:CGSizeMake(SCREEN_WIDTH, kSearchHistorySubViewHeight) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil].size.width + 20;
// 防止 宽度过大
if (width > SCREEN_WIDTH - 30) {
width = SCREEN_WIDTH - 30;
}
return width;
}
#pragma mark - Private Method
- (void)tagDidClick:(UITapGestureRecognizer *)tapGesture {
CanCancelLabel *clickLabel = (CanCancelLabel *)tapGesture.view;
NSInteger index = clickLabel.tag - kSearchHistoryContentViewTag;
NSLog(@"history == %lu",index);
if (self.searchTapHistoryBlock) {
self.searchTapHistoryBlock(index);
}
}
- (void)longPressClick:(UILongPressGestureRecognizer *)panGesture {
CanCancelLabel *clickLabel = (CanCancelLabel *)panGesture.view;
NSInteger index = clickLabel.tag - kSearchHistoryContentViewTag;
if(panGesture.state == UIGestureRecognizerStateBegan) {
NSLog(@"long Press == %lu",index);
clickLabel.showCancel = NO;
__weak typeof(self) weakSelf = self;
clickLabel.clearOneHistroyBlock = ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf.searchLongPressClearHistoryBlock) {
strongSelf.searchLongPressClearHistoryBlock(index);
}
};
}
}
@end
此处的 Frame 计算,还可以 优化。
3、一般是 Masonry 布局,还是 用 Frame 搭 UI。
通过上述 Demo 中,我发现自己一般情况下是用 Masonry, 但是遇到类似用 For 循环的却是是用 Frame 的搭 UI的,所以自己需要总结下到底什么时候,需要用到 Frame,毕竟项目中大部分还是用 Masonry 的。
用 Masonry 是为了便于界面适配,也是为了保持整体代码统一,不能 Frame 和 Masonry 乱用,但实际上直接用 Frame 的效率却是要高一些,所以也不能排斥它。
因此就得出自己的小结论,在类似 For 循环的情况下 或者 UITableViewHeaderView 的时候才用 Frame,其他时候还是用 Masonry 。
4、如何显示每个UILabel 上的取消按钮,方案多种,但是哪一种更优呢?
后来我是采取自定义 CanCancelLabel 的方式,来实现长按手势之后显示 取消效果的。
5、由于我是通过设计 Cell 布局的,如何确定高度呢?
5-1、给予其 SearchHotRankCell (热词排行)一个加方法:
+ (CGFloat)hotRankCellHeightWithData:(NSArray *)hotArray {
return ceilf((CGFloat)hotArray.count/2) * kSearchHotRankSubViewHeight;
}
此处用到 ceil\ceilf 数学函数,向上取整。
5-2、给予其 SearchHistoryCell (历史词汇) 这个确定高度,有点绕
- 直接用 加方法,等于是要重算一次 有几行,感觉计算量蛮多的,没必要
- 通过值传出去,感觉不符合高内聚低耦合的思想,多处使用这个高度。
所以小纠结啊,看到 PYSearch 中采取了个CGRectGetMaxY 的方法,然而此处还是不适用此处,犹如第二种方法,而且他那边不是用的 Cell 来实现的。
// 设置contentView高度
contentView.py_height = CGRectGetMaxY(contentView.subviews.lastObject.frame);
最终还是先采取了临时保存量的方式记录的,用 NSUserDefaults 保存临时值的。
+ (CGFloat)historyCellHeightWithData:(NSArray *)historyArray {
NSInteger countRow = 0; // 换几次行
countRow = [[NSUserDefaults standardUserDefaults] integerForKey:kSearchHistoryRowKeyIden];
countRow = (historyArray.count > 0) ? (countRow + 1) : 0;
return countRow * (kSearchHistorySubViewHeight + kSearchHistorySubViewTopSpace) + kSearchHistorySubViewTopSpace;
}
如想到更好的方法再优化,如有朋友有好的方法,欢迎指导。
整体来说,这又是一个简单搭建熟练的 UI 的过程。