// // MedalsRankViewController.m // YuMi // // Created by P on 2025/6/20. // #import "MedalsRankViewController.h" #import "MedalsPresenter.h" #import "MedalsViewController.h" #import "UserInfoModel.h" #import #import "DJDKMIMOMColor.h" @interface MedalsRankListCell : UITableViewCell @property (nonatomic, strong) UILabel *indexLabel; @property (nonatomic, strong) NetImageView *avatarImageView; @property (nonatomic, strong) UILabel *nameLabel; @property (nonatomic, strong) UILabel *countLabel; + (void)registerTo:(UITableView *)tableView; + (instancetype)cellFor:(UITableView *)tableView atIndexPath:(NSIndexPath *)index; - (void)updateCell:(MedalsRankUserModel *)userModel atIndex:(NSInteger)index; @end @implementation MedalsRankListCell + (NSString *)cellID { return NSStringFromClass([MedalsRankListCell class]); } + (void)registerTo:(UITableView *)tableView { [tableView registerClass:[self class] forCellReuseIdentifier:[self cellID]]; } + (instancetype)cellFor:(UITableView *)tableView atIndexPath:(NSIndexPath *)index { MedalsRankListCell *cell = [tableView dequeueReusableCellWithIdentifier:[self cellID] forIndexPath:index]; return cell; } - (void)updateCell:(MedalsRankUserModel *)userModel atIndex:(NSInteger)index { self.avatarImageView.imageUrl = userModel.avatar; self.nameLabel.text = userModel.nick; self.indexLabel.text = @(index + 4).stringValue; self.countLabel.text = @(userModel.medalCount).stringValue; } - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { self.backgroundColor = [UIColor clearColor]; self.contentView.backgroundColor = [UIColor clearColor]; self.selectionStyle = UITableViewCellSelectionStyleNone; self.indexLabel = [UILabel labelInitWithText:@"" font:kFontMedium(14) textColor:[UIColor whiteColor]]; [self.contentView addSubview:self.indexLabel]; [self.indexLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(self.contentView); make.leading.mas_equalTo(18); }]; NetImageConfig *config = [[NetImageConfig alloc] init]; config.placeHolder = [UIImageConstant defaultAvatarPlaceholder]; self.avatarImageView = [[NetImageView alloc] initWithConfig:config]; [self.avatarImageView setCornerRadius:47/2]; [self.contentView addSubview:self.avatarImageView]; [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(self.contentView); make.leading.mas_equalTo(self.indexLabel.mas_trailing).offset(16); make.size.mas_equalTo(CGSizeMake(47, 47)); }]; self.nameLabel = [UILabel labelInitWithText:@"" font:kFontSemibold(15) textColor:[UIColor whiteColor]]; [self.contentView addSubview:self.nameLabel]; [self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.avatarImageView.mas_trailing).offset(6); make.top.mas_equalTo(self.avatarImageView); }]; UIImageView *rankIcon = [[UIImageView alloc] initWithImage:kImage(@"medals_rank")]; [self.contentView addSubview:rankIcon]; [rankIcon mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.avatarImageView.mas_trailing).offset(6); make.bottom.mas_equalTo(self.avatarImageView); make.size.mas_equalTo(CGSizeMake(10, 14)); }]; self.countLabel = [UILabel labelInitWithText:@"" font:kFontMedium(14) textColor:[UIColor whiteColor]]; [self.contentView addSubview:self.countLabel]; [self.countLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(rankIcon.mas_trailing).offset(6); make.centerY.mas_equalTo(rankIcon); }]; UILabel *detailLabel = [UILabel labelInitWithText:YMLocalizedString(@"20.20.61_text_13") font:kFontRegular(12) textColor:[UIColor colorWithWhite:1 alpha:0.6]]; [self.contentView addSubview:detailLabel]; [detailLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.trailing.mas_equalTo(self.contentView).offset(-28); make.centerY.mas_equalTo(self.contentView); }]; UIImageView *arrow = [[UIImageView alloc] initWithImage:kImage(@"grey_right_arrow")]; [self.contentView addSubview:arrow]; [arrow mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(self.contentView); make.trailing.mas_equalTo(self.contentView).offset(-15); make.size.mas_equalTo(CGSizeMake(15, 15)); }]; } return self; } @end @interface MedalsRankTopView : UIView @property (nonatomic, assign) NSInteger topLevel; @property (nonatomic, strong) MedalsRankUserModel *userModel; @property (nonatomic, copy) void (^onTapBlock)(MedalsRankUserModel *userModel); @end @implementation MedalsRankTopView { NetImageView *avatarImageView; UIImageView *decorationImageView; UILabel *nameLabel; UILabel *honorLabel; UIImageView *honorImageView; UIImageView *medalIconImageView; UILabel *medalCountLabel; UIStackView *medalCountStack; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { NetImageConfig *config = [[NetImageConfig alloc] init]; config.placeHolder = [UIImageConstant defaultAvatarPlaceholder]; avatarImageView = [[NetImageView alloc] initWithConfig:config]; [avatarImageView setCornerRadius:50]; [self addSubview:avatarImageView]; [avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.center.mas_equalTo(self); make.size.mas_equalTo(CGSizeMake(100, 100)); }]; decorationImageView = [[UIImageView alloc] init]; [self addSubview:decorationImageView]; [decorationImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(self); }]; // honorImageView = [[UIImageView alloc] initWithImage:kImage(@"medals_rank_namebar")]; // [self addSubview:honorImageView]; // [honorImageView mas_makeConstraints:^(MASConstraintMaker *make) { // make.centerX.mas_equalTo(self); // make.bottom.mas_equalTo(self).offset(6); // }]; // // honorLabel = [UILabel labelInitWithText:YMLocalizedString(@"20.20.61_text_12") // font:kFontSemibold(12) // textColor:UIColorFromRGB(0xfff27b)]; // [self addSubview:honorLabel]; // [honorLabel mas_makeConstraints:^(MASConstraintMaker *make) { // make.center.mas_equalTo(honorImageView); // }]; // 1. 先加 label,因為我們要讓 imageView 跟著 label 的大小走 honorLabel = [UILabel labelInitWithText:YMLocalizedString(@"20.20.61_text_12") font:kFontSemibold(12) textColor:UIColorFromRGB(0xfff27b)]; honorLabel.textAlignment = NSTextAlignmentCenter; [self addSubview:honorLabel]; // 2. 再加 imageView,但先不要給它固定圖片尺寸 honorImageView = [[UIImageView alloc] initWithImage:kImage(@"medals_rank_namebar")]; honorImageView.contentMode = UIViewContentModeScaleToFill; // 讓圖片自動拉伸 [self insertSubview:honorImageView belowSubview:honorLabel]; // 圖片在 label 下面 // 3. 讓 imageView 的邊緣跟 label 保持固定間距(例如上下左右各 8pt) [honorLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self); make.bottom.mas_equalTo(self).offset(6); // 與父 view 的相對位置 }]; [honorImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(honorLabel); // 中心對齊 label make.left.equalTo(honorLabel.mas_left).offset(-8); make.right.equalTo(honorLabel.mas_right).offset(8); make.top.equalTo(honorLabel.mas_top).offset(-4); make.bottom.equalTo(honorLabel.mas_bottom).offset(4); }]; nameLabel = [UILabel labelInitWithText:@"" font:kFontSemibold(15) textColor:UIColorFromRGB(0xffffff)]; [self addSubview:nameLabel]; medalIconImageView = [[UIImageView alloc] initWithImage:kImage(@"medals_rank")]; medalCountLabel = [UILabel labelInitWithText:@"0" font:kFontMedium(14) textColor:UIColorFromRGB(0xffffff)]; medalCountStack = [[UIStackView alloc] initWithArrangedSubviews:@[ medalIconImageView, medalCountLabel ]]; [self addSubview:medalCountStack]; honorLabel.hidden = YES; honorImageView.hidden = YES; // 添加点击手势 UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapTopView)]; [self addGestureRecognizer:tapGesture]; self.userInteractionEnabled = YES; } return self; } - (void)didTapTopView { if (self.onTapBlock && self.userModel) { self.onTapBlock(self.userModel); } } - (void)setTopLevel:(NSInteger)topLevel { _topLevel = topLevel; NSString *imagePath = @""; NSInteger nameTopOffset = 0; NSInteger bottomOffset = 0; switch (topLevel) { case 1: imagePath = @"medals_rank_top_1"; honorLabel.hidden = NO; honorImageView.hidden = NO; nameTopOffset = 8; bottomOffset = 40; break; case 2: imagePath = @"medals_rank_top_2"; nameTopOffset = -4; bottomOffset = 20; break; case 3: imagePath = @"medals_rank_top_3"; nameTopOffset = -4; bottomOffset = 20; break; default: break; } [nameLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self); make.top.mas_equalTo(self.mas_bottom).offset(nameTopOffset); }]; [medalCountStack mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self); make.top.mas_equalTo(nameLabel.mas_bottom).offset(4); }]; if (![NSString isEmpty:imagePath]) { decorationImageView.image = kImage(imagePath); } } - (void)setUserModel:(MedalsRankUserModel *)userModel { _userModel = userModel; if (userModel) { avatarImageView.imageUrl = userModel.avatar; nameLabel.text = userModel.nick; medalCountLabel.text = @(userModel.medalCount).stringValue; } } @end @interface MedalsRankViewController () @property (nonatomic, strong) UILabel *indexLabel; @property (nonatomic, strong) NetImageView *avatarImageView; @property (nonatomic, strong) UILabel *nameLabel; @property (nonatomic, strong) UILabel *countLabel; @property (nonatomic, strong) MedalsRankTopView *topOne; @property (nonatomic, strong) MedalsRankTopView *topTwo; @property (nonatomic, strong) MedalsRankTopView *topThree; @property (nonatomic, strong) MedalsRankUserModel *mineRankModel; @property (nonatomic, strong) NSMutableArray *rankList; @property (nonatomic, strong) UITableView *rankTableView; // 分页相关属性 @property (nonatomic, assign) NSInteger currentPage; @end @implementation MedalsRankViewController - (BOOL)isHiddenNavBar { return YES; } - (MedalsPresenter *)createPresenter { return [[MedalsPresenter alloc] init]; } - (void)viewDidLoad { [super viewDidLoad]; // 初始化分页 self.currentPage = 1; [self setupUI]; [self setupRefresh]; [self loadData]; } #pragma mark - - (void)setupUI { self.view.backgroundColor = UIColorFromRGB(0x1B0043); [self setupTopArea]; [self setupRankList]; [self setupBottomArea]; [self setupNavigationBar]; } - (void)setupNavigationBar { [self setupCustomNavigationBar:^{} title:YMLocalizedString(@"20.20.61_text_11") titleColor:[UIColor whiteColor]]; } - (void)setupTopArea { UIImageView *topBG = [[UIImageView alloc] initWithImage:kImage(@"medals_rank_top_bg")]; topBG.frame = CGRectMake(0, 0, KScreenWidth, kGetScaleWidth(270)); [self.view addSubview:topBG]; self.topOne = [self rankTopView:1]; self.topTwo = [self rankTopView:2]; self.topThree = [self rankTopView:3]; // 设置点击回调 __weak typeof(self) weakSelf = self; self.topOne.onTapBlock = ^(MedalsRankUserModel *userModel) { [weakSelf handleTopViewTap:userModel]; }; self.topTwo.onTapBlock = ^(MedalsRankUserModel *userModel) { [weakSelf handleTopViewTap:userModel]; }; self.topThree.onTapBlock = ^(MedalsRankUserModel *userModel) { [weakSelf handleTopViewTap:userModel]; }; [self.view addSubview:self.topOne]; [self.view addSubview:self.topTwo]; [self.view addSubview:self.topThree]; [self.topOne mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(self.view).offset(95); make.centerX.mas_equalTo(self.view); make.size.mas_equalTo(CGSizeMake(209, 145)); }]; [self.topTwo mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(topBG.mas_bottom); make.leading.mas_equalTo(self.view).offset(25); make.size.mas_equalTo(CGSizeMake(145, 145)); }]; [self.topThree mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(topBG.mas_bottom); make.trailing.mas_equalTo(self.view).offset(-25); make.size.mas_equalTo(CGSizeMake(145, 145)); }]; } - (void)setupRankList { [self.view addSubview:self.rankTableView]; CGFloat height = 84; [self.rankTableView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(self.topTwo.mas_bottom).offset(40); make.bottom.mas_equalTo(self.view).offset(-height); make.leading.trailing.mas_equalTo(self.view); }]; } - (void)setupBottomArea { CGFloat height = 84; UIView *bottomArea = [[UIView alloc] initWithFrame:CGRectMake(0, KScreenHeight - height, KScreenWidth, height)]; [bottomArea addGradientBackgroundWithColors:@[ UIColorFromRGB(0x3d1479), UIColorFromRGB(0x1c0044), ] startPoint:CGPointMake(0.5, 0) endPoint:CGPointMake(0.5, 1) cornerRadius:0]; [self.view addSubview:bottomArea]; self.indexLabel = [UILabel labelInitWithText:@"" font:kFontMedium(14) textColor:[UIColor whiteColor]]; [bottomArea addSubview:self.indexLabel]; [self.indexLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(bottomArea); make.leading.mas_equalTo(18); }]; NetImageConfig *config = [[NetImageConfig alloc] init]; config.placeHolder = [UIImageConstant defaultAvatarPlaceholder]; self.avatarImageView = [[NetImageView alloc] initWithConfig:config]; [self.avatarImageView setCornerRadius:47/2]; [bottomArea addSubview:self.avatarImageView]; [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(bottomArea); make.leading.mas_equalTo(self.indexLabel.mas_trailing).offset(16); make.size.mas_equalTo(CGSizeMake(47, 47)); }]; self.nameLabel = [UILabel labelInitWithText:@"" font:kFontSemibold(15) textColor:[UIColor whiteColor]]; [bottomArea addSubview:self.nameLabel]; [self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.avatarImageView.mas_trailing).offset(6); make.top.mas_equalTo(self.avatarImageView); }]; UIImageView *rankIcon = [[UIImageView alloc] initWithImage:kImage(@"medals_rank")]; [bottomArea addSubview:rankIcon]; [rankIcon mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.avatarImageView.mas_trailing).offset(6); make.bottom.mas_equalTo(self.avatarImageView); make.size.mas_equalTo(CGSizeMake(10, 14)); }]; self.countLabel = [UILabel labelInitWithText:@"" font:kFontMedium(14) textColor:[UIColor whiteColor]]; [bottomArea addSubview:self.countLabel]; [self.countLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(rankIcon.mas_trailing).offset(6); make.centerY.mas_equalTo(rankIcon); }]; } - (void)setupRefresh { // 设置下拉刷新 MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(headerRefresh)]; header.stateLabel.font = [UIFont systemFontOfSize:10.0]; header.lastUpdatedTimeLabel.font = [UIFont systemFontOfSize:10.0]; header.stateLabel.textColor = [DJDKMIMOMColor secondTextColor]; header.lastUpdatedTimeLabel.textColor = [DJDKMIMOMColor secondTextColor]; self.rankTableView.mj_header = header; // // 设置上拉加载更多 // MJRefreshBackNormalFooter *footer = [MJRefreshBackNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(footerRefresh)]; // footer.stateLabel.textColor = [DJDKMIMOMColor secondTextColor]; // footer.stateLabel.font = [UIFont systemFontOfSize:10.0]; // self.rankTableView.mj_footer = footer; } #pragma mark - - (void)loadData { [self.presenter rankList:self.currentPage]; } - (void)rankListSuccess:(MedalsRankModel *)model { // 结束刷新状态 [self.rankTableView.mj_header endRefreshing]; [self.rankTableView.mj_footer endRefreshing]; NSInteger currentRank = 4; // 从第4名开始计算(前三名已经处理) if (model) { // 第一页时更新个人信息和前三名 if (self.currentPage == 1) { self.mineRankModel = model.mine; self.avatarImageView.imageUrl = self.mineRankModel.avatar; self.nameLabel.text = self.mineRankModel.nick; self.indexLabel.text = self.mineRankModel.rank == 0 ? @"10+" : @(self.mineRankModel.rank).stringValue; self.countLabel.text = @(self.mineRankModel.medalCount).stringValue; self.rankList = [NSMutableArray array]; // 处理前三名显示 for (MedalsRankUserModel *user in model.rankList) { if (user.rank == 1) { self.topOne.userModel = user; } else if (user.rank == 2) { self.topTwo.userModel = user; } else if (user.rank == 3) { self.topThree.userModel = user; } else { [self.rankList addObject:user]; currentRank = MAX(currentRank, user.rank + 1); } } } else { // 非第一页,追加数据(排除前三名) for (MedalsRankUserModel *user in model.rankList) { if (user.rank > 3) { [self.rankList addObject:user]; } } } // 检查是否还有更多数据 if (model.rankList.count < [self.presenter rankListSize]) { if (self.currentPage == 1) { // 补充数据至 10 个(rankList 中应有 7 个,因为前三名在 top 视图中) NSInteger targetCount = 7; // 总共10个,减去前3名 while (self.rankList.count < targetCount) { MedalsRankUserModel *emptyUser = [[MedalsRankUserModel alloc] init]; emptyUser.rank = currentRank; emptyUser.uid = 0; emptyUser.avatar = @""; emptyUser.nick = @"--"; emptyUser.medalCount = 0; [self.rankList addObject:emptyUser]; currentRank++; } } [self.rankTableView.mj_footer endRefreshingWithNoMoreData]; } else { [self.rankTableView.mj_footer resetNoMoreData]; } [self.rankTableView reloadData]; } } - (void)rankListFailure { // 结束刷新状态 [self.rankTableView.mj_header endRefreshing]; [self.rankTableView.mj_footer endRefreshing]; // 如果是加载更多失败,页码需要回退 if (self.currentPage > 1) { self.currentPage--; } } #pragma mark - UITableView DataSource & Delegate - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.rankList.count; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 70; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { MedalsRankListCell *cell = [MedalsRankListCell cellFor:tableView atIndexPath:indexPath]; MedalsRankUserModel *model = [self.rankList xpSafeObjectAtIndex:indexPath.row]; [cell updateCell:model atIndex:indexPath.row]; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // 取消选中高亮效果 [tableView deselectRowAtIndexPath:indexPath animated:YES]; MedalsRankUserModel *model = [self.rankList xpSafeObjectAtIndex:indexPath.row]; [self handleTopViewTap:model]; } - (void)handleTopViewTap:(MedalsRankUserModel *)model { if (model) { UserInfoModel *userInfo = [[UserInfoModel alloc] init]; userInfo.uid = model.uid; userInfo.avatar = model.avatar; userInfo.nick = model.nick; MedalsViewController *vc = [[MedalsViewController alloc] initForOtherMedals:userInfo]; [self.navigationController pushViewController:vc animated:YES]; } } #pragma mark - - (MedalsRankTopView *)rankTopView:(NSInteger)rank { MedalsRankTopView *topView = [[MedalsRankTopView alloc] initWithFrame:CGRectMake(0, 0, rank == 1 ? 209 : 145, 145)]; topView.topLevel = rank; return topView; } - (UITableView *)rankTableView { if (!_rankTableView) { _rankTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; _rankTableView.delegate = self; _rankTableView.dataSource = self; _rankTableView.backgroundColor = [UIColor clearColor]; [MedalsRankListCell registerTo:_rankTableView]; } return _rankTableView; } #pragma mark - 刷新方法 - (void)headerRefresh { self.currentPage = 1; [self loadData]; } - (void)footerRefresh { self.currentPage++; [self loadData]; } @end