// // KBPanModalView.m // keyBoard // // Created by Mac on 2025/11/6. // #import "KBPanModalView.h" #import "KBDirectionIndicatorView.h" // 子控制器 #import "HomeHotVC.h" #import "HomeRankVC.h" @interface KBPanModalView() @property (nonatomic, strong) KBDirectionIndicatorView *indicator; // 顶部切换按钮与指示条 @property (nonatomic, strong) UIView *topBar; @property (nonatomic, strong) UIView *bigWhiteContentView; @property (nonatomic, strong) UIView *secWhiteContentView; /// 皇冠👑 @property (nonatomic, strong) UIImageView *hgImageView; /// 人 @property (nonatomic, strong) UIImageView *personImageView; @property (nonatomic, strong) UIImageView *leftBgImageView; @property (nonatomic, strong) UIImageView *rightBgImageView; @property (nonatomic, strong) UIButton *hotButton; @property (nonatomic, strong) UIButton *rankButton; @property (nonatomic, strong) UIImageView *underlineImageView; // 选中下划线 /// 顶部拖拽方向图标(初始向上,展开至顶部后向下) @property (nonatomic, strong) UIImageView *dragArrowImageView; // 承载子控制器内容 // 注意:父类 HWPanModalContentView 内部已有名为 `containerView` 的属性(类型为 HWPanModalContainerView), // 不能同名,否则会导致运行时通过该属性访问到我们自己的 UIView,从而在库里调用 // `currentPresentationState` 时崩溃(- [UIView currentPresentationState] unrecognized selector)。 // 因此这里将承载子内容的视图命名为 contentContainerView。 @property (nonatomic, strong) UIView *contentContainerView; @property (nonatomic, strong) UIViewController *currentChild; @property (nonatomic, strong) HomeHotVC *hotVC; @property (nonatomic, strong) HomeRankVC *rankVC; @property (nonatomic, assign) NSInteger currentIndex; @end @implementation KBPanModalView - (instancetype)initWithFrame:(CGRect)frame{ if (self = [super initWithFrame:frame]) { // 该背景色会被 HWPanModal 用来设置容器的 _contentView 背景色 // 参考 Pods/HWPanModal/Sources/View/PanModal/HWPanModalContainerView.m: adjustPanContainerBackgroundColor // 想要改变外层白色底(截图中 _contentView)的颜色,只需改这里即可 // self.backgroundColor = [UIColor colorWithHex:0xE8FFF4]; // 柔和的绿色 self.backgroundColor = [UIColor clearColor]; // HWBackgroundConfig *config = [HWBackgroundConfig configWithBehavior:HWBackgroundBehaviorDefault]; // config.backgroundAlpha = 0; // [self.hw_dimmedView reloadConfig:config]; // 顶部按钮 + 容器 [self setupTopButtonsAndContainer]; // 默认展示“热门” [self switchToIndex:0 animated:NO]; } return self; } - (HWBackgroundConfig *)backgroundConfig { HWBackgroundConfig *config = [HWBackgroundConfig configWithBehavior:HWBackgroundBehaviorDefault]; config.backgroundAlpha = 0; config.blurTintColor = [UIColor clearColor]; return config; } - (UIView *)customIndicatorView { if (!_indicator) _indicator = [KBDirectionIndicatorView new]; return _indicator; } // 弹窗展示动画完成:根据当前展示状态设置一次箭头图 - (void)panModalTransitionDidFinish { [self kb_updateDragArrowForState:self.hw_presentationState]; } - (void)didChangeTransitionToState:(PresentationState)state { // 每次状态切换完成后刷新顶部箭头 [self kb_updateDragArrowForState:state]; } - (PanModalHeight)shortFormHeight { return PanModalHeightMake(PanModalHeightTypeMaxTopInset, self.minHeight); } - (PanModalHeight)longFormHeight { return PanModalHeightMake(PanModalHeightTypeMaxTopInset, self.topInset ?: 100); } - (PresentationState)originPresentationState { return PresentationStateShort; // 初始就以最小高度展示 } - (BOOL)anchorModalToLongForm { return YES; // 到 long 后不再继续往上拖 } - (BOOL)allowsPullDownWhenShortState { return NO; // 在 short 状态禁止继续往下拉(锁住最小高度) } - (CGFloat)topOffset{ return 0.001; } /// 允许时间穿透 - (BOOL)allowsTouchEventsPassingThroughTransitionView { return YES; } -(BOOL)shouldAutoSetPanScrollContentInset{ return NO; } - (UIScrollView *)panScrollable { if (self.currentIndex == 0) { return self.hotVC.tableView; } return self.rankVC.collectionView; } // 可选:完全不允许拖拽关闭(避免被拉到底消失) - (BOOL)allowsDragToDismiss { return NO; } // //- (BOOL)showDragIndicator{ // return NO; //} // // 点背景不关闭 - (BOOL)allowsTapBackgroundToDismiss { return NO; } // 出现时不做上推动画(瞬时到位)开了下拉动画有问题 //- (NSTimeInterval)transitionDuration { // return 0; //} //// 可选:关闭触觉反馈,避免出现时的轻微震动 //- (BOOL)isHapticFeedbackEnabled { // return NO; //} #pragma mark - UI - (void)setupTopButtonsAndContainer { // 添加最大的白色容器 [self addSubview:self.bigWhiteContentView]; [self.bigWhiteContentView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self).inset(0); make.top.equalTo(self).offset(40); make.bottom.equalTo(self); }]; [self.bigWhiteContentView addSubview:self.secWhiteContentView]; [self.secWhiteContentView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self).inset(0); make.top.equalTo(self.bigWhiteContentView).offset(40); make.bottom.equalTo(self.bigWhiteContentView); }]; [self.secWhiteContentView addSubview:self.leftBgImageView]; [self.secWhiteContentView addSubview:self.rightBgImageView]; [self.leftBgImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.top.equalTo(self.secWhiteContentView); make.height.mas_equalTo(466); }]; [self.rightBgImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.top.equalTo(self.secWhiteContentView); make.height.mas_equalTo(466); }]; // 顶部栏 [self.secWhiteContentView addSubview:self.topBar]; [self.secWhiteContentView addSubview:self.contentContainerView]; // 顶部拖拽方向箭头(放在顶部栏之上) [self addSubview:self.dragArrowImageView]; // 调整层级:将人物与皇冠图放到左右背景图的后面(z 轴更低) [self.secWhiteContentView insertSubview:self.personImageView belowSubview:self.leftBgImageView]; [self.secWhiteContentView insertSubview:self.hgImageView belowSubview:self.rightBgImageView]; // 固定各层 zPosition,避免插入顺序导致的偶发层级问题(背景 < 人/皇冠 < 磨砂 < 顶部栏) self.leftBgImageView.layer.zPosition = 0; self.rightBgImageView.layer.zPosition = 0; self.personImageView.layer.zPosition = 1; self.hgImageView.layer.zPosition = 1; self.topBar.layer.zPosition = 3; // [self.topBar addSubview:self.leftImageView]; // [self.topBar addSubview:self.rightImageView]; // 两个按钮 self.hotButton = [UIButton buttonWithType:UIButtonTypeCustom]; [self.hotButton setTitle:@"Ranking List" 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:@"Persona circle" 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.topBar addSubview:self.underlineImageView]; // Masonry 约束 CGFloat topPadding = 12; // 与顶部小指示器留点空间 [self.topBar mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self.secWhiteContentView); make.top.equalTo(self.secWhiteContentView).offset(topPadding); make.height.mas_equalTo(54); }]; // 将箭头放在顶部栏上方,居中显示 [self.dragArrowImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(self.topBar); make.bottom.equalTo(self.topBar.mas_top).offset(-20); make.width.mas_equalTo(18); make.height.mas_equalTo(18); }]; [self.hotButton mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(self.topBar).offset(3); make.left.equalTo(self.topBar); }]; [self.rankButton mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(self.hotButton); make.right.equalTo(self.topBar); make.left.equalTo(self.hotButton.mas_right); make.width.equalTo(self.hotButton); }]; // 初始先放在“热门”下方,宽度稍小于按钮文字 [self.underlineImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(5); make.bottom.equalTo(self.topBar.mas_bottom).offset(-14); make.centerX.equalTo(self.hotButton).offset(-0); make.width.mas_equalTo(78); }]; [self.contentContainerView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.topBar.mas_bottom).offset(8); make.left.right.equalTo(self.secWhiteContentView).inset(20); make.bottom.equalTo(self.secWhiteContentView); }]; [self.personImageView mas_makeConstraints:^(MASConstraintMaker *make) { // 仍按页面整体的左右边距定位,保持与改层级前的视觉一致 make.left.equalTo(self).offset(46); make.bottom.equalTo(self.topBar.mas_top).offset(11); make.width.mas_equalTo(53); make.height.mas_equalTo(81); }]; [self.hgImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(self).offset(-38); make.bottom.equalTo(self.topBar.mas_top).offset(16); make.width.mas_equalTo(82); make.height.mas_equalTo(66); }]; } #pragma mark - Action - (void)onTapTopButton:(UIButton *)sender { [self switchToIndex:sender.tag animated:false]; // [self hw_panModalSetNeedsLayoutUpdate]; } #pragma mark - Switch Child - (void)switchToIndex:(NSInteger)index animated:(BOOL)animated { self.currentIndex = index; 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.contentContainerView addSubview:target.view]; [target.view mas_makeConstraints:^(MASConstraintMaker *make) { make.left.top.right.equalTo(self.contentContainerView); make.bottom.equalTo(self.contentContainerView).offset(-KB_TABBAR_HEIGHT); }]; // [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.underlineImageView mas_remakeConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(5); make.bottom.equalTo(self.topBar.mas_bottom).offset(-15); make.centerX.equalTo(btn).offset(index == 0 ? -6 : -11); make.width.mas_equalTo(78); }]; if (index == 0) { self.leftBgImageView.hidden = NO; self.rightBgImageView.hidden = YES; self.personImageView.hidden = YES; self.hgImageView.hidden = NO; } else { self.leftBgImageView.hidden = YES; self.rightBgImageView.hidden = NO; self.personImageView.hidden = NO; self.hgImageView.hidden = YES; } // 确认层级:把可见的装饰图放到当前背景图后面 UIView *visibleBg = self.rightBgImageView.hidden ? self.leftBgImageView : self.rightBgImageView; if (!self.hgImageView.hidden) { [self.secWhiteContentView insertSubview:self.hgImageView belowSubview:visibleBg]; } if (!self.personImageView.hidden) { [self.secWhiteContentView insertSubview:self.personImageView belowSubview:visibleBg]; } if (animated) { [UIView animateWithDuration:0.25 animations:^{ [self.topBar layoutIfNeeded]; }]; } else { [self.topBar layoutIfNeeded]; } } - (UIView *)bigWhiteContentView{ if (!_bigWhiteContentView) { _bigWhiteContentView = [[UIView alloc] init]; _bigWhiteContentView.backgroundColor = [UIColor whiteColor]; _bigWhiteContentView.layer.cornerRadius = 40; // 不裁剪子视图,避免顶部装饰图越界部分被截断 _bigWhiteContentView.layer.masksToBounds = NO; // 等同于 clipsToBounds = NO } return _bigWhiteContentView; } - (UIView *)secWhiteContentView{ if (!_secWhiteContentView) { _secWhiteContentView = [[UIView alloc] init]; } return _secWhiteContentView; } - (UIImageView *)hgImageView{ if (!_hgImageView) { _hgImageView = [[UIImageView alloc] init]; _hgImageView.image = [UIImage imageNamed:@"home_hg_icon"]; _hgImageView.contentMode = UIViewContentModeScaleAspectFit; } return _hgImageView; } - (UIImageView *)personImageView{ if (!_personImageView) { _personImageView = [[UIImageView alloc] init]; _personImageView.image = [UIImage imageNamed:@"home_person_icon"]; _personImageView.contentMode = UIViewContentModeScaleAspectFit; } return _personImageView; } - (UIView *)topBar{ if (!_topBar) { _topBar = [[UIView alloc] init]; } return _topBar; } - (UIView *)contentContainerView{ if (!_contentContainerView) { _contentContainerView = [[UIView alloc] init]; } return _contentContainerView; } - (UIImageView *)underlineImageView{ if (!_underlineImageView) { _underlineImageView = [[UIImageView alloc] init]; _underlineImageView.image = [UIImage imageNamed:@"home_bar_underline"]; } return _underlineImageView; } // 顶部拖拽方向箭头 - (UIImageView *)dragArrowImageView { if (!_dragArrowImageView) { _dragArrowImageView = [[UIImageView alloc] init]; _dragArrowImageView.contentMode = UIViewContentModeScaleAspectFit; // 初始在 Short 状态,显示向上的箭头 _dragArrowImageView.image = [UIImage imageNamed:@"home_up_arrow"]; // 可点击:点击根据箭头方向在“最高/最低”之间切换 _dragArrowImageView.userInteractionEnabled = YES; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(kb_onTapDragArrow)]; [_dragArrowImageView addGestureRecognizer:tap]; } return _dragArrowImageView; } // 根据弹窗展示状态切换箭头图 - (void)kb_updateDragArrowForState:(PresentationState)state { // 视为“最高态”的状态:Medium 或 Long(当两者高度一致时,库可能标记为 Long) BOOL isAtTop = (state == PresentationStateMedium || state == PresentationStateLong); NSString *imgName = isAtTop ? @"home_down_arrow" : @"home_up_arrow"; UIImage *img = [UIImage imageNamed:imgName]; if (img && self.dragArrowImageView.image != img) { // 柔和切换,避免频繁拖拽时突兀 [UIView transitionWithView:self.dragArrowImageView duration:0.18 options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionBeginFromCurrentState animations:^{ self.dragArrowImageView.image = img; } completion:nil]; } } /// 点击顶部拖拽方向箭头: /// - 当箭头朝上(当前非最高态),点击切换到最高态(Medium)。 /// - 当箭头朝下(当前最高态),点击切换到最低态(Short)。 - (void)kb_onTapDragArrow { // 以当前展示状态为准做切换 PresentationState state = self.hw_presentationState; BOOL isAtTop = (state == PresentationStateMedium || state == PresentationStateLong); if (isAtTop) { // 已在最高态 -> 切到最低态 [self hw_panModalTransitionTo:PresentationStateShort]; } else { // 不在最高态 -> 切到最高态 [self hw_panModalTransitionTo:PresentationStateMedium]; } } - (UIImageView *)leftBgImageView{ if (!_leftBgImageView) { _leftBgImageView = [[UIImageView alloc] init]; _leftBgImageView.image = [UIImage imageNamed:@"home_left_bg"]; } return _leftBgImageView; } - (UIImageView *)rightBgImageView{ if (!_rightBgImageView) { _rightBgImageView = [[UIImageView alloc] init]; _rightBgImageView.image = [UIImage imageNamed:@"home_right_bg"]; } return _rightBgImageView; } @end