Files
peko-ios/YuMi/Modules/YMMine/View/Medals/MedalsLevelIndicatorView.m

431 lines
14 KiB
Mathematica
Raw Normal View History

//
// MedalsLevelIndicatorView.m
// YuMi
//
// Created by P on 2025/6/18.
//
#import "MedalsLevelIndicatorView.h"
#import "UIImage+Utils.h"
//
@interface LevelItemView : UIView
@property (nonatomic, strong) UILabel *levelLabel;
@property (nonatomic, strong) UIView *dotView; //
@property (nonatomic, strong) NetImageView *imageView; //
@property (nonatomic, assign) BOOL isSelected;
@property (nonatomic, assign) NSInteger level;
@property (nonatomic, assign) BOOL hasImage;
@property (nonatomic, strong) UIImage *originalImage; //
@property (nonatomic, strong) UIImage *grayImage; //
@property (nonatomic, copy) NSString *cachedImageUrl; // URL
- (instancetype)initWithLevel:(NSInteger)level;
- (void)setSelected:(BOOL)selected animated:(BOOL)animated;
- (void)setImageUrl:(NSString *)imageUrl;
- (void)createImageViewIfNeeded;
@end
@implementation LevelItemView
- (instancetype)initWithLevel:(NSInteger)level {
self = [super init];
if (self) {
_level = level;
_isSelected = NO;
_hasImage = NO;
//
_dotView = [[UIView alloc] init];
_dotView.backgroundColor = UIColorFromRGB(0x8B54E8);
_dotView.layer.cornerRadius = 3; //
_dotView.clipsToBounds = YES;
[self addSubview:_dotView];
//
self.levelLabel = [UILabel labelInitWithText:[NSString stringWithFormat:@"LV%ld", (long)level]
font:kFontMedium(10)
textColor:UIColorFromRGB(0x8B54E8)];
self.levelLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.levelLabel];
//
[_dotView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self);
make.top.mas_equalTo(self);
make.width.height.mas_equalTo(6); //
}];
[self.levelLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self);
make.top.mas_equalTo(_dotView.mas_bottom).offset(4); //
make.leading.trailing.bottom.mas_equalTo(self);
}];
}
return self;
}
- (void)createImageViewIfNeeded {
if (_imageView) {
return;
}
//
_imageView = [[NetImageView alloc] init];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.clipsToBounds = YES;
[self addSubview:_imageView];
[_imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self);
make.bottom.mas_equalTo(_dotView.mas_top).offset(-4);
make.width.height.mas_equalTo(40);
}];
//
[_dotView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self);
make.top.mas_equalTo(_imageView.mas_bottom).offset(4);
make.width.height.mas_equalTo(6);
}];
// URL
if (_cachedImageUrl) {
[self loadImageWithUrl:_cachedImageUrl];
_cachedImageUrl = nil;
}
}
- (void)setImageUrl:(NSString *)imageUrl {
_hasImage = YES;
_cachedImageUrl = imageUrl;
// imageView
if (_imageView) {
[self loadImageWithUrl:imageUrl];
}
}
- (void)loadImageWithUrl:(NSString *)imageUrl {
// 使URL
#ifdef DEBUG
NSString *testUrl = @"https://img.toto.im/mw600/66b3de17ly1i2jopju47bj20xc1e0dx4.jpg.webp";
imageUrl = testUrl;
NSLog(@"调试模式: 使用测试图片URL: %@", testUrl);
#endif
//
@kWeakify(self);
[_imageView loadImageWithUrl:imageUrl completion:^(UIImage * _Nullable image, NSURL * _Nonnull url) {
@kStrongify(self);
if (image) {
UIImage *cutImage = [image cutImage:CGSizeMake(40, 40)];
self.originalImage = cutImage;
self.grayImage = [cutImage grayscaleImage];
[self updateImageEffect];
}
}];
}
//
- (void)updateImageEffect {
if (!_hasImage || !_imageView) {
return;
}
if (_isSelected && _originalImage) {
//
self.imageView.image = _originalImage;
} else if (!_isSelected && _grayImage) {
//
self.imageView.image = _grayImage;
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
if (_isSelected == selected) {
return;
}
_isSelected = selected;
void (^updateBlock)(void) = ^{
UIColor *color = selected ? [UIColor whiteColor] : UIColorFromRGB(0x8B54E8);
self.levelLabel.textColor = color;
self.dotView.backgroundColor = color;
//
if (self.hasImage) {
[self updateImageEffect];
}
};
if (animated) {
[UIView animateWithDuration:0.2 animations:updateBlock];
} else {
updateBlock();
}
}
@end
//
@interface MedalsLevelIndicatorView()
@property (nonatomic, strong) NSMutableArray<LevelItemView *> *levelItems;
@property (nonatomic, strong) NSMutableArray<UIView *> *connectionLines; // 线
@property (nonatomic, assign) NSInteger maxLevel;
@property (nonatomic, assign) NSInteger selectedLevel;
@end
@implementation MedalsLevelIndicatorView
- (instancetype)init {
self = [super init];
if (self) {
_levelItems = [NSMutableArray array];
_connectionLines = [NSMutableArray array];
_selectedLevel = 1;
_indicatorType = MedalsLevelIndicatorTypeNormal;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
[self addGestureRecognizer:tapGesture];
}
return self;
}
- (void)setupWithMaxLevel:(NSInteger)maxLevel {
if (maxLevel < 0) {
maxLevel = 1;
}
if (_maxLevel == maxLevel && _levelItems.count == maxLevel) {
return;
}
_maxLevel = maxLevel;
// 线
for (UIView *view in _levelItems) {
[view removeFromSuperview];
}
[_levelItems removeAllObjects];
for (UIView *line in _connectionLines) {
[line removeFromSuperview];
}
[_connectionLines removeAllObjects];
//
CGFloat itemWidth = 25.0; //
// 20
CGFloat availableWidth = self.bounds.size.width;// - 40.0; // 20
//
CGFloat spacing = 0;
if (maxLevel > 1) {
spacing = (availableWidth - itemWidth * maxLevel) / (maxLevel - 1);
}
//
spacing = MAX(spacing, 10.0);
// 20
CGFloat startX = 20.0;
for (NSInteger i = 1; i <= maxLevel; i++) {
//
LevelItemView *levelItem = [[LevelItemView alloc] initWithLevel:i];
[self addSubview:levelItem];
[_levelItems addObject:levelItem];
//
CGFloat x = startX + (i - 1) * (itemWidth + spacing);
[levelItem mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(itemWidth);
make.height.mas_equalTo(self);
make.centerY.mas_equalTo(self);
make.leading.mas_equalTo(self).offset(x);
}];
// 线
if (i > 1) {
UIView *line = [[UIView alloc] init];
line.backgroundColor = UIColorFromRGB(0x8B54E8); //
[self insertSubview:line atIndex:0];
[_connectionLines addObject:line];
// 线
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(1); // 线
make.centerY.mas_equalTo(levelItem.dotView);
make.leading.mas_equalTo(_levelItems[i-2].dotView.mas_centerX);
make.trailing.mas_equalTo(levelItem.dotView.mas_centerX);
}];
}
}
// LV1
[self setSelectedLevel:1 animated:NO];
//
[self updateImageVisibility];
}
- (void)layoutSubviews {
[super layoutSubviews];
if (_maxLevel > 0 && _levelItems.count > 0) {
// 20
CGFloat itemWidth = 25.0;
CGFloat availableWidth = self.bounds.size.width;// - 40.0; // 20
//
CGFloat spacing = 0;
if (_maxLevel > 1) {
spacing = (availableWidth - itemWidth * _maxLevel) / (_maxLevel - 1);
}
//
spacing = MAX(spacing, 10.0);
// 20
CGFloat startX = 20.0;
for (NSInteger i = 0; i < _levelItems.count; i++) {
LevelItemView *item = _levelItems[i];
CGFloat x = startX + i * (itemWidth + spacing);
[item mas_updateConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self).offset(x);
}];
}
// 线
for (NSInteger i = 0; i < _connectionLines.count && i + 1 < _levelItems.count; i++) {
UIView *line = _connectionLines[i];
[line mas_updateConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(_levelItems[i].dotView.mas_centerX);
make.trailing.mas_equalTo(_levelItems[i+1].dotView.mas_centerX);
}];
}
}
}
- (void)setSelectedLevel:(NSInteger)level animated:(BOOL)animated {
if (level < 1 || level > _maxLevel) {
return;
}
_selectedLevel = level;
//
for (NSInteger i = 0; i < _levelItems.count; i++) {
LevelItemView *item = _levelItems[i];
[item setSelected:(i + 1) <= level animated:animated];
}
// 线
for (NSInteger i = 0; i < _connectionLines.count; i++) {
UIView *line = _connectionLines[i];
// 线
// 线线
BOOL isSelected = (i + 2) <= level;
void (^updateBlock)(void) = ^{
line.backgroundColor = isSelected ? [UIColor whiteColor] : UIColorFromRGB(0x8B54E8);
};
if (animated) {
[UIView animateWithDuration:0.2 animations:updateBlock];
} else {
updateBlock();
}
}
}
- (void)setIndicatorType:(MedalsLevelIndicatorType)indicatorType {
_indicatorType = indicatorType;
//
if (_levelItems.count > 0) {
// imageView
if (indicatorType == MedalsLevelIndicatorTypeWithImage) {
for (LevelItemView *item in _levelItems) {
[item createImageViewIfNeeded];
}
}
[self updateImageVisibility];
}
}
//
- (void)updateImageVisibility {
for (LevelItemView *item in _levelItems) {
if (item.imageView) {
item.imageView.hidden = (_indicatorType == MedalsLevelIndicatorTypeNormal);
}
}
}
- (void)setImageUrl:(NSString *)imageUrl forLevel:(NSInteger)level {
if (level < 1 || level > _maxLevel || level > _levelItems.count) {
return;
}
LevelItemView *item = _levelItems[level - 1];
//
if (_indicatorType == MedalsLevelIndicatorTypeWithImage) {
[item createImageViewIfNeeded]; // imageView
[item setImageUrl:imageUrl];
} else {
// URL imageView
[item setImageUrl:imageUrl];
}
}
- (void)handleTap:(UITapGestureRecognizer *)gesture {
CGPoint location = [gesture locationInView:self];
for (NSInteger i = 0; i < _levelItems.count; i++) {
LevelItemView *item = _levelItems[i];
CGPoint itemLocation = [self convertPoint:location toView:item];
// item
if (CGRectContainsPoint(item.bounds, itemLocation)) {
NSInteger level = i + 1;
[self setSelectedLevel:level animated:YES];
if (_levelSelectedBlock) {
_levelSelectedBlock(level);
}
break;
}
}
}
//
- (void)calculateLayoutParams:(CGFloat *)itemWidth spacing:(CGFloat *)spacing startX:(CGFloat *)startX forMaxLevel:(NSInteger)maxLevel {
*itemWidth = 25.0; //
//
CGFloat availableWidth = self.bounds.size.width;
//
*spacing = 0;
if (maxLevel > 1) {
*spacing = (availableWidth - (*itemWidth) * maxLevel) / (maxLevel - 1);
}
//
*spacing = MAX(*spacing, 10.0);
// 20
*startX = 20.0;
}
@end