Files
keyboard/keyBoard/Class/Home/V/KBPanModalView.m
2025-11-14 19:48:15 +08:00

507 lines
18 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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<HWPanModalIndicatorProtocol> *)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