// // KBSearchVC.m // keyBoard // // Created by Mac on 2025/11/7. // #import "KBSearchVC.h" #import "KBSearchBarView.h" #import "KBSearchSectionHeader.h" #import "KBTagCell.h" #import "KBSkinCardCell.h" static NSString * const kTagCellId = @"KBTagCell"; static NSString * const kSkinCellId = @"KBSkinCardCell"; static NSString * const kHeaderId = @"KBSearchSectionHeader"; typedef NS_ENUM(NSInteger, KBSearchSection) { KBSearchSectionHistory = 0, KBSearchSectionRecommend = 1, }; @interface KBSearchVC () // 顶部搜索栏(封装的 View)——放到导航栏的 titleView 中,Y 轴与返回按钮一致 @property (nonatomic, strong) KBSearchBarView *searchBarView; @property (nonatomic, strong) UIView *titleContainer; // 承载 searchBarView 的容器 // 列表 @property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout; // 数据 @property (nonatomic, strong) NSMutableArray *historyWords; // 历史搜索 @property (nonatomic, strong) NSArray *recommendItems; // 推荐数据(title/price) @end @implementation KBSearchVC - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; // UI [self.view addSubview:self.collectionView]; // 将搜索栏放进导航栏 titleView,Y 轴自然与返回按钮对齐 self.navigationItem.titleView = self.titleContainer; // 布局 - Masonry(列表顶端紧贴导航栏底部) [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.view.mas_top).offset(KB_NAV_TOTAL_HEIGHT + 8); make.left.right.bottom.equalTo(self.view); }]; // 初始化测试数据 self.historyWords = [@[@"果冻橙", @"芒果", @"有机水果卷心菜", @"水果萝卜", @"熟冻帝王蟹", @"赣南脐橙"] mutableCopy]; self.recommendItems = @[ @{@"title":@"Dopamine", @"price":@"20"}, @{@"title":@"Dopamine", @"price":@"20"}, @{@"title":@"Dopamine", @"price":@"20"}, @{@"title":@"Dopamine", @"price":@"20"}, @{@"title":@"Dopamine", @"price":@"20"}, @{@"title":@"Dopamine", @"price":@"20"}, ]; [self.collectionView reloadData]; } #pragma mark - Private /// 执行搜索:简单将关键字加入历史 - (void)performSearch:(NSString *)kw { NSString *trim = [kw stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (trim.length == 0) { return; } // 去重插入到最前 [self.historyWords removeObject:trim]; [self.historyWords insertObject:trim atIndex:0]; [self.collectionView reloadData]; } /// 清空历史 - (void)clearHistory { [self.historyWords removeAllObjects]; [self.collectionView reloadData]; } #pragma mark - UICollectionViewDataSource - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 2; // 历史 + 推荐 } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { if (section == KBSearchSectionHistory) { return self.historyWords.count; // 无历史则为 0 } return self.recommendItems.count; } - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == KBSearchSectionHistory) { KBTagCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kTagCellId forIndexPath:indexPath]; [cell config:self.historyWords[indexPath.item]]; return cell; } KBSkinCardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kSkinCellId forIndexPath:indexPath]; NSDictionary *it = self.recommendItems[indexPath.item]; [cell configWithTitle:it[@"title"] imageURL:nil price:it[@"price"]]; return cell; } - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { if (kind == UICollectionElementKindSectionHeader) { KBSearchSectionHeader *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kHeaderId forIndexPath:indexPath]; if (indexPath.section == KBSearchSectionHistory) { // 当没有历史时,外部通过 sizeForHeader 返回 0,这里仍设置但不会显示 [header configWithTitle:@"Historical Search" showTrash:self.historyWords.count > 0]; __weak typeof(self) weakSelf = self; header.onTapTrash = ^{ [weakSelf clearHistory]; }; } else { [header configWithTitle:@"Recommended Skin" showTrash:NO]; } return header; } return [UICollectionReusableView new]; } #pragma mark - UICollectionViewDelegateFlowLayout - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { CGFloat width = collectionView.bounds.size.width; if (indexPath.section == KBSearchSectionHistory) { NSString *t = self.historyWords[indexPath.item]; return [KBTagCell sizeForText:t]; } // 两列卡片 CGFloat inset = 16; // 左右间距 CGFloat spacing = 12; // 列间距 CGFloat w = floor((width - inset*2 - spacing) / 2.0); // 高度:封面 0.75w + 标题 + 价格 + 边距,近似定高 CGFloat h = w*0.75 + 8 + 20 + 10 + 6 + 8; // 估算 return CGSizeMake(w, h); } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { if (section == KBSearchSectionHistory) { return UIEdgeInsetsMake(8, 16, 12, 16); } return UIEdgeInsetsMake(8, 16, 20, 16); } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { return section == KBSearchSectionHistory ? 8 : 12; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return section == KBSearchSectionHistory ? 8 : 16; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { // 当历史记录为空时,header 高度为 0,从而不显示标题与垃圾桶 if (section == KBSearchSectionHistory) { return self.historyWords.count == 0 ? CGSizeZero : CGSizeMake(collectionView.bounds.size.width, 40); } return CGSizeMake(collectionView.bounds.size.width, 40); } #pragma mark - UICollectionViewDelegate - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == KBSearchSectionHistory) { NSString *kw = self.historyWords[indexPath.item]; [self.searchBarView updateKeyword:kw]; [self performSearch:kw]; } } #pragma mark - Lazy - (KBSearchBarView *)searchBarView { if (!_searchBarView) { _searchBarView = [[KBSearchBarView alloc] init]; _searchBarView.placeholder = @"Themes"; __weak typeof(self) weakSelf = self; _searchBarView.onSearch = ^(NSString * _Nonnull keyword) { [weakSelf performSearch:keyword]; }; } return _searchBarView; } - (UIView *)titleContainer { if (!_titleContainer) { // 固定尺寸:宽 315,高 36(与返回按钮同一 Y 轴,作为 titleView 显示) CGFloat width = KBFit(315.0); _titleContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 36.0)]; _titleContainer.backgroundColor = [UIColor clearColor]; // 告诉导航栏使用 AutoLayout 计算尺寸,并强约束宽高为 315x36 _titleContainer.translatesAutoresizingMaskIntoConstraints = NO; [[_titleContainer.widthAnchor constraintEqualToConstant:width] setActive:YES]; [[_titleContainer.heightAnchor constraintEqualToConstant:36.0] setActive:YES]; [_titleContainer setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; [_titleContainer setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; // 将 searchBarView 塞入容器,充满容器 [_titleContainer addSubview:self.searchBarView]; [self.searchBarView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(_titleContainer); make.height.mas_equalTo(36.0); }]; } return _titleContainer; } - (UICollectionViewFlowLayout *)flowLayout { if (!_flowLayout) { _flowLayout = [[UICollectionViewFlowLayout alloc] init]; _flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical; _flowLayout.sectionHeadersPinToVisibleBounds = NO; } return _flowLayout; } - (UICollectionView *)collectionView { if (!_collectionView) { _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout]; _collectionView.backgroundColor = [UIColor whiteColor]; _collectionView.dataSource = self; _collectionView.delegate = self; // 注册 cell & header [_collectionView registerClass:KBTagCell.class forCellWithReuseIdentifier:kTagCellId]; [_collectionView registerClass:KBSkinCardCell.class forCellWithReuseIdentifier:kSkinCellId]; [_collectionView registerClass:KBSearchSectionHeader.class forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kHeaderId]; } return _collectionView; } @end