如何跨视图层级适配布局?Cell内菜单移至控制器视图的实现方案
更自适应的菜单视图布局实现方案
首先得给你点个赞,已经意识到硬编码坐标的问题了——这种方式在Cell高度变化、屏幕旋转或者有导航栏/状态栏调整时很容易出bug。下面我就给你拆解怎么用convertRect:toView:这类方法实现完全自适应的布局,同时把菜单视图移到控制器层面管理。
第一步:重构Cell的点击事件传递
先把Cell里的菜单视图代码删掉,改成通过Block/代理把点击事件传递给控制器,这样控制器能拿到触发点击的按钮和Cell,方便后续计算坐标:
// ZBMyProductsCell.h typedef void(^OperationButtonTapBlock)(ZBMyProductsCell *cell, UIButton *tapButton); @interface ZBMyProductsCell : UITableViewCell @property (nonatomic, copy) OperationButtonTapBlock operationTapBlock; @end // ZBMyProductsCell.m @implementation ZBMyProductsCell - (void)awakeFromNib { [super awakeFromNib]; // 移除原来的_operationMenu初始化代码 } - (IBAction)operationButtonClick:(UIButton *)sender { if (self.operationTapBlock) { self.operationTapBlock(self, sender); } } @end
第二步:控制器层面管理菜单视图
在控制器里初始化菜单视图,默认隐藏并添加到控制器的view上:
// 控制器类中 @property (nonatomic, strong) ProductsOperationMenu *operationMenuView; @property (nonatomic, weak) NSLayoutConstraint *menuLeadingConstraint; @property (nonatomic, weak) NSLayoutConstraint *menuBottomConstraint; - (void)viewDidLoad { [super viewDidLoad]; // 初始化菜单视图,用Auto Layout管理 self.operationMenuView = [[ProductsOperationMenu alloc] initWithFrame:CGRectZero]; self.operationMenuView.hidden = YES; self.operationMenuView.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.operationMenuView]; // 固定菜单的宽高 [self.operationMenuView.widthAnchor constraintEqualToConstant:205].active = YES; [self.operationMenuView.heightAnchor constraintEqualToConstant:60].active = YES; // 初始化动态约束(后续会修改) self.menuBottomConstraint = [self.operationMenuView.bottomAnchor constraintEqualToAnchor:nil constant:0]; self.menuLeadingConstraint = [self.operationMenuView.leadingAnchor constraintEqualToAnchor:nil constant:0]; self.menuBottomConstraint.active = YES; self.menuLeadingConstraint.active = YES; }
第三步:用坐标转换实现自适应布局
在控制器的Cell回调里,利用convertPoint:toView:/convertRect:toView:把按钮的坐标从Cell坐标系转换到控制器的view坐标系,再动态调整菜单的位置:
// 在tableView:cellForRowAtIndexPath:中绑定点击Block - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { ZBMyProductsCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ZBMyProductsCell" forIndexPath:indexPath]; __weak typeof(self) weakSelf = self; cell.operationTapBlock = ^(ZBMyProductsCell *cell, UIButton *tapButton) { [weakSelf handleOperationButtonTap:tapButton fromCell:cell]; }; return cell; } // 核心处理方法 - (void)handleOperationButtonTap:(UIButton *)button fromCell:(ZBMyProductsCell *)cell { BOOL shouldShowMenu = !self.operationMenuView.isHidden; // 隐藏菜单的逻辑 if (!shouldShowMenu) { [UIView animateWithDuration:0.25 animations:^{ // 把菜单滑回按钮右侧外 self.menuLeadingConstraint.constant += 205 + 10; [self.view layoutIfNeeded]; } completion:^(BOOL finished) { self.operationMenuView.hidden = YES; }]; return; } // 显示菜单的逻辑:转换按钮坐标到控制器view // 获取按钮底部在控制器view中的坐标 CGPoint buttonBottomPoint = [button convertPoint:CGPointMake(0, button.bounds.size.height) toView:self.view]; // 获取按钮左侧在控制器view中的x坐标 CGFloat buttonLeftX = [button convertPoint:CGPointZero toView:self.view].x; // 设置菜单初始位置(在按钮右侧外) self.operationMenuView.hidden = NO; self.menuBottomConstraint.constant = buttonBottomPoint.y; self.menuLeadingConstraint.constant = buttonLeftX + 10; [self.view layoutIfNeeded]; // 动画滑到按钮左侧 [UIView animateWithDuration:0.25 animations:^{ self.menuLeadingConstraint.constant = buttonLeftX - 205 - 10; [self.view layoutIfNeeded]; }]; }
为什么这种方式更自适应?
- 脱离硬编码依赖:不再依赖Cell高度、屏幕尺寸这类固定值,不管Cell怎么复用、滚动,坐标转换都会自动处理TableView的偏移量。
- 适配布局变化:屏幕旋转、导航栏隐藏/显示时,
convert方法会自动调整坐标,菜单位置始终和按钮对齐。 - Auto Layout友好:用约束动画替代frame动画,更符合iOS的布局规范,避免AutoresizingMask和Auto Layout冲突的问题。
如果需要点击菜单外区域隐藏菜单,还可以给控制器view添加一个全屏的点击手势,在手势回调里触发隐藏逻辑即可。
内容的提问来源于stack exchange,提问作者dengApro




