diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 7cfc57a..f2b0638 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -62,7 +62,12 @@ 048908DA2EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908D82EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m */; }; 048908DD2EBF67EB00FABA60 /* KBSearchResultVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908DC2EBF67EB00FABA60 /* KBSearchResultVC.m */; }; 048908E02EBF73DC00FABA60 /* MySkinVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908DF2EBF73DC00FABA60 /* MySkinVC.m */; }; - 048908E32EBF760000FABA60 /* MySkinCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF760000FABA60 /* MySkinCell.m */; }; + 048908E32EBF760000FABA60 /* MySkinCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF760000FABA60 /* MySkinCell.m */; }; + 048908E32EBF821700FABA60 /* KBSkinDetailVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF821700FABA60 /* KBSkinDetailVC.m */; }; + 048908E62EBF841B00FABA60 /* KBSkinDetailTagCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E52EBF841B00FABA60 /* KBSkinDetailTagCell.m */; }; + 048908E92EBF843000FABA60 /* KBSkinDetailHeaderCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E82EBF843000FABA60 /* KBSkinDetailHeaderCell.m */; }; + 048908EC2EBF849300FABA60 /* KBSkinTagsContainerCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908EB2EBF849300FABA60 /* KBSkinTagsContainerCell.m */; }; + 048908EF2EBF861800FABA60 /* KBSkinSectionTitleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908EE2EBF861800FABA60 /* KBSkinSectionTitleCell.m */; }; 04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97082EB31B14007BD342 /* KBHUD.m */; }; 04A9FE132EB4D0D20020DB6D /* KBFullAccessManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */; }; 04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE152EB873C80020DB6D /* UIViewController+Extension.m */; }; @@ -225,8 +230,18 @@ 048908DC2EBF67EB00FABA60 /* KBSearchResultVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchResultVC.m; sourceTree = ""; }; 048908DE2EBF73DC00FABA60 /* MySkinVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinVC.h; sourceTree = ""; }; 048908DF2EBF73DC00FABA60 /* MySkinVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinVC.m; sourceTree = ""; }; - 048908E12EBF760000FABA60 /* MySkinCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinCell.h; sourceTree = ""; }; - 048908E22EBF760000FABA60 /* MySkinCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinCell.m; sourceTree = ""; }; + 048908E12EBF760000FABA60 /* MySkinCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinCell.h; sourceTree = ""; }; + 048908E12EBF821700FABA60 /* KBSkinDetailVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinDetailVC.h; sourceTree = ""; }; + 048908E22EBF760000FABA60 /* MySkinCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinCell.m; sourceTree = ""; }; + 048908E22EBF821700FABA60 /* KBSkinDetailVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinDetailVC.m; sourceTree = ""; }; + 048908E42EBF841B00FABA60 /* KBSkinDetailTagCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinDetailTagCell.h; sourceTree = ""; }; + 048908E52EBF841B00FABA60 /* KBSkinDetailTagCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinDetailTagCell.m; sourceTree = ""; }; + 048908E72EBF843000FABA60 /* KBSkinDetailHeaderCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinDetailHeaderCell.h; sourceTree = ""; }; + 048908E82EBF843000FABA60 /* KBSkinDetailHeaderCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinDetailHeaderCell.m; sourceTree = ""; }; + 048908EA2EBF849300FABA60 /* KBSkinTagsContainerCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinTagsContainerCell.h; sourceTree = ""; }; + 048908EB2EBF849300FABA60 /* KBSkinTagsContainerCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinTagsContainerCell.m; sourceTree = ""; }; + 048908ED2EBF861800FABA60 /* KBSkinSectionTitleCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinSectionTitleCell.h; sourceTree = ""; }; + 048908EE2EBF861800FABA60 /* KBSkinSectionTitleCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinSectionTitleCell.m; sourceTree = ""; }; 04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBResponderUtils.h; sourceTree = ""; }; 04A9FE102EB4D0D20020DB6D /* KBFullAccessManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFullAccessManager.h; sourceTree = ""; }; 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFullAccessManager.m; sourceTree = ""; }; @@ -767,6 +782,14 @@ children = ( 048908E12EBF760000FABA60 /* MySkinCell.h */, 048908E22EBF760000FABA60 /* MySkinCell.m */, + 048908E42EBF841B00FABA60 /* KBSkinDetailTagCell.h */, + 048908E52EBF841B00FABA60 /* KBSkinDetailTagCell.m */, + 048908E72EBF843000FABA60 /* KBSkinDetailHeaderCell.h */, + 048908E82EBF843000FABA60 /* KBSkinDetailHeaderCell.m */, + 048908EA2EBF849300FABA60 /* KBSkinTagsContainerCell.h */, + 048908EB2EBF849300FABA60 /* KBSkinTagsContainerCell.m */, + 048908ED2EBF861800FABA60 /* KBSkinSectionTitleCell.h */, + 048908EE2EBF861800FABA60 /* KBSkinSectionTitleCell.m */, ); path = V; sourceTree = ""; @@ -778,6 +801,8 @@ 04FC95D12EB1E7AE007BD342 /* MyVC.m */, 048908DE2EBF73DC00FABA60 /* MySkinVC.h */, 048908DF2EBF73DC00FABA60 /* MySkinVC.m */, + 048908E12EBF821700FABA60 /* KBSkinDetailVC.h */, + 048908E22EBF821700FABA60 /* KBSkinDetailVC.m */, ); path = VC; sourceTree = ""; @@ -1272,10 +1297,13 @@ 04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */, 04C6EABE2EAF86530089C901 /* AppDelegate.m in Sources */, 04FC95F12EB339A7007BD342 /* LoginViewController.m in Sources */, + 048908E92EBF843000FABA60 /* KBSkinDetailHeaderCell.m in Sources */, 04FC96142EB34E00007BD342 /* KBLoginSheetViewController.m in Sources */, 04A9FE1B2EB892460020DB6D /* KBLocalizationManager.m in Sources */, 048908BC2EBE1FCB00FABA60 /* BaseViewController.m in Sources */, 04FC95D72EB1EA16007BD342 /* BaseTableView.m in Sources */, + 048908EF2EBF861800FABA60 /* KBSkinSectionTitleCell.m in Sources */, + 048908E32EBF821700FABA60 /* KBSkinDetailVC.m in Sources */, 0477BDF32EBB7B850055D639 /* KBDirectionIndicatorView.m in Sources */, 048908D22EBF611D00FABA60 /* KBHistoryMoreCell.m in Sources */, 04FC95D82EB1EA16007BD342 /* BaseCell.m in Sources */, @@ -1289,7 +1317,9 @@ 04FC95DD2EB202A3007BD342 /* KBGuideVC.m in Sources */, 04FC95E52EB220B5007BD342 /* UIColor+Extension.m in Sources */, 048908E02EBF73DC00FABA60 /* MySkinVC.m in Sources */, + 048908EC2EBF849300FABA60 /* KBSkinTagsContainerCell.m in Sources */, 0477BDF02EBB76E30055D639 /* HomeSheetVC.m in Sources */, + 048908E62EBF841B00FABA60 /* KBSkinDetailTagCell.m in Sources */, 04FC97002EB30A00007BD342 /* KBGuideTopCell.m in Sources */, 0477BDFA2EBC66340055D639 /* HomeHeadView.m in Sources */, 04FC97032EB30A00007BD342 /* KBGuideKFCell.m in Sources */, diff --git a/keyBoard/Class/Me/V/KBSkinDetailHeaderCell.h b/keyBoard/Class/Me/V/KBSkinDetailHeaderCell.h new file mode 100644 index 0000000..20cb401 --- /dev/null +++ b/keyBoard/Class/Me/V/KBSkinDetailHeaderCell.h @@ -0,0 +1,19 @@ +// +// KBSkinDetailHeaderCell.h +// keyBoard +// +// Created by Mac on 2025/11/8. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KBSkinDetailHeaderCell : UICollectionViewCell +@property (nonatomic, strong) UIImageView *coverView; // 顶部大图 +@property (nonatomic, strong) UILabel *leftLabel; // 下方左侧文案(#1B1F1A) +@property (nonatomic, strong) UILabel *rightLabel; // 下方右侧文案(#02BEAC) +- (void)configWithTitle:(NSString *)title right:(NSString *)right; +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/V/KBSkinDetailHeaderCell.m b/keyBoard/Class/Me/V/KBSkinDetailHeaderCell.m new file mode 100644 index 0000000..0950ccd --- /dev/null +++ b/keyBoard/Class/Me/V/KBSkinDetailHeaderCell.m @@ -0,0 +1,75 @@ +// +// KBSkinDetailHeaderCell.m +// keyBoard +// +// Created by Mac on 2025/11/8. +// + +#import "KBSkinDetailHeaderCell.h" + +@implementation KBSkinDetailHeaderCell +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.contentView.backgroundColor = [UIColor whiteColor]; + self.contentView.layer.cornerRadius = 12; + self.contentView.layer.masksToBounds = YES; + + [self.contentView addSubview:self.coverView]; + [self.contentView addSubview:self.leftLabel]; + [self.contentView addSubview:self.rightLabel]; + + // 上图,16:9 比例;下方左右文案 + [self.coverView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.top.right.equalTo(self.contentView); + // 高度按照宽度等比(接近截图比例) + make.height.equalTo(self.contentView.mas_width).multipliedBy(0.58); + }]; + [self.leftLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.contentView).offset(12); + make.top.equalTo(self.coverView.mas_bottom).offset(10); + }]; + [self.rightLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.contentView).offset(-12); + make.centerY.equalTo(self.leftLabel); + }]; + } + return self; +} + +- (void)configWithTitle:(NSString *)title right:(NSString *)right { + // 演示:标题/下载量文案 + self.leftLabel.text = title.length ? title : @"Dopamine"; + self.rightLabel.text = right.length ? right : @"Download: 1 Million"; + // 本示例不做网络图,直接用占位背景色 + self.coverView.backgroundColor = [UIColor colorWithWhite:0.94 alpha:1.0]; +} + +#pragma mark - Lazy +- (UIImageView *)coverView { + if (!_coverView) { + _coverView = [[UIImageView alloc] init]; + _coverView.contentMode = UIViewContentModeScaleAspectFill; + _coverView.clipsToBounds = YES; + } + return _coverView; +} +- (UILabel *)leftLabel { + if (!_leftLabel) { + _leftLabel = [UILabel new]; + _leftLabel.textColor = [UIColor colorWithHex:0x1B1F1A]; + _leftLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold]; + _leftLabel.text = @"Dopamine"; + } + return _leftLabel; +} +- (UILabel *)rightLabel { + if (!_rightLabel) { + _rightLabel = [UILabel new]; + _rightLabel.textColor = [UIColor colorWithHex:0x02BEAC]; + _rightLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium]; + _rightLabel.textAlignment = NSTextAlignmentRight; + _rightLabel.text = @"Download: 1 Million"; + } + return _rightLabel; +} +@end diff --git a/keyBoard/Class/Me/V/KBSkinDetailTagCell.h b/keyBoard/Class/Me/V/KBSkinDetailTagCell.h new file mode 100644 index 0000000..b2be094 --- /dev/null +++ b/keyBoard/Class/Me/V/KBSkinDetailTagCell.h @@ -0,0 +1,18 @@ +// +// KBSkinDetailTagCell.h +// keyBoard +// +// Created by Mac on 2025/11/8. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KBSkinDetailTagCell : UICollectionViewCell +- (void)config:(NSString *)text; +/// 根据文案计算自适应宽度(外部布局用) ++ (CGSize)sizeForText:(NSString *)text; +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/V/KBSkinDetailTagCell.m b/keyBoard/Class/Me/V/KBSkinDetailTagCell.m new file mode 100644 index 0000000..63ee5fd --- /dev/null +++ b/keyBoard/Class/Me/V/KBSkinDetailTagCell.m @@ -0,0 +1,48 @@ +// +// KBSkinDetailTagCell.m +// keyBoard +// +// Created by Mac on 2025/11/8. +// + +#import "KBSkinDetailTagCell.h" +@interface KBSkinDetailTagCell () +@property (nonatomic, strong) UILabel *titleLabel; +@end +@implementation KBSkinDetailTagCell +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.contentView.backgroundColor = [UIColor colorWithWhite:0.96 alpha:1.0]; + self.contentView.layer.cornerRadius = 16; + self.contentView.layer.masksToBounds = YES; + [self.contentView addSubview:self.titleLabel]; + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.contentView).insets(UIEdgeInsetsMake(6, 12, 6, 12)); + }]; + } + return self; +} + +- (void)config:(NSString *)text { + self.titleLabel.text = text ?: @""; +} + ++ (CGSize)sizeForText:(NSString *)text { + if (text.length == 0) { return CGSizeMake(40, 32); } + CGSize s = [text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]}]; + // 两侧内边距 12 + 12,高度固定 32 + return CGSizeMake(ceil(s.width) + 24, 32); +} + +#pragma mark - Lazy + +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [[UILabel alloc] init]; + _titleLabel.font = [UIFont systemFontOfSize:14]; + _titleLabel.textColor = [UIColor colorWithHex:0x1B1F1A]; + } + return _titleLabel; +} + +@end diff --git a/keyBoard/Class/Me/V/KBSkinSectionTitleCell.h b/keyBoard/Class/Me/V/KBSkinSectionTitleCell.h new file mode 100644 index 0000000..c7e6cb8 --- /dev/null +++ b/keyBoard/Class/Me/V/KBSkinSectionTitleCell.h @@ -0,0 +1,17 @@ +// +// KBSkinSectionTitleCell.h +// keyBoard +// +// Created by Mac on 2025/11/8. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +/// MARK: - 区块标题 cell +@interface KBSkinSectionTitleCell : UICollectionViewCell +@property (nonatomic, strong) UILabel *titleLabel; +- (void)config:(NSString *)title; +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/V/KBSkinSectionTitleCell.m b/keyBoard/Class/Me/V/KBSkinSectionTitleCell.m new file mode 100644 index 0000000..397cbaf --- /dev/null +++ b/keyBoard/Class/Me/V/KBSkinSectionTitleCell.m @@ -0,0 +1,36 @@ +// +// KBSkinSectionTitleCell.m +// keyBoard +// +// Created by Mac on 2025/11/8. +// + +#import "KBSkinSectionTitleCell.h" + +@implementation KBSkinSectionTitleCell +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.contentView.backgroundColor = [UIColor whiteColor]; + [self.contentView addSubview:self.titleLabel]; + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.contentView).offset(16); + make.centerY.equalTo(self.contentView); + }]; + } + return self; +} + +- (void)config:(NSString *)title { + self.titleLabel.text = title ?: @"Recommended Skin"; +} + +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [UILabel new]; + _titleLabel.textColor = [UIColor colorWithHex:0x1B1F1A]; + _titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold]; + _titleLabel.text = @"Recommended Skin"; + } + return _titleLabel; +} +@end diff --git a/keyBoard/Class/Me/V/KBSkinTagsContainerCell.h b/keyBoard/Class/Me/V/KBSkinTagsContainerCell.h new file mode 100644 index 0000000..3cf4f72 --- /dev/null +++ b/keyBoard/Class/Me/V/KBSkinTagsContainerCell.h @@ -0,0 +1,20 @@ +// +// KBSkinTagsContainerCell.h +// keyBoard +// +// Created by Mac on 2025/11/8. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KBSkinTagsContainerCell : UICollectionViewCell +@property (nonatomic, strong) UICollectionView *tagsView; // 内部标签列表 +@property (nonatomic, copy) NSArray *tags; // 标签文案 +- (void)configWithTags:(NSArray *)tags; +/// 根据给定宽度,计算该容器需要的高度(用于外部 sizeForItem) ++ (CGFloat)heightForTags:(NSArray *)tags width:(CGFloat)width; +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/V/KBSkinTagsContainerCell.m b/keyBoard/Class/Me/V/KBSkinTagsContainerCell.m new file mode 100644 index 0000000..e23ba59 --- /dev/null +++ b/keyBoard/Class/Me/V/KBSkinTagsContainerCell.m @@ -0,0 +1,91 @@ +// +// KBSkinTagsContainerCell.m +// keyBoard +// +// Created by Mac on 2025/11/8. +// + +#import "KBSkinTagsContainerCell.h" +#import "KBSkinDetailTagCell.h" +#import "UICollectionViewLeftAlignedLayout.h" +static NSString * const kInnerTagCellId = @"kInnerTagCellId"; + +@implementation KBSkinTagsContainerCell + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.contentView.backgroundColor = [UIColor whiteColor]; + [self.contentView addSubview:self.tagsView]; + [self.tagsView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.contentView); + }]; + } + return self; +} + +- (void)configWithTags:(NSArray *)tags { + self.tags = tags; + [self.tagsView reloadData]; +} + +#pragma mark - UICollectionView + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.tags.count; +} +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + KBSkinDetailTagCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kInnerTagCellId forIndexPath:indexPath]; + [cell config:self.tags[indexPath.item]]; + return cell; +} +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + // 根据文案自适应宽度,高度固定 32 + return [KBSkinDetailTagCell sizeForText:self.tags[indexPath.item]]; +} + +#pragma mark - Height Helper + ++ (CGFloat)heightForTags:(NSArray *)tags width:(CGFloat)width { + if (tags.count == 0) { return 0; } + // 计算在给定宽度下的自动换行高度(与内部布局保持一致:行间距 8,item 间距 8,sectionInsets = {0,16,0,16}) + CGFloat leftRight = 16 * 2; // 外部 VC 会给整体 sectionInset=16,这里额外留一点安全边距 + CGFloat maxWidth = width - leftRight; + CGFloat x = 0; + CGFloat rows = 1; + for (NSString *t in tags) { + CGSize s = [KBSkinDetailTagCell sizeForText:t]; + CGFloat iw = ceil(s.width); + if (x == 0) { + x = iw; + } else { + // item 间距 8 + if (x + 8 + iw > maxWidth) { // 换行 + rows += 1; + x = iw; + } else { + x += 8 + iw; + } + } + } + // 行高固定 32;行间距 8;上下额外增加 8 视觉留白 + return rows * 32 + (rows - 1) * 8 + 16; +} + +#pragma mark - Lazy +- (UICollectionView *)tagsView { + if (!_tagsView) { + UICollectionViewLeftAlignedLayout *layout = [UICollectionViewLeftAlignedLayout new]; + layout.minimumInteritemSpacing = 8; + layout.minimumLineSpacing = 8; + layout.sectionInset = UIEdgeInsetsMake(8, 0, 8, 16); + + _tagsView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + _tagsView.backgroundColor = [UIColor whiteColor]; + _tagsView.dataSource = self; + _tagsView.delegate = self; + [_tagsView registerClass:KBSkinDetailTagCell.class forCellWithReuseIdentifier:kInnerTagCellId]; + _tagsView.scrollEnabled = NO; // 由外层滚动 + } + return _tagsView; +} +@end diff --git a/keyBoard/Class/Me/VC/KBSkinDetailVC.h b/keyBoard/Class/Me/VC/KBSkinDetailVC.h new file mode 100644 index 0000000..addd4c6 --- /dev/null +++ b/keyBoard/Class/Me/VC/KBSkinDetailVC.h @@ -0,0 +1,16 @@ +// +// KBSkinDetailVC.h +// keyBoard +// +// Created by Mac on 2025/11/8. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KBSkinDetailVC : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/VC/KBSkinDetailVC.m b/keyBoard/Class/Me/VC/KBSkinDetailVC.m new file mode 100644 index 0000000..4670012 --- /dev/null +++ b/keyBoard/Class/Me/VC/KBSkinDetailVC.m @@ -0,0 +1,167 @@ +// +// KBSkinDetailVC.m +// keyBoard +// +// 皮肤详情页(UICollectionView 实现) +// 结构: +// - Section0:顶部大卡片(上图,下方左右两段文案) +// - Section1:标签容器 cell(内部再嵌套一个 collectionView 以展示 Cute/Fresh 等标签) +// - Section2:区块标题 cell(例如 “Recommended Skin”) +// - Section3:推荐皮肤 2 列网格(使用已有 KBSkinCardCell) +// + +#import "KBSkinDetailVC.h" +#import +#import "UIColor+Extension.h" +#import "UICollectionViewLeftAlignedLayout.h" +#import "KBSkinDetailHeaderCell.h" +#import "KBSkinTagsContainerCell.h" +#import "KBSkinCardCell.h" // 已有的 皮肤卡片 cell(用作 2 列网格) +#import "KBSkinSectionTitleCell.h" + +static NSString * const kHeaderCellId = @"kHeaderCellId"; +static NSString * const kTagsContainerCellId = @"kTagsContainerCellId"; +static NSString * const kSectionTitleCellId = @"kSectionTitleCellId"; +static NSString * const kGridCellId = @"kGridCellId"; // 直接复用 KBSkinCardCell + +typedef NS_ENUM(NSInteger, KBSkinDetailSection) { + KBSkinDetailSectionHeader = 0, + KBSkinDetailSectionTags, + KBSkinDetailSectionTitle, + KBSkinDetailSectionGrid, + KBSkinDetailSectionCount +}; + +@interface KBSkinDetailVC () +@property (nonatomic, strong) UICollectionView *collectionView; // 主列表 +@property (nonatomic, copy) NSArray *tags; // 标签数据 +@property (nonatomic, copy) NSArray *gridData; // 底部网格数据 +@end + +@implementation KBSkinDetailVC + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + + // 简单数据(演示) + self.tags = @[ @"Cute", @"Fresh", @"Cute", @"Fresh", @"Cute", @"Fresh" ]; + self.gridData = @[ + @{ @"title": @"Dopamine" }, @{ @"title": @"Dopamine" }, + @{ @"title": @"Dopamine" }, @{ @"title": @"Dopamine" }, + @{ @"title": @"Dopamine" }, @{ @"title": @"Dopamine" }, + ]; + + [self.view addSubview:self.collectionView]; + [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.view); + }]; +} + +#pragma mark - UICollectionView DataSource + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return KBSkinDetailSectionCount; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + switch (section) { + case KBSkinDetailSectionHeader: return 1; // 顶部大卡片 + case KBSkinDetailSectionTags: return 1; // 标签容器 + case KBSkinDetailSectionTitle: return 1; // 标题 + case KBSkinDetailSectionGrid: return self.gridData.count; // 2 列网格 + } + return 0; +} + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + switch (indexPath.section) { + case KBSkinDetailSectionHeader: { + KBSkinDetailHeaderCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kHeaderCellId forIndexPath:indexPath]; + [cell configWithTitle:@"Dopamine" right:@"Download: 1 Million"]; + return cell; + } + case KBSkinDetailSectionTags: { + KBSkinTagsContainerCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kTagsContainerCellId forIndexPath:indexPath]; + [cell configWithTags:self.tags]; + return cell; + } + case KBSkinDetailSectionTitle: { + KBSkinSectionTitleCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kSectionTitleCellId forIndexPath:indexPath]; + [cell config:@"Recommended Skin"]; + return cell; + } + case KBSkinDetailSectionGrid: { + KBSkinCardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kGridCellId forIndexPath:indexPath]; + NSDictionary *d = self.gridData[indexPath.item]; + [cell configWithTitle:d[@"title"] imageURL:nil price:@"20"]; + return cell; + } + } + return [UICollectionViewCell new]; +} + +#pragma mark - UICollectionView DelegateFlowLayout + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + CGFloat W = collectionView.bounds.size.width; + CGFloat insetLR = 16.0; + CGFloat contentW = W - insetLR * 2; + switch (indexPath.section) { + case KBSkinDetailSectionHeader: { + // 高度 = 图片 0.58W + 文案与上下留白(≈56) + CGFloat h = contentW * 0.58 + 56; + return CGSizeMake(contentW, h); + } + case KBSkinDetailSectionTags: { + CGFloat h = [KBSkinTagsContainerCell heightForTags:self.tags width:W]; + return CGSizeMake(contentW, h); + } + case KBSkinDetailSectionTitle: { + return CGSizeMake(contentW, 44); + } + case KBSkinDetailSectionGrid: { + // 2 列 + CGFloat spacing = 12.0; + CGFloat itemW = floor((contentW - spacing) / 2.0); + CGFloat itemH = itemW * 0.75 + 56; // 参考 KBSkinCardCell 内部布局 + return CGSizeMake(itemW, itemH); + } + } + return CGSizeZero; +} + +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { + return UIEdgeInsetsMake(12, 16, 12, 16); +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { + return 12.0; +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { + // 网格区需要这个间距,其它区也保持统一 + return 12.0; +} + +#pragma mark - Lazy + +- (UICollectionView *)collectionView { + if (!_collectionView) { + UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; + layout.scrollDirection = UICollectionViewScrollDirectionVertical; + _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + _collectionView.backgroundColor = [UIColor whiteColor]; + _collectionView.dataSource = self; + _collectionView.delegate = self; + + // 注册 cell + [_collectionView registerClass:KBSkinDetailHeaderCell.class forCellWithReuseIdentifier:kHeaderCellId]; + [_collectionView registerClass:KBSkinTagsContainerCell.class forCellWithReuseIdentifier:kTagsContainerCellId]; + [_collectionView registerClass:KBSkinSectionTitleCell.class forCellWithReuseIdentifier:kSectionTitleCellId]; + [_collectionView registerClass:KBSkinCardCell.class forCellWithReuseIdentifier:kGridCellId]; + } + return _collectionView; +} + +@end diff --git a/keyBoard/Class/Me/VC/MyVC.m b/keyBoard/Class/Me/VC/MyVC.m index 2e91868..44fba6c 100644 --- a/keyBoard/Class/Me/VC/MyVC.m +++ b/keyBoard/Class/Me/VC/MyVC.m @@ -7,6 +7,7 @@ #import "MyVC.h" #import "MySkinVC.h" +#import "KBSkinDetailVC.h" @interface MyVC () @@ -28,7 +29,9 @@ } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ - MySkinVC *vc = [[MySkinVC alloc] init]; +// MySkinVC *vc = [[MySkinVC alloc] init]; + KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init]; + [self.navigationController pushViewController:vc animated:true]; }