iOS 搜索页面—— 热门搜索和历史记录

在接到这个需求的时候,搜索页面—— 热门搜索和历史记录,第一反应就想到了之前看到的 PYSearch 的轮子,其中基本功能还是很明确的,但是和我们的需求还是有点不符合,于是只能自己来先写一个Demo啦,当然其中 PYSearch 有很多是可以借鉴的。

iOS 搜索页面—— 热门搜索和历史记录_第1张图片
效果图
在这过程中遇到可以探讨的问题:
  • 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 *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 的过程。

你可能感兴趣的:(iOS 搜索页面—— 热门搜索和历史记录)