// // XJHealthKnowledgeView.m // XinjiangProject // // Created by Apple on 2026/6/29. // #import "XJHealthKnowledgeView.h" #import "XJHealthKnowledgeModel.h" #import "XJHealthKnowledgeCell.h" static CGFloat const kHeaderHeight = 40; static CGFloat const kSearchBoxHeight = 36; static CGFloat const kCellHeight = 130; static CGFloat const kMaxCellCount = 3; static CGFloat const kTabButtonGap = 43; static CGFloat const kDefaultEmptyHeight = 200; // 标签区:文字 14pt(≈17) + 指示间距6 + 指示2 + 分割间距9 + 分割1 = ~35 static CGFloat const kTabAreaHeight = 48; @interface XJHealthKnowledgeView () @property (nonatomic, strong) UIView * headerView; @property (nonatomic, strong) UIImageView * headerBgImg; @property (nonatomic, strong) UILabel * titleLabel; @property (nonatomic, strong) UIButton * moreBtn; @property (nonatomic, strong) UIView * searchContainer; @property (nonatomic, strong) UIImageView * searchIcon; @property (nonatomic, strong) UITextField * searchTextField; @property (nonatomic, strong) UIButton * searchBtn; @property (nonatomic, strong) UIScrollView * tabScrollView; @property (nonatomic, strong) NSMutableArray * tabButtons; @property (nonatomic, strong) UIView * indicatorView; @property (nonatomic, strong) UIView * tabSeparator; @property (nonatomic, strong) UITableView * listTableView; @property (nonatomic, strong) UILabel * emptyLabel; @end @implementation XJHealthKnowledgeView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = KWhiteColor; self.layer.cornerRadius = 12; self.layer.masksToBounds = YES; self.tabButtons = [[NSMutableArray alloc] init]; [self createUI]; } return self; } #pragma mark - ————— UI 搭建 ————— - (void)createUI { // 头部 self.headerView = [[UIView alloc] init]; [self addSubview:self.headerView]; [self.headerView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.top.mas_offset(0); make.height.mas_offset(kRealValue(kHeaderHeight)); }]; self.headerBgImg = [[UIImageView alloc] init]; self.headerBgImg.image = [UIImage imageNamed:@"heaedTitleBackImg"]; [self.headerView addSubview:self.headerBgImg]; [self.headerBgImg mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_offset(0); }]; self.titleLabel = [[UILabel alloc] init]; self.titleLabel.text = @"健康知识"; self.titleLabel.textColor = CFontColor1; self.titleLabel.font = BOLDSYSTEMFONT(16); [self.headerView addSubview:self.titleLabel]; [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_offset(13); make.centerY.mas_equalTo(self.headerView); }]; self.moreBtn = [[UIButton alloc] init]; [self.moreBtn setImage:[UIImage imageNamed:@"wacthRightAccow"] forState:UIControlStateNormal]; [self.moreBtn addTarget:self action:@selector(moreAction) forControlEvents:UIControlEventTouchUpInside]; [self.headerView addSubview:self.moreBtn]; [self.moreBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.right.mas_offset(-8); make.centerY.mas_equalTo(self.headerView); make.width.height.mas_offset(30); }]; // 搜索框 - 圆角 18 背景 #F5F7FB self.searchContainer = [[UIView alloc] init]; self.searchContainer.backgroundColor = UIColorHex(#F5F7FB); self.searchContainer.layer.cornerRadius = 18; self.searchContainer.layer.masksToBounds = YES; self.searchContainer.hidden = YES; // 初始隐藏,等数据来了再显示 [self addSubview:self.searchContainer]; [self.searchContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_offset(15); make.right.mas_offset(-15); make.top.mas_equalTo(self.headerView.mas_bottom).offset(10); make.height.mas_offset(kSearchBoxHeight); }]; self.searchIcon = [[UIImageView alloc] init]; self.searchIcon.image = [UIImage imageNamed:@"searchLeftIcon"]; self.searchIcon.contentMode = UIViewContentModeScaleAspectFit; [self.searchContainer addSubview:self.searchIcon]; [self.searchIcon mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_offset(15); make.centerY.mas_equalTo(self.searchContainer); make.width.height.mas_offset(14); }]; self.searchBtn = [[UIButton alloc] init]; self.searchBtn.backgroundColor = UIColorHex(#14BEBE); self.searchBtn.layer.cornerRadius = 14; self.searchBtn.layer.masksToBounds = YES; [self.searchBtn setTitle:@"搜索" forState:UIControlStateNormal]; [self.searchBtn setTitleColor:KWhiteColor forState:UIControlStateNormal]; self.searchBtn.titleLabel.font = MEDIUMFONT(14); [self.searchBtn addTarget:self action:@selector(searchAction) forControlEvents:UIControlEventTouchUpInside]; [self.searchContainer addSubview:self.searchBtn]; [self.searchBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.right.mas_offset(0); make.centerY.mas_equalTo(self.searchContainer); make.width.mas_offset(55); make.height.mas_offset(28); }]; self.searchTextField = [[UITextField alloc] init]; self.searchTextField.placeholder = @"请输入关键字进行搜索"; self.searchTextField.font = [UIFont systemFontOfSize:13 weight:UIFontWeightRegular]; self.searchTextField.textColor = UIColorHex(#252535); // placeholder 颜色 self.searchTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"请输入关键字进行搜索" attributes:@{NSForegroundColorAttributeName: UIColorHex(#B6B6B6)}]; self.searchTextField.delegate = self; [self.searchContainer addSubview:self.searchTextField]; [self.searchTextField mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.searchIcon.mas_right).offset(8); make.right.mas_equalTo(self.searchBtn.mas_left).offset(-8); make.centerY.mas_equalTo(self.searchContainer); make.height.mas_equalTo(self.searchContainer); }]; // 标签切换区 self.tabScrollView = [[UIScrollView alloc] init]; self.tabScrollView.showsHorizontalScrollIndicator = NO; self.tabScrollView.showsVerticalScrollIndicator = NO; self.tabScrollView.hidden = YES; // 初始隐藏 [self addSubview:self.tabScrollView]; [self.tabScrollView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_offset(0); make.top.mas_equalTo(self.searchContainer.mas_bottom).offset(12); make.height.mas_offset(kTabAreaHeight); }]; // 指示条 13×2 self.indicatorView = [[UIView alloc] init]; self.indicatorView.backgroundColor = UIColorHex(#14BEBE); self.indicatorView.layer.cornerRadius = 1; self.indicatorView.layer.masksToBounds = YES; self.indicatorView.hidden = YES; [self.tabScrollView addSubview:self.indicatorView]; // 分割线 在指示条底部 +9 self.tabSeparator = [[UIView alloc] init]; self.tabSeparator.backgroundColor = UIColorHex(#EAECF1); [self.tabScrollView addSubview:self.tabSeparator]; // 列表 self.listTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; self.listTableView.delegate = self; self.listTableView.dataSource = self; self.listTableView.separatorStyle = UITableViewCellSeparatorStyleNone; self.listTableView.scrollEnabled = NO; self.listTableView.rowHeight = kCellHeight; self.listTableView.backgroundColor = KWhiteColor; self.listTableView.hidden = YES; // 初始隐藏 if (@available(iOS 15.0, *)) { self.listTableView.sectionHeaderTopPadding = 0; } [self addSubview:self.listTableView]; [self.listTableView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_offset(0); make.top.mas_equalTo(self.tabScrollView.mas_bottom); make.height.mas_offset(0); }]; // 空状态 - 放在 tabScrollView 下方 self.emptyLabel = [[UILabel alloc] init]; self.emptyLabel.text = @"暂无健康知识"; self.emptyLabel.textColor = UIColorHex(#14BEBE); self.emptyLabel.font = [UIFont systemFontOfSize:15]; self.emptyLabel.textAlignment = NSTextAlignmentCenter; self.emptyLabel.hidden = YES; [self addSubview:self.emptyLabel]; [self.emptyLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self); make.top.mas_equalTo(self.tabScrollView.mas_bottom).offset(40); }]; } #pragma mark - ————— Setter ————— - (void)setDataArray:(NSArray *)dataArray { _dataArray = dataArray; BOOL hasData = dataArray.count > 0; self.listTableView.hidden = !hasData; self.emptyLabel.hidden = hasData; if (hasData) { [self.listTableView reloadData]; CGFloat tableH = MIN(dataArray.count, kMaxCellCount) * kCellHeight; [self.listTableView mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_offset(0); make.top.mas_equalTo(self.tabScrollView.mas_bottom); make.height.mas_offset(tableH); }]; } else { [self.listTableView mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_offset(0); make.top.mas_equalTo(self.tabScrollView.mas_bottom); make.height.mas_offset(0); }]; } } - (void)setTabTitles:(NSArray *)tabTitles { _tabTitles = tabTitles; for (UIButton * btn in self.tabButtons) { [btn removeFromSuperview]; } [self.tabButtons removeAllObjects]; self.tabScrollView.hidden = (tabTitles.count == 0); self.searchContainer.hidden = (tabTitles.count == 0); if (tabTitles.count == 0) { self.indicatorView.hidden = YES; return; } self.indicatorView.hidden = NO; // 按钮高度给满 kTabAreaHeight 让点击范围大 CGFloat btnHeight = kTabAreaHeight - 2 - 6 - 9 - 1; // 文字区域 ≈30pt CGFloat btnTop = 0; CGFloat leftOffset = 15; for (NSInteger i = 0; i < tabTitles.count; i++) { NSString * title = tabTitles[i]; CGSize textSize = [title sizeWithAttributes:@{NSFontAttributeName: BOLDSYSTEMFONT(14)}]; CGFloat btnWidth = ceil(textSize.width) + 4; // 左右各留 2pt // 透明大按钮包住文字区域 UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom]; [btn setTitle:title forState:UIControlStateNormal]; btn.titleLabel.font = (i == self.selectedTabIndex) ? BOLDSYSTEMFONT(14) : [UIFont systemFontOfSize:14 weight:UIFontWeightRegular]; [btn setTitleColor:(i == self.selectedTabIndex) ? UIColorHex(#14BEBE) : UIColorHex(#808080) forState:UIControlStateNormal]; btn.backgroundColor = KClearColor; btn.tag = i; [btn addTarget:self action:@selector(tabButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; btn.frame = CGRectMake(leftOffset, btnTop, btnWidth, btnHeight); [self.tabScrollView addSubview:btn]; [self.tabButtons addObject:btn]; leftOffset += btnWidth + kTabButtonGap; } leftOffset += 15 - kTabButtonGap; self.tabScrollView.contentSize = CGSizeMake(leftOffset, kTabAreaHeight); // 更新分割线位置:在标签区底部 self.tabSeparator.frame = CGRectMake(0, kTabAreaHeight - 1, MAX(leftOffset, self.tabScrollView.bounds.size.width), 1); // 强制布局,确保按钮 titleLabel.frame 计算正确 [self.tabScrollView layoutIfNeeded]; if (self.tabButtons.count > 0) { [self updateIndicatorPosition:self.selectedTabIndex animated:NO]; } } - (void)setSelectedTabIndex:(NSInteger)selectedTabIndex { if (selectedTabIndex < 0 || selectedTabIndex >= self.tabButtons.count) return; _selectedTabIndex = selectedTabIndex; for (NSInteger i = 0; i < self.tabButtons.count; i++) { UIButton * btn = self.tabButtons[i]; if (i == selectedTabIndex) { [btn setTitleColor:UIColorHex(#14BEBE) forState:UIControlStateNormal]; btn.titleLabel.font = BOLDSYSTEMFONT(14); } else { [btn setTitleColor:UIColorHex(#808080) forState:UIControlStateNormal]; btn.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightRegular]; } } [self.tabScrollView layoutIfNeeded]; [self updateIndicatorPosition:selectedTabIndex animated:YES]; } - (void)showNotLoggedInPlaceholder { self.dataArray = @[]; self.tabTitles = nil; self.emptyLabel.text = @"暂未登录"; self.emptyLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0]; self.emptyLabel.hidden = NO; // 未登录时在头部下方区域上下居中 [self.emptyLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self); make.top.mas_equalTo(self.headerView.mas_bottom); make.bottom.mas_equalTo(self); }]; } #pragma mark - ————— 高度计算 ————— - (CGFloat)totalHeight { BOOL hasTabs = self.tabTitles.count > 0; BOOL hasData = self.dataArray.count > 0; if (!hasTabs) { return kDefaultEmptyHeight; } // 头部(40) + 10 + 搜索(36) + 12 + 标签(48) + 列表/空区域 CGFloat base = kRealValue(kHeaderHeight) + 10 + kSearchBoxHeight + 12 + kTabAreaHeight; if (hasData) { CGFloat tableH = MIN(self.dataArray.count, kMaxCellCount) * kCellHeight; return base + tableH; } else { return base + 80; } } #pragma mark - ————— 私有 ————— - (void)updateIndicatorPosition:(NSInteger)index animated:(BOOL)animated { if (index < 0 || index >= self.tabButtons.count) return; UIButton * selectedBtn = self.tabButtons[index]; CGFloat indicatorW = 13; CGFloat indicatorH = 2; CGFloat indicatorX = selectedBtn.center.x - indicatorW / 2; // 文字底部 +6 CGFloat textBottom = CGRectGetMaxY(selectedBtn.titleLabel.frame); // 兜底:14pt 字体行高 ≈ 17,垂直居中 if (textBottom <= 0) { CGFloat lineH = 17; textBottom = (selectedBtn.frame.size.height - lineH) / 2.0 + lineH; } CGFloat indicatorY = textBottom + 6; void (^updateBlock)(void) = ^{ self.indicatorView.frame = CGRectMake(indicatorX, indicatorY, indicatorW, indicatorH); }; if (animated) { [UIView animateWithDuration:0.25 animations:updateBlock]; } else { updateBlock(); } } #pragma mark - ————— 事件 ————— - (void)moreAction { if (self.moreBlock) self.moreBlock(); } - (void)searchAction { if (self.searchBlock) self.searchBlock(self.searchTextField.text ?: @""); } - (void)tabButtonTapped:(UIButton *)sender { if (sender.tag == self.selectedTabIndex) return; self.selectedTabIndex = sender.tag; if (self.tabSelectBlock) self.tabSelectBlock(sender.tag); } #pragma mark - ————— UITextFieldDelegate ————— - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { return YES; } - (void)textFieldDidBeginEditing:(UITextField *)textField { if (self.searchBeginBlock) self.searchBeginBlock(); } - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; if (self.searchBlock) self.searchBlock(textField.text ?: @""); return YES; } #pragma mark - ————— UITableViewDataSource ————— - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return MIN(self.dataArray.count, kMaxCellCount); } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString * cellID = @"XJHealthKnowledgeCell"; XJHealthKnowledgeCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID]; if (!cell) { cell = [[XJHealthKnowledgeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; } cell.model = self.dataArray[indexPath.row]; return cell; } #pragma mark - ————— UITableViewDelegate ————— - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (self.cellSelectBlock && indexPath.row < self.dataArray.count) { self.cellSelectBlock(self.dataArray[indexPath.row]); } } @end