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

在接到这个需求的时候,搜索页面—— 热门搜索和历史记录,第一反应就想到了之前看到的 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 的过程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容