diff --git a/Shared/KBConfig.h b/Shared/KBConfig.h index d4e343d..e525573 100644 --- a/Shared/KBConfig.h +++ b/Shared/KBConfig.h @@ -8,6 +8,11 @@ #ifndef KBConfig_h #define KBConfig_h +// UIKit is needed for CGFloat and UIScreen used by size-adaptation helpers +#if __OBJC__ +#import +#endif + // 基础baseUrl #ifndef KB_BASE_URL #define KB_BASE_URL @"https://m1.apifoxmock.com/m1/5438099-5113192-default/" @@ -45,6 +50,27 @@ #define KB_APP_SCHEME @"kbkeyboardAppExtension" #endif +// --- 尺寸适配(以 375 设计稿为基准) --- +// 用法:传入设计稿上的数值,返回按当前屏幕宽度缩放后的值。 +// 例如:CGFloat padding = KBFit(16); +#ifndef KB_DESIGN_WIDTH +#define KB_DESIGN_WIDTH 375.0 +#endif + +#if __OBJC__ +static inline CGFloat KBScreenWidth(void) { + return [UIScreen mainScreen].bounds.size.width; +} + +static inline CGFloat KBScaleFactor(void) { + return KBScreenWidth() / (CGFloat)KB_DESIGN_WIDTH; +} + +static inline CGFloat KBFit(CGFloat designValue) { + return designValue * KBScaleFactor(); +} +#endif + // --- 常用宏 --- // 弱引用 self(在 block 中避免循环引用):使用处直接写 KBWeakSelf; #ifndef KBWeakSelf diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 4038055..aec26d4 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 0477BDF32EBB7B850055D639 /* KBDirectionIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDF22EBB7B850055D639 /* KBDirectionIndicatorView.m */; }; 0477BDF72EBC63A80055D639 /* KBTestVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDF62EBC63A80055D639 /* KBTestVC.m */; }; 0477BDFA2EBC66340055D639 /* HomeHeadView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDF92EBC66340055D639 /* HomeHeadView.m */; }; + 0477BDFD2EBC6A170055D639 /* HomeHotVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDFC2EBC6A170055D639 /* HomeHotVC.m */; }; + 0477BE002EBC6A330055D639 /* HomeRankVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDFF2EBC6A330055D639 /* HomeRankVC.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 */; }; @@ -109,6 +111,10 @@ 0477BDF62EBC63A80055D639 /* KBTestVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBTestVC.m; sourceTree = ""; }; 0477BDF82EBC66340055D639 /* HomeHeadView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeHeadView.h; sourceTree = ""; }; 0477BDF92EBC66340055D639 /* HomeHeadView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HomeHeadView.m; sourceTree = ""; }; + 0477BDFB2EBC6A170055D639 /* HomeHotVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeHotVC.h; sourceTree = ""; }; + 0477BDFC2EBC6A170055D639 /* HomeHotVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HomeHotVC.m; sourceTree = ""; }; + 0477BDFE2EBC6A330055D639 /* HomeRankVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeRankVC.h; sourceTree = ""; }; + 0477BDFF2EBC6A330055D639 /* HomeRankVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HomeRankVC.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 = ""; }; @@ -241,6 +247,19 @@ path = Utils; sourceTree = ""; }; + 0477BE012EBC6D420055D639 /* FunctionTest */ = { + isa = PBXGroup; + children = ( + 0477BDF52EBC63A80055D639 /* KBTestVC.h */, + 0477BDF62EBC63A80055D639 /* KBTestVC.m */, + A1B2D7002EB8C00100000001 /* KBLangTestVC.h */, + A1B2D7012EB8C00100000001 /* KBLangTestVC.m */, + 0459D1B22EBA284C00F2D189 /* KBSkinCenterVC.h */, + 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */, + ); + path = FunctionTest; + sourceTree = ""; + }; 04A9FE122EB4D0D20020DB6D /* Manager */ = { isa = PBXGroup; children = ( @@ -363,14 +382,13 @@ children = ( 04FC95CD2EB1E7A1007BD342 /* HomeVC.h */, 04FC95CE2EB1E7A1007BD342 /* HomeVC.m */, - A1B2D7002EB8C00100000001 /* KBLangTestVC.h */, - A1B2D7012EB8C00100000001 /* KBLangTestVC.m */, - 0459D1B22EBA284C00F2D189 /* KBSkinCenterVC.h */, - 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */, 0477BDEE2EBB76E30055D639 /* HomeSheetVC.h */, 0477BDEF2EBB76E30055D639 /* HomeSheetVC.m */, - 0477BDF52EBC63A80055D639 /* KBTestVC.h */, - 0477BDF62EBC63A80055D639 /* KBTestVC.m */, + 0477BDFB2EBC6A170055D639 /* HomeHotVC.h */, + 0477BDFC2EBC6A170055D639 /* HomeHotVC.m */, + 0477BDFE2EBC6A330055D639 /* HomeRankVC.h */, + 0477BDFF2EBC6A330055D639 /* HomeRankVC.m */, + 0477BE012EBC6D420055D639 /* FunctionTest */, ); path = VC; sourceTree = ""; @@ -885,6 +903,7 @@ files = ( 04FC95E92EB23B67007BD342 /* KBNetworkManager.m in Sources */, 04FC95D22EB1E7AE007BD342 /* MyVC.m in Sources */, + 0477BE002EBC6A330055D639 /* HomeRankVC.m in Sources */, 043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */, 04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */, 04C6EABE2EAF86530089C901 /* AppDelegate.m in Sources */, @@ -910,6 +929,7 @@ A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */, 04C6EABF2EAF86530089C901 /* main.m in Sources */, 04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */, + 0477BDFD2EBC6A170055D639 /* HomeHotVC.m in Sources */, 0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */, 04FC95F42EB339C1007BD342 /* AppleSignInManager.m in Sources */, 04C6EAC12EAF86530089C901 /* ViewController.m in Sources */, diff --git a/keyBoard/Class/Home/V/HomeHeadView.m b/keyBoard/Class/Home/V/HomeHeadView.m index 41ce832..9fa922d 100644 --- a/keyBoard/Class/Home/V/HomeHeadView.m +++ b/keyBoard/Class/Home/V/HomeHeadView.m @@ -9,12 +9,11 @@ @implementation HomeHeadView -/* -// Only override drawRect: if you perform custom drawing. -// An empty implementation adversely affects performance during animation. -- (void)drawRect:(CGRect)rect { - // Drawing code +- (instancetype)initWithFrame:(CGRect)frame{ + if (self = [super initWithFrame:frame]) { + self.backgroundColor = [UIColor blueColor]; + } + return self; } -*/ @end diff --git a/keyBoard/Class/Home/VC/KBLangTestVC.h b/keyBoard/Class/Home/VC/FunctionTest/KBLangTestVC.h similarity index 100% rename from keyBoard/Class/Home/VC/KBLangTestVC.h rename to keyBoard/Class/Home/VC/FunctionTest/KBLangTestVC.h diff --git a/keyBoard/Class/Home/VC/KBLangTestVC.m b/keyBoard/Class/Home/VC/FunctionTest/KBLangTestVC.m similarity index 100% rename from keyBoard/Class/Home/VC/KBLangTestVC.m rename to keyBoard/Class/Home/VC/FunctionTest/KBLangTestVC.m diff --git a/keyBoard/Class/Home/VC/KBSkinCenterVC.h b/keyBoard/Class/Home/VC/FunctionTest/KBSkinCenterVC.h similarity index 100% rename from keyBoard/Class/Home/VC/KBSkinCenterVC.h rename to keyBoard/Class/Home/VC/FunctionTest/KBSkinCenterVC.h diff --git a/keyBoard/Class/Home/VC/KBSkinCenterVC.m b/keyBoard/Class/Home/VC/FunctionTest/KBSkinCenterVC.m similarity index 100% rename from keyBoard/Class/Home/VC/KBSkinCenterVC.m rename to keyBoard/Class/Home/VC/FunctionTest/KBSkinCenterVC.m diff --git a/keyBoard/Class/Home/VC/KBTestVC.h b/keyBoard/Class/Home/VC/FunctionTest/KBTestVC.h similarity index 100% rename from keyBoard/Class/Home/VC/KBTestVC.h rename to keyBoard/Class/Home/VC/FunctionTest/KBTestVC.h diff --git a/keyBoard/Class/Home/VC/KBTestVC.m b/keyBoard/Class/Home/VC/FunctionTest/KBTestVC.m similarity index 100% rename from keyBoard/Class/Home/VC/KBTestVC.m rename to keyBoard/Class/Home/VC/FunctionTest/KBTestVC.m diff --git a/keyBoard/Class/Home/VC/HomeHotVC.h b/keyBoard/Class/Home/VC/HomeHotVC.h new file mode 100644 index 0000000..59c8c35 --- /dev/null +++ b/keyBoard/Class/Home/VC/HomeHotVC.h @@ -0,0 +1,16 @@ +// +// HomeHotVC.h +// keyBoard +// +// Created by Mac on 2025/11/6. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface HomeHotVC : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Home/VC/HomeHotVC.m b/keyBoard/Class/Home/VC/HomeHotVC.m new file mode 100644 index 0000000..1725328 --- /dev/null +++ b/keyBoard/Class/Home/VC/HomeHotVC.m @@ -0,0 +1,31 @@ +// +// HomeHotVC.m +// keyBoard +// +// Created by Mac on 2025/11/6. +// + +#import "HomeHotVC.h" + +@interface HomeHotVC () + +@end + +@implementation HomeHotVC + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + +/* +#pragma mark - Navigation + +// In a storyboard-based application, you will often want to do a little preparation before navigation +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + // Get the new view controller using [segue destinationViewController]. + // Pass the selected object to the new view controller. +} +*/ + +@end diff --git a/keyBoard/Class/Home/VC/HomeRankVC.h b/keyBoard/Class/Home/VC/HomeRankVC.h new file mode 100644 index 0000000..e439160 --- /dev/null +++ b/keyBoard/Class/Home/VC/HomeRankVC.h @@ -0,0 +1,16 @@ +// +// HomeRankVC.h +// keyBoard +// +// Created by Mac on 2025/11/6. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface HomeRankVC : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Home/VC/HomeRankVC.m b/keyBoard/Class/Home/VC/HomeRankVC.m new file mode 100644 index 0000000..802a286 --- /dev/null +++ b/keyBoard/Class/Home/VC/HomeRankVC.m @@ -0,0 +1,31 @@ +// +// HomeRankVC.m +// keyBoard +// +// Created by Mac on 2025/11/6. +// + +#import "HomeRankVC.h" + +@interface HomeRankVC () + +@end + +@implementation HomeRankVC + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + +/* +#pragma mark - Navigation + +// In a storyboard-based application, you will often want to do a little preparation before navigation +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + // Get the new view controller using [segue destinationViewController]. + // Pass the selected object to the new view controller. +} +*/ + +@end diff --git a/keyBoard/Class/Home/VC/HomeSheetVC.m b/keyBoard/Class/Home/VC/HomeSheetVC.m index 4e4a5f9..ae320e8 100644 --- a/keyBoard/Class/Home/VC/HomeSheetVC.m +++ b/keyBoard/Class/Home/VC/HomeSheetVC.m @@ -7,9 +7,24 @@ #import "HomeSheetVC.h" #import "KBDirectionIndicatorView.h" +// 子控制器 +#import "HomeHotVC.h" +#import "HomeRankVC.h" @interface HomeSheetVC () @property (nonatomic, strong) KBDirectionIndicatorView *indicator; + +// 顶部切换按钮与指示条 +@property (nonatomic, strong) UIView *topBar; +@property (nonatomic, strong) UIButton *hotButton; +@property (nonatomic, strong) UIButton *rankButton; +@property (nonatomic, strong) UIView *underlineView; // 选中下划线 + +// 承载子控制器内容 +@property (nonatomic, strong) UIView *containerView; +@property (nonatomic, strong) UIViewController *currentChild; +@property (nonatomic, strong) HomeHotVC *hotVC; +@property (nonatomic, strong) HomeRankVC *rankVC; @end @implementation HomeSheetVC @@ -22,6 +37,11 @@ config = [HWBackgroundConfig configWithBehavior:HWBackgroundBehaviorDefault]; config.backgroundAlpha = 0.01; [self.hw_dimmedView reloadConfig:config]; + + // 顶部按钮 + 容器 + [self setupTopButtonsAndContainer]; + // 默认展示“热门” + [self switchToIndex:0 animated:NO]; } - (UIView *)customIndicatorView { @@ -32,6 +52,13 @@ - (void)panModalTransitionDidFinish { // 初次展示后按当前状态设定一次朝向 [self.indicator applyPresentationState:self.hw_presentationState]; + // 避免出现内容随弹窗上移的“位移动画”观感: + // 顶部栏在展示动画期间先隐藏,待完成后淡入 + if (self.topBar && self.topBar.alpha < 1.0) { + [UIView animateWithDuration:0.18 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.topBar.alpha = 1.0; + } completion:nil]; + } } - (void)didChangeTransitionToState:(PresentationState)state { @@ -77,5 +104,157 @@ return NO; } +// 出现时不做上推动画(瞬时到位) +- (NSTimeInterval)transitionDuration { + return 0; +} + +// 可选:关闭触觉反馈,避免出现时的轻微震动 +- (BOOL)isHapticFeedbackEnabled { + return NO; +} + + +#pragma mark - UI + +- (void)setupTopButtonsAndContainer { + // 顶部栏 + self.topBar = [[UIView alloc] init]; + self.topBar.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9]; + [self.view addSubview:self.topBar]; + // 首次展示时先隐藏,待转场完成后再淡入,避免“自底向上滑入”的错觉 + self.topBar.alpha = 0.0; + + // 两个按钮 + self.hotButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [self.hotButton setTitle:@"热门" forState:UIControlStateNormal]; + [self.hotButton setTitleColor:[UIColor darkTextColor] forState:UIControlStateNormal]; + [self.hotButton setTitleColor:[UIColor blackColor] forState:UIControlStateSelected]; + self.hotButton.titleLabel.font = [UIFont boldSystemFontOfSize:16]; + self.hotButton.tag = 0; + [self.hotButton addTarget:self action:@selector(onTapTopButton:) forControlEvents:UIControlEventTouchUpInside]; + [self.topBar addSubview:self.hotButton]; + + self.rankButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [self.rankButton setTitle:@"排行" forState:UIControlStateNormal]; + [self.rankButton setTitleColor:[UIColor darkTextColor] forState:UIControlStateNormal]; + [self.rankButton setTitleColor:[UIColor blackColor] forState:UIControlStateSelected]; + self.rankButton.titleLabel.font = [UIFont boldSystemFontOfSize:16]; + self.rankButton.tag = 1; + [self.rankButton addTarget:self action:@selector(onTapTopButton:) forControlEvents:UIControlEventTouchUpInside]; + [self.topBar addSubview:self.rankButton]; + + // 下划线(跟随选中按钮) + self.underlineView = [[UIView alloc] init]; + self.underlineView.backgroundColor = [UIColor blackColor]; + self.underlineView.layer.cornerRadius = 1.0; + [self.topBar addSubview:self.underlineView]; + + // 容器视图 + self.containerView = [[UIView alloc] init]; + self.containerView.backgroundColor = [UIColor whiteColor]; + self.containerView.clipsToBounds = YES; + [self.view addSubview:self.containerView]; + + // Masonry 约束 + CGFloat topPadding = 12; // 与顶部小指示器留点空间 + [self.topBar mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.equalTo(self.view); + make.top.equalTo(self.view).offset(topPadding); + make.height.mas_equalTo(44); + }]; + + [self.hotButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.bottom.equalTo(self.topBar); + make.left.equalTo(self.topBar); + }]; + [self.rankButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.bottom.equalTo(self.topBar); + make.right.equalTo(self.topBar); + make.left.equalTo(self.hotButton.mas_right); + make.width.equalTo(self.hotButton); + }]; + + // 初始先放在“热门”下方,宽度稍小于按钮文字 + [self.underlineView mas_makeConstraints:^(MASConstraintMaker *make) { + make.height.mas_equalTo(2); + make.bottom.equalTo(self.topBar.mas_bottom).offset(-2); + make.centerX.equalTo(self.hotButton); + make.width.mas_equalTo(24); + }]; + + [self.containerView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.topBar.mas_bottom).offset(8); + make.left.right.bottom.equalTo(self.view); + }]; +} + +#pragma mark - Action + +- (void)onTapTopButton:(UIButton *)sender { + [self switchToIndex:sender.tag animated:YES]; +} + +#pragma mark - Switch Child + +- (void)switchToIndex:(NSInteger)index animated:(BOOL)animated { + UIViewController *target = (index == 0) ? self.hotVC : self.rankVC; + if (!target) { + if (index == 0) { + self.hotVC = [HomeHotVC new]; + target = self.hotVC; + } else { + self.rankVC = [HomeRankVC new]; + target = self.rankVC; + } + } + + if (self.currentChild == target) { + // 已经是目标 + [self updateButtonStateForIndex:index animated:animated]; + return; + } + + // 移除当前 + if (self.currentChild) { + [self.currentChild willMoveToParentViewController:nil]; + [self.currentChild.view removeFromSuperview]; + [self.currentChild removeFromParentViewController]; + } + + // 添加目标 + [self addChildViewController:target]; + [self.containerView addSubview:target.view]; + target.view.backgroundColor = [UIColor colorWithWhite:0.98 alpha:1]; + [target.view mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.containerView); + }]; + [target didMoveToParentViewController:self]; + self.currentChild = target; + + [self updateButtonStateForIndex:index animated:animated]; +} + +- (void)updateButtonStateForIndex:(NSInteger)index animated:(BOOL)animated { + self.hotButton.selected = (index == 0); + self.rankButton.selected = (index == 1); + + UIButton *btn = (index == 0) ? self.hotButton : self.rankButton; + // 更新下划线位置 + [self.underlineView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.height.mas_equalTo(2); + make.bottom.equalTo(self.topBar.mas_bottom).offset(-2); + make.centerX.equalTo(btn); + make.width.mas_equalTo(24); + }]; + + if (animated) { + [UIView animateWithDuration:0.25 animations:^{ + [self.topBar layoutIfNeeded]; + }]; + } else { + [self.topBar layoutIfNeeded]; + } +} @end diff --git a/keyBoard/Class/Home/VC/HomeVC.m b/keyBoard/Class/Home/VC/HomeVC.m index 533205e..639b8d5 100644 --- a/keyBoard/Class/Home/VC/HomeVC.m +++ b/keyBoard/Class/Home/VC/HomeVC.m @@ -19,19 +19,22 @@ - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; + CGFloat topV = KBFit(500); + [self.view addSubview:self.headView]; + [self setupMas:topV]; // 创建sheetVC - HomeSheetVC *vc = [HomeSheetVC new]; - vc.minHeight = 300; + HomeSheetVC *vc = [[HomeSheetVC alloc] init]; + vc.minHeight = KB_SCREEN_HEIGHT - topV - 30; vc.topInset = 100; [self presentPanModal:vc]; } -- (void)setupMas{ +- (void)setupMas:(CGFloat)headViewTopV{ [self.headView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self.view); make.top.equalTo(self.view); - + make.height.mas_equalTo(headViewTopV); }]; }