Files
xj-ios/XinjiangProject/XinjiangProject/Project/HomeV2/View/XJHealthKnowledgeView.m
T
jiayangyangandClaude Opus 4.7 ee498064cc feat(sos): 集成微信SDK,实现地图卡片长按转发微信;新增急救资源详情页及群成员面板
- 手动集成微信 OpenSDK-NoPay,创建 XJWeChatManager 统一管理分享能力
- 地图卡片新增长按手势,通过代理模式实现「转发到微信」,分享含高德导航链接
- 聊天页新增群成员侧滑面板,支持查看成员列表及一键拨号
- 新增急救资源详情页(XJFirstAidResourceDetailVC)及选择联系人页
- 首页健康知识/资讯模块,含双视图列表及 H5 跳转
- 修复急救资源详情页编译报错

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-14 17:53:44 +08:00

435 lines
16 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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 () <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>
@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<UIButton *> * 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<XJHealthKnowledgeModel *> *)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<NSString *> *)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