3
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
048908DE2EBF73DC00FABA60 /* MySkinVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinVC.h; sourceTree = "<group>"; };
|
||||
048908DF2EBF73DC00FABA60 /* MySkinVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinVC.m; sourceTree = "<group>"; };
|
||||
048908E12EBF760000FABA60 /* MySkinCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinCell.h; sourceTree = "<group>"; };
|
||||
048908E22EBF760000FABA60 /* MySkinCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinCell.m; sourceTree = "<group>"; };
|
||||
048908E12EBF760000FABA60 /* MySkinCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinCell.h; sourceTree = "<group>"; };
|
||||
048908E12EBF821700FABA60 /* KBSkinDetailVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinDetailVC.h; sourceTree = "<group>"; };
|
||||
048908E22EBF760000FABA60 /* MySkinCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinCell.m; sourceTree = "<group>"; };
|
||||
048908E22EBF821700FABA60 /* KBSkinDetailVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinDetailVC.m; sourceTree = "<group>"; };
|
||||
048908E42EBF841B00FABA60 /* KBSkinDetailTagCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinDetailTagCell.h; sourceTree = "<group>"; };
|
||||
048908E52EBF841B00FABA60 /* KBSkinDetailTagCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinDetailTagCell.m; sourceTree = "<group>"; };
|
||||
048908E72EBF843000FABA60 /* KBSkinDetailHeaderCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinDetailHeaderCell.h; sourceTree = "<group>"; };
|
||||
048908E82EBF843000FABA60 /* KBSkinDetailHeaderCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinDetailHeaderCell.m; sourceTree = "<group>"; };
|
||||
048908EA2EBF849300FABA60 /* KBSkinTagsContainerCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinTagsContainerCell.h; sourceTree = "<group>"; };
|
||||
048908EB2EBF849300FABA60 /* KBSkinTagsContainerCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinTagsContainerCell.m; sourceTree = "<group>"; };
|
||||
048908ED2EBF861800FABA60 /* KBSkinSectionTitleCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinSectionTitleCell.h; sourceTree = "<group>"; };
|
||||
048908EE2EBF861800FABA60 /* KBSkinSectionTitleCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinSectionTitleCell.m; sourceTree = "<group>"; };
|
||||
04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBResponderUtils.h; sourceTree = "<group>"; };
|
||||
04A9FE102EB4D0D20020DB6D /* KBFullAccessManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFullAccessManager.h; sourceTree = "<group>"; };
|
||||
04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFullAccessManager.m; sourceTree = "<group>"; };
|
||||
@@ -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 = "<group>";
|
||||
@@ -778,6 +801,8 @@
|
||||
04FC95D12EB1E7AE007BD342 /* MyVC.m */,
|
||||
048908DE2EBF73DC00FABA60 /* MySkinVC.h */,
|
||||
048908DF2EBF73DC00FABA60 /* MySkinVC.m */,
|
||||
048908E12EBF821700FABA60 /* KBSkinDetailVC.h */,
|
||||
048908E22EBF821700FABA60 /* KBSkinDetailVC.m */,
|
||||
);
|
||||
path = VC;
|
||||
sourceTree = "<group>";
|
||||
@@ -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 */,
|
||||
|
||||
19
keyBoard/Class/Me/V/KBSkinDetailHeaderCell.h
Normal file
19
keyBoard/Class/Me/V/KBSkinDetailHeaderCell.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// KBSkinDetailHeaderCell.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2025/11/8.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
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
|
||||
75
keyBoard/Class/Me/V/KBSkinDetailHeaderCell.m
Normal file
75
keyBoard/Class/Me/V/KBSkinDetailHeaderCell.m
Normal file
@@ -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
|
||||
18
keyBoard/Class/Me/V/KBSkinDetailTagCell.h
Normal file
18
keyBoard/Class/Me/V/KBSkinDetailTagCell.h
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// KBSkinDetailTagCell.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2025/11/8.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface KBSkinDetailTagCell : UICollectionViewCell
|
||||
- (void)config:(NSString *)text;
|
||||
/// 根据文案计算自适应宽度(外部布局用)
|
||||
+ (CGSize)sizeForText:(NSString *)text;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
48
keyBoard/Class/Me/V/KBSkinDetailTagCell.m
Normal file
48
keyBoard/Class/Me/V/KBSkinDetailTagCell.m
Normal file
@@ -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
|
||||
17
keyBoard/Class/Me/V/KBSkinSectionTitleCell.h
Normal file
17
keyBoard/Class/Me/V/KBSkinSectionTitleCell.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// KBSkinSectionTitleCell.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2025/11/8.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
/// MARK: - 区块标题 cell
|
||||
@interface KBSkinSectionTitleCell : UICollectionViewCell
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
- (void)config:(NSString *)title;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
36
keyBoard/Class/Me/V/KBSkinSectionTitleCell.m
Normal file
36
keyBoard/Class/Me/V/KBSkinSectionTitleCell.m
Normal file
@@ -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
|
||||
20
keyBoard/Class/Me/V/KBSkinTagsContainerCell.h
Normal file
20
keyBoard/Class/Me/V/KBSkinTagsContainerCell.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// KBSkinTagsContainerCell.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2025/11/8.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface KBSkinTagsContainerCell : UICollectionViewCell <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
||||
@property (nonatomic, strong) UICollectionView *tagsView; // 内部标签列表
|
||||
@property (nonatomic, copy) NSArray<NSString *> *tags; // 标签文案
|
||||
- (void)configWithTags:(NSArray<NSString *> *)tags;
|
||||
/// 根据给定宽度,计算该容器需要的高度(用于外部 sizeForItem)
|
||||
+ (CGFloat)heightForTags:(NSArray<NSString *> *)tags width:(CGFloat)width;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
91
keyBoard/Class/Me/V/KBSkinTagsContainerCell.m
Normal file
91
keyBoard/Class/Me/V/KBSkinTagsContainerCell.m
Normal file
@@ -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<NSString *> *)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<NSString *> *)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
|
||||
16
keyBoard/Class/Me/VC/KBSkinDetailVC.h
Normal file
16
keyBoard/Class/Me/VC/KBSkinDetailVC.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// KBSkinDetailVC.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2025/11/8.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface KBSkinDetailVC : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
167
keyBoard/Class/Me/VC/KBSkinDetailVC.m
Normal file
167
keyBoard/Class/Me/VC/KBSkinDetailVC.m
Normal file
@@ -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 <Masonry/Masonry.h>
|
||||
#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 () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
||||
@property (nonatomic, strong) UICollectionView *collectionView; // 主列表
|
||||
@property (nonatomic, copy) NSArray<NSString *> *tags; // 标签数据
|
||||
@property (nonatomic, copy) NSArray<NSDictionary *> *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
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#import "MyVC.h"
|
||||
#import "MySkinVC.h"
|
||||
#import "KBSkinDetailVC.h"
|
||||
|
||||
@interface MyVC ()
|
||||
|
||||
@@ -28,7 +29,9 @@
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)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];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user