refactor: 重构 EPMomentViewController,替换 UITableView 为 EPMomentListView
主要变更: 1. 移除 UITableView,改为使用 EPMomentListView 以简化数据展示和交互。 2. 添加顶部固定文案 UILabel,提升用户体验。 3. 通过 EPMomentAPIHelper 统一管理 Moments 列表 API 请求,优化数据加载逻辑。 4. 更新 UI 约束,确保布局适配不同屏幕。 此重构旨在提升代码可维护性和用户界面的一致性。
This commit is contained in:
@@ -7,34 +7,20 @@
|
||||
//
|
||||
|
||||
#import "EPMomentViewController.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "EPMomentCell.h"
|
||||
#import "Api+Moments.h"
|
||||
#import "AccountInfoStorage.h"
|
||||
#import "MomentsInfoModel.h"
|
||||
#import "EPMomentListView.h"
|
||||
|
||||
@interface EPMomentViewController () <UITableViewDelegate, UITableViewDataSource>
|
||||
@interface EPMomentViewController ()
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
/// 主列表(卡片式布局)
|
||||
@property (nonatomic, strong) UITableView *tableView;
|
||||
/// 列表视图(MVVM:View)
|
||||
@property (nonatomic, strong) EPMomentListView *listView;
|
||||
|
||||
/// 刷新控件
|
||||
@property (nonatomic, strong) UIRefreshControl *refreshControl;
|
||||
|
||||
/// 发布按钮
|
||||
@property (nonatomic, strong) UIButton *publishButton;
|
||||
|
||||
// MARK: - Data
|
||||
|
||||
/// 动态数据源(MomentsInfoModel 数组)
|
||||
@property (nonatomic, strong) NSMutableArray<MomentsInfoModel *> *dataSource;
|
||||
|
||||
/// 当前页码
|
||||
@property (nonatomic, assign) NSInteger currentPage;
|
||||
|
||||
/// 是否正在加载
|
||||
@property (nonatomic, assign) BOOL isLoading;
|
||||
/// 顶部固定文案
|
||||
@property (nonatomic, strong) UILabel *topTipLabel;
|
||||
|
||||
@end
|
||||
|
||||
@@ -48,7 +34,7 @@
|
||||
self.title = @"动态";
|
||||
|
||||
[self setupUI];
|
||||
[self loadData];
|
||||
[self.listView reloadFirstPage];
|
||||
|
||||
NSLog(@"[EPMomentViewController] 页面加载完成");
|
||||
}
|
||||
@@ -74,70 +60,29 @@
|
||||
make.edges.mas_equalTo(self.view);
|
||||
}];
|
||||
|
||||
// TableView
|
||||
[self.view addSubview:self.tableView];
|
||||
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.view);
|
||||
// 顶部固定文案
|
||||
[self.view addSubview:self.topTipLabel];
|
||||
[self.topTipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(8);
|
||||
make.left.equalTo(self.view).offset(20);
|
||||
make.right.equalTo(self.view).offset(-20);
|
||||
}];
|
||||
|
||||
// TODO: 调整为右上角
|
||||
[self.view addSubview:self.publishButton];
|
||||
[self.publishButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.view).offset(-20);
|
||||
make.bottom.equalTo(self.view).offset(-100); // 避开 TabBar
|
||||
make.size.mas_equalTo(CGSizeMake(56, 56));
|
||||
// 列表视图
|
||||
[self.view addSubview:self.listView];
|
||||
[self.listView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.bottom.equalTo(self.view);
|
||||
make.top.equalTo(self.topTipLabel.mas_bottom).offset(8);
|
||||
}];
|
||||
|
||||
// 右上角发布按钮
|
||||
UIBarButtonItem *publishItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(onPublishButtonTapped)];
|
||||
self.navigationItem.rightBarButtonItem = publishItem;
|
||||
|
||||
NSLog(@"[EPMomentViewController] UI 设置完成");
|
||||
}
|
||||
|
||||
// MARK: - Data Loading
|
||||
|
||||
- (void)loadData {
|
||||
if (self.isLoading) return;
|
||||
|
||||
self.isLoading = YES;
|
||||
NSLog(@"[EPMomentViewController] 开始加载数据,页码: %ld", (long)self.currentPage);
|
||||
|
||||
// 调用真实 API 加载动态列表
|
||||
NSString *page = [NSString stringWithFormat:@"%ld", (long)self.currentPage];
|
||||
NSString *pageSize = @"10";
|
||||
NSString *types = @"0,2"; // 类型:0=图片,2=文字
|
||||
|
||||
@kWeakify(self);
|
||||
[Api momentsRecommendList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
@kStrongify(self);
|
||||
self.isLoading = NO;
|
||||
[self.refreshControl endRefreshing];
|
||||
|
||||
if (code == 200 && data.data) {
|
||||
// 解析数据
|
||||
NSArray *list = [MomentsInfoModel mj_objectArrayWithKeyValuesArray:data.data];
|
||||
|
||||
if (list.count > 0) {
|
||||
[self.dataSource addObjectsFromArray:list];
|
||||
self.currentPage++;
|
||||
[self.tableView reloadData];
|
||||
NSLog(@"[EPMomentViewController] 加载成功,新增 %lu 条动态", (unsigned long)list.count);
|
||||
} else {
|
||||
NSLog(@"[EPMomentViewController] 没有更多数据");
|
||||
}
|
||||
} else {
|
||||
NSLog(@"[EPMomentViewController] 加载失败: code=%ld, msg=%@", (long)code, msg);
|
||||
// 如果 API 失败,显示提示
|
||||
if (self.dataSource.count == 0) {
|
||||
// 首次加载失败,显示空状态
|
||||
[self showAlertWithMessage:msg ?: @"加载失败"];
|
||||
}
|
||||
}
|
||||
} page:page pageSize:pageSize types:types];
|
||||
}
|
||||
|
||||
- (void)onRefresh {
|
||||
self.currentPage = 0;
|
||||
[self.dataSource removeAllObjects];
|
||||
[self loadData];
|
||||
}
|
||||
// 不再在 VC 内部直接发请求/维护分页
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@@ -155,108 +100,35 @@
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.dataSource.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
EPMomentCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NewMomentCell" forIndexPath:indexPath];
|
||||
|
||||
if (indexPath.row < self.dataSource.count) {
|
||||
MomentsInfoModel *model = self.dataSource[indexPath.row];
|
||||
[cell configureWithModel:model];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
|
||||
NSLog(@"[EPMomentViewController] 点击动态: %ld", (long)indexPath.row);
|
||||
// TODO: 跳转到详情页
|
||||
[self showAlertWithMessage:[NSString stringWithFormat:@"点击了第 %ld 条动态", (long)indexPath.row]];
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return UITableViewAutomaticDimension;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return 200;
|
||||
}
|
||||
|
||||
// 滚动到底部时加载更多
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
CGFloat offsetY = scrollView.contentOffset.y;
|
||||
CGFloat contentHeight = scrollView.contentSize.height;
|
||||
CGFloat screenHeight = scrollView.frame.size.height;
|
||||
|
||||
if (offsetY > contentHeight - screenHeight - 100 && !self.isLoading) {
|
||||
[self loadData];
|
||||
}
|
||||
}
|
||||
// 列表点击回调由 listView 暴露
|
||||
|
||||
// MARK: - Lazy Loading
|
||||
|
||||
- (UITableView *)tableView {
|
||||
if (!_tableView) {
|
||||
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
_tableView.backgroundColor = [UIColor clearColor]; // 透明背景,显示下层背景图
|
||||
_tableView.estimatedRowHeight = 200;
|
||||
_tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
_tableView.showsVerticalScrollIndicator = NO;
|
||||
_tableView.contentInset = UIEdgeInsetsMake(10, 0, 10, 0);
|
||||
|
||||
// 注册 Cell
|
||||
[_tableView registerClass:[EPMomentCell class] forCellReuseIdentifier:@"NewMomentCell"];
|
||||
|
||||
// 添加下拉刷新
|
||||
_tableView.refreshControl = self.refreshControl;
|
||||
// Lazy
|
||||
|
||||
- (EPMomentListView *)listView {
|
||||
if (!_listView) {
|
||||
_listView = [[EPMomentListView alloc] initWithFrame:CGRectZero];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_listView.onSelectMoment = ^(NSInteger index) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
[self showAlertWithMessage:[NSString stringWithFormat:@"点击了第 %ld 条动态", (long)index]];
|
||||
};
|
||||
}
|
||||
return _tableView;
|
||||
return _listView;
|
||||
}
|
||||
|
||||
- (UIRefreshControl *)refreshControl {
|
||||
if (!_refreshControl) {
|
||||
_refreshControl = [[UIRefreshControl alloc] init];
|
||||
[_refreshControl addTarget:self action:@selector(onRefresh) forControlEvents:UIControlEventValueChanged];
|
||||
- (UILabel *)topTipLabel {
|
||||
if (!_topTipLabel) {
|
||||
_topTipLabel = [UILabel new];
|
||||
_topTipLabel.numberOfLines = 0;
|
||||
_topTipLabel.textColor = [UIColor whiteColor];
|
||||
_topTipLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular];
|
||||
_topTipLabel.text = @"The disease is like a cruel ruler, measuring the true length of my life, but it is also like a lamp, illuminating the present that I have always ignored. Now I feel a strange freedom: since the end is clear, I can take every step with my whole heart.";
|
||||
}
|
||||
return _refreshControl;
|
||||
return _topTipLabel;
|
||||
}
|
||||
|
||||
- (UIButton *)publishButton {
|
||||
if (!_publishButton) {
|
||||
_publishButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_publishButton.backgroundColor = [UIColor colorWithRed:0.2 green:0.6 blue:0.86 alpha:1.0]; // 主色调
|
||||
_publishButton.layer.cornerRadius = 28;
|
||||
_publishButton.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
_publishButton.layer.shadowOffset = CGSizeMake(0, 2);
|
||||
_publishButton.layer.shadowOpacity = 0.3;
|
||||
_publishButton.layer.shadowRadius = 4;
|
||||
|
||||
// 设置图标(暂时使用文字)
|
||||
[_publishButton setTitle:@"+" forState:UIControlStateNormal];
|
||||
_publishButton.titleLabel.font = [UIFont systemFontOfSize:32 weight:UIFontWeightLight];
|
||||
[_publishButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
|
||||
[_publishButton addTarget:self action:@selector(onPublishButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _publishButton;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)dataSource {
|
||||
if (!_dataSource) {
|
||||
_dataSource = [NSMutableArray array];
|
||||
}
|
||||
return _dataSource;
|
||||
}
|
||||
// 无数据源属性
|
||||
|
||||
@end
|
||||
|
36
YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.h
Normal file
36
YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.h
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// EPMomentAPIHelper.h
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-10.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "BaseMvpPresenter.h"
|
||||
#import "MomentsInfoModel.h"
|
||||
#import "MomentsListInfoModel.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 推荐/我的动态列表数据源类型
|
||||
typedef NS_ENUM(NSInteger, EPMomentListSourceType) {
|
||||
EPMomentListSourceTypeRecommend = 0,
|
||||
EPMomentListSourceTypeMine = 1
|
||||
};
|
||||
|
||||
/// 统一封装 Moments 列表 API
|
||||
@interface EPMomentAPIHelper : BaseMvpPresenter
|
||||
|
||||
/// 拉取动态列表(默认 types:"0,2" 图片+文字)
|
||||
/// page 从 0 开始,pageSize > 0
|
||||
/// completion 返回 data 数组(字典数组) 或错误
|
||||
- (void)fetchMomentsWithType:(EPMomentListSourceType)sourceType
|
||||
page:(NSInteger)page
|
||||
pageSize:(NSInteger)pageSize
|
||||
completion:(void (^)(NSArray <MomentsInfoModel *>* _Nullable list, NSInteger code, NSString * _Nullable msg))completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
58
YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.m
Normal file
58
YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.m
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// EPMomentAPIHelper.m
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-10.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "EPMomentAPIHelper.h"
|
||||
#import "Api+Moments.h"
|
||||
#import "AccountInfoStorage.h"
|
||||
#import "BaseModel.h"
|
||||
|
||||
|
||||
@implementation EPMomentAPIHelper
|
||||
|
||||
- (void)fetchMomentsWithType:(EPMomentListSourceType)sourceType
|
||||
page:(NSInteger)page
|
||||
pageSize:(NSInteger)pageSize
|
||||
completion:(void (^)(NSArray <MomentsInfoModel *>* _Nullable list, NSInteger code, NSString * _Nullable msg))completion {
|
||||
// 兼容后端从 1 开始分页:若收到 0 则转成 1
|
||||
NSInteger requestPage = page <= 0 ? 1 : page;
|
||||
NSString *pageStr = [NSString stringWithFormat:@"%ld", (long)requestPage];
|
||||
NSString *pageSizeStr = [NSString stringWithFormat:@"%ld", (long)pageSize];
|
||||
NSString *types = @"0,2"; // 图片+文字
|
||||
|
||||
if (sourceType == EPMomentListSourceTypeRecommend) {
|
||||
// [Api momentsRecommendList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
// if (code == 200 && data.data) {
|
||||
// NSArray *array = [MomentsInfoModel modelsWithArray:data.data];
|
||||
// if (completion) completion(array ?: @[], 200, @"success");
|
||||
// } else {
|
||||
// if (completion) completion(@[], code, msg);
|
||||
// }
|
||||
// } page:pageStr pageSize:pageSizeStr types:types];
|
||||
[Api momentsLatestList:[self createHttpCompletion:^(BaseModel * _Nonnull data) {
|
||||
MomentsListInfoModel *listInfo = [MomentsListInfoModel modelWithDictionary:data.data];
|
||||
if (completion) completion(listInfo.dynamicList ?: @[], 200, @"success");
|
||||
} fail:^(NSInteger code, NSString * _Nullable msg) {
|
||||
if (completion) completion(@[], code, msg);
|
||||
}] dynamicId:@"" pageSize:pageSizeStr types:types];
|
||||
|
||||
} else {
|
||||
// 预留:我的动态列表(暂时复用推荐接口,后续替换为真正的“我的动态”API)
|
||||
[Api momentsRecommendList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
if (code == 200 && data.data) {
|
||||
NSArray *array = [MomentsInfoModel modelsWithArray:data.data];
|
||||
if (completion) completion(array ?: @[], 200, @"success");
|
||||
} else {
|
||||
if (completion) completion(@[], code, msg);
|
||||
}
|
||||
} page:pageStr pageSize:pageSizeStr types:types];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
34
YuMi/E-P/NewMoments/Views/EPMomentListView.h
Normal file
34
YuMi/E-P/NewMoments/Views/EPMomentListView.h
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// EPMomentListView.h
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-10.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "EPMomentAPIHelper.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class MomentsInfoModel;
|
||||
|
||||
/// 承载 Moments 列表与分页刷新的视图
|
||||
@interface EPMomentListView : UIView
|
||||
|
||||
/// 当前数据源(外部可读)
|
||||
@property (nonatomic, strong, readonly) NSArray *rawList;
|
||||
|
||||
/// 列表类型:推荐 / 我的
|
||||
@property (nonatomic, assign) EPMomentListSourceType sourceType;
|
||||
|
||||
/// 外部可设置:当某一项被点击
|
||||
@property (nonatomic, copy) void (^onSelectMoment)(NSInteger index);
|
||||
|
||||
/// 重新加载(刷新到第一页)
|
||||
- (void)reloadFirstPage;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
143
YuMi/E-P/NewMoments/Views/EPMomentListView.m
Normal file
143
YuMi/E-P/NewMoments/Views/EPMomentListView.m
Normal file
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// EPMomentListView.m
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-10.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "EPMomentListView.h"
|
||||
#import "EPMomentCell.h"
|
||||
|
||||
|
||||
@interface EPMomentListView () <UITableViewDelegate, UITableViewDataSource>
|
||||
|
||||
@property (nonatomic, strong) UITableView *tableView;
|
||||
@property (nonatomic, strong) UIRefreshControl *refreshControl;
|
||||
@property (nonatomic, strong) NSMutableArray *mutableRawList;
|
||||
@property (nonatomic, strong) EPMomentAPIHelper *api;
|
||||
@property (nonatomic, assign) NSInteger currentPage;
|
||||
@property (nonatomic, assign) BOOL isLoading;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EPMomentListView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
_api = [[EPMomentAPIHelper alloc] init];
|
||||
_mutableRawList = [NSMutableArray array];
|
||||
_currentPage = 0; // 若后端从1开始,这里会在首次请求时自增到1
|
||||
_sourceType = EPMomentListSourceTypeRecommend;
|
||||
|
||||
[self addSubview:self.tableView];
|
||||
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray<NSMutableDictionary *> *)rawList {
|
||||
return [self.mutableRawList copy];
|
||||
}
|
||||
|
||||
- (void)reloadFirstPage {
|
||||
self.currentPage = 0;
|
||||
[self.mutableRawList removeAllObjects];
|
||||
[self.tableView reloadData];
|
||||
[self requestNextPage];
|
||||
}
|
||||
|
||||
- (void)requestNextPage {
|
||||
if (self.isLoading) return;
|
||||
self.isLoading = YES;
|
||||
|
||||
NSLog(@"[EPMomentListView] 请求页码: %ld", (long)self.currentPage);
|
||||
@kWeakify(self);
|
||||
[self.api fetchMomentsWithType:self.sourceType page:self.currentPage pageSize:20 completion:^(NSArray <MomentsInfoModel *>* _Nullable list, NSInteger code, NSString * _Nullable msg) {
|
||||
@kStrongify(self);
|
||||
self.isLoading = NO;
|
||||
[self.refreshControl endRefreshing];
|
||||
NSLog(@"[EPMomentListView] 返回 code=%ld, count=%lu", (long)code, (unsigned long)list.count);
|
||||
if (code == 200 && list.count > 0) {
|
||||
[self.mutableRawList addObjectsFromArray:list];
|
||||
self.currentPage++;
|
||||
[self.tableView reloadData];
|
||||
} else if (code == 200 && list.count == 0 && self.currentPage == 0) {
|
||||
// 如果第一页就为空,尝试从1开始(兼容某些后端从1计数)
|
||||
self.currentPage = 1;
|
||||
[self requestNextPage];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - UITableView
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.mutableRawList.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
EPMomentCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NewMomentCell" forIndexPath:indexPath];
|
||||
if (indexPath.row < self.mutableRawList.count) {
|
||||
MomentsInfoModel *model = [self.mutableRawList xpSafeObjectAtIndex:indexPath.row];
|
||||
[cell configureWithModel:model];
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return UITableViewAutomaticDimension;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return 200;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
if (self.onSelectMoment) self.onSelectMoment(indexPath.row);
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
CGFloat offsetY = scrollView.contentOffset.y;
|
||||
CGFloat contentHeight = scrollView.contentSize.height;
|
||||
CGFloat screenHeight = scrollView.frame.size.height;
|
||||
if (offsetY > contentHeight - screenHeight - 100 && !self.isLoading) {
|
||||
[self requestNextPage];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Lazy
|
||||
|
||||
- (UITableView *)tableView {
|
||||
if (!_tableView) {
|
||||
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
_tableView.backgroundColor = [UIColor clearColor];
|
||||
_tableView.estimatedRowHeight = 200;
|
||||
_tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
_tableView.showsVerticalScrollIndicator = NO;
|
||||
_tableView.contentInset = UIEdgeInsetsMake(10, 0, 10, 0);
|
||||
[_tableView registerClass:[EPMomentCell class] forCellReuseIdentifier:@"NewMomentCell"];
|
||||
_tableView.refreshControl = self.refreshControl;
|
||||
}
|
||||
return _tableView;
|
||||
}
|
||||
|
||||
- (UIRefreshControl *)refreshControl {
|
||||
if (!_refreshControl) {
|
||||
_refreshControl = [[UIRefreshControl alloc] init];
|
||||
[_refreshControl addTarget:self action:@selector(reloadFirstPage) forControlEvents:UIControlEventValueChanged];
|
||||
}
|
||||
return _refreshControl;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -43,6 +43,9 @@ import SnapKit
|
||||
// 隐藏原生 TabBar
|
||||
self.tabBar.isHidden = true
|
||||
|
||||
// 设置 delegate 以完全控制切换行为
|
||||
self.delegate = self
|
||||
|
||||
setupCustomFloatingTabBar()
|
||||
setupGlobalManagers()
|
||||
setupInitialViewControllers()
|
||||
@@ -111,14 +114,12 @@ import SnapKit
|
||||
selectedImage: "tab_moment_on",
|
||||
tag: 0
|
||||
)
|
||||
momentButton.isSelected = true
|
||||
|
||||
let mineButton = createTabButton(
|
||||
normalImage: "tab_mine_off",
|
||||
selectedImage: "tab_mine_on",
|
||||
tag: 1
|
||||
)
|
||||
mineButton.isSelected = true
|
||||
|
||||
tabButtons = [momentButton, mineButton]
|
||||
|
||||
@@ -144,35 +145,35 @@ import SnapKit
|
||||
private func createTabButton(normalImage: String, selectedImage: String, tag: Int) -> UIButton {
|
||||
let button = UIButton(type: .custom)
|
||||
button.tag = tag
|
||||
|
||||
// 存储图片名称到 button,方便后续切换
|
||||
button.accessibilityLabel = normalImage
|
||||
button.accessibilityHint = selectedImage
|
||||
button.adjustsImageWhenHighlighted = false // 禁用高亮效果,避免闪烁
|
||||
|
||||
// 尝试设置自定义图片,如果不存在则使用 SF Symbols
|
||||
if let normalImg = UIImage(named: normalImage), let _ = UIImage(named: selectedImage) {
|
||||
// 使用自定义图片(初始显示 off 图片)
|
||||
if let normalImg = UIImage(named: normalImage), let selectedImg = UIImage(named: selectedImage) {
|
||||
// 正确设置:分别为 normal 和 selected 状态设置图片
|
||||
button.setImage(normalImg, for: .normal)
|
||||
button.setImage(selectedImg, for: .selected)
|
||||
} else {
|
||||
// 使用 SF Symbols 作为备用
|
||||
let fallbackIcons = ["sparkles", "person.circle"]
|
||||
let iconName = fallbackIcons[tag]
|
||||
let imageConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .medium)
|
||||
let normalIcon = UIImage(systemName: iconName, withConfiguration: imageConfig)
|
||||
|
||||
button.setImage(UIImage(systemName: iconName, withConfiguration: imageConfig), for: .normal)
|
||||
button.setImage(normalIcon, for: .normal)
|
||||
button.setImage(normalIcon, for: .selected)
|
||||
button.tintColor = .white.withAlphaComponent(0.6)
|
||||
}
|
||||
|
||||
// 图片渲染模式
|
||||
button.imageView?.contentMode = .scaleAspectFit
|
||||
|
||||
// 移除标题,只使用图片
|
||||
// 移除标题
|
||||
button.setTitle(nil, for: .normal)
|
||||
button.setTitle(nil, for: .selected)
|
||||
|
||||
// 设置图片大小约束
|
||||
button.imageView?.snp.makeConstraints { make in
|
||||
make.size.equalTo(28) // 图标大小
|
||||
make.size.equalTo(28)
|
||||
}
|
||||
|
||||
button.addTarget(self, action: #selector(tabButtonTapped(_:)), for: .touchUpInside)
|
||||
@@ -191,8 +192,10 @@ import SnapKit
|
||||
// 先更新按钮状态
|
||||
updateTabButtonStates(selectedIndex: newIndex)
|
||||
|
||||
// 再切换 ViewController(使用动画)
|
||||
selectedIndex = newIndex
|
||||
// 禁用 UITabBarController 的默认切换动画,避免闪烁
|
||||
UIView.performWithoutAnimation {
|
||||
selectedIndex = newIndex
|
||||
}
|
||||
|
||||
let tabNames = ["动态", "我的"]
|
||||
NSLog("[EPTabBarController] 选中 Tab: \(tabNames[newIndex])")
|
||||
@@ -205,32 +208,23 @@ import SnapKit
|
||||
|
||||
for (index, button) in tabButtons.enumerated() {
|
||||
let isSelected = (index == selectedIndex)
|
||||
|
||||
// 直接设置 isSelected 属性即可,图片会自动切换
|
||||
button.isSelected = isSelected
|
||||
|
||||
// 更新图片状态
|
||||
if let normalImageName = button.accessibilityLabel,
|
||||
let selectedImageName = button.accessibilityHint {
|
||||
// 使用自定义图片
|
||||
let imageName = isSelected ? selectedImageName : normalImageName
|
||||
if let image = UIImage(named: imageName) {
|
||||
button.setImage(image, for: .normal)
|
||||
} else {
|
||||
// 如果自定义图片不存在,使用 SF Symbols
|
||||
button.tintColor = isSelected ? .white : .white.withAlphaComponent(0.6)
|
||||
}
|
||||
} else {
|
||||
// 使用 SF Symbols(备用方案)
|
||||
// SF Symbols 的情况需要手动更新 tintColor
|
||||
if button.currentImage?.isSymbolImage == true {
|
||||
button.tintColor = isSelected ? .white : .white.withAlphaComponent(0.6)
|
||||
}
|
||||
|
||||
// 选中状态动画
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseInOut], animations: {
|
||||
// 选中状态缩放动画
|
||||
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut], animations: {
|
||||
button.transform = isSelected ? CGAffineTransform(scaleX: 1.1, y: 1.1) : .identity
|
||||
}, completion: nil)
|
||||
})
|
||||
}
|
||||
|
||||
// 延迟恢复按钮交互,避免动画期间的重复点击
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
// 延迟恢复按钮交互
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
||||
self.tabButtons.forEach { $0.isUserInteractionEnabled = true }
|
||||
}
|
||||
}
|
||||
@@ -317,22 +311,34 @@ import SnapKit
|
||||
!(viewControllers?[0] is EPMomentViewController) ||
|
||||
!(viewControllers?[1] is EPMineViewController) {
|
||||
|
||||
// 创建真实的 ViewController(OC 类)
|
||||
// 创建真实的 ViewController(OC 类),并使用导航控制器包裹以显示标题/右上按钮
|
||||
let momentVC = EPMomentViewController()
|
||||
momentVC.tabBarItem = createTabBarItem(
|
||||
momentVC.title = "动态"
|
||||
let momentNav = UINavigationController(rootViewController: momentVC)
|
||||
momentNav.navigationBar.isTranslucent = true
|
||||
momentNav.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
||||
momentNav.navigationBar.shadowImage = UIImage()
|
||||
momentNav.view.backgroundColor = .clear
|
||||
momentNav.tabBarItem = createTabBarItem(
|
||||
title: "动态",
|
||||
normalImage: "tab_moment_normal",
|
||||
selectedImage: "tab_moment_selected"
|
||||
)
|
||||
|
||||
|
||||
let mineVC = EPMineViewController()
|
||||
mineVC.tabBarItem = createTabBarItem(
|
||||
mineVC.title = "我的"
|
||||
let mineNav = UINavigationController(rootViewController: mineVC)
|
||||
mineNav.navigationBar.isTranslucent = true
|
||||
mineNav.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
||||
mineNav.navigationBar.shadowImage = UIImage()
|
||||
mineNav.view.backgroundColor = .clear
|
||||
mineNav.tabBarItem = createTabBarItem(
|
||||
title: "我的",
|
||||
normalImage: "tab_mine_normal",
|
||||
selectedImage: "tab_mine_selected"
|
||||
)
|
||||
|
||||
viewControllers = [momentVC, mineVC]
|
||||
viewControllers = [momentNav, mineNav]
|
||||
NSLog("[EPTabBarController] 登录后 ViewControllers 创建完成 - Moment & Mine")
|
||||
}
|
||||
|
||||
@@ -347,6 +353,21 @@ extension EPTabBarController: UITabBarControllerDelegate {
|
||||
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
|
||||
NSLog("[EPTabBarController] 选中 Tab: \(item.title ?? "Unknown")")
|
||||
}
|
||||
|
||||
/// 禁用系统默认的切换动画
|
||||
func tabBarController(_ tabBarController: UITabBarController,
|
||||
animationControllerForTransitionFrom fromVC: UIViewController,
|
||||
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
// 返回 nil 表示不使用动画
|
||||
return nil
|
||||
}
|
||||
|
||||
/// 完全控制是否允许切换
|
||||
func tabBarController(_ tabBarController: UITabBarController,
|
||||
shouldSelect viewController: UIViewController) -> Bool {
|
||||
// 允许切换,但通过返回 nil 的 animationController 来禁用动画
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - OC Compatibility
|
||||
|
Reference in New Issue
Block a user