如何用Auto Layout实现UITableViewCell内视图的展开折叠(限宽50)
这需求我之前做类似的自定义滑动操作cell时踩过坑,用Auto Layout完全能优雅实现,核心是靠约束常量的动态变化+动画来驱动视图展开/折叠,不用手动算frame。下面给你分两种方案,一种是手动约束(灵活性更高),一种是用栈视图简化(代码更简洁),看你偏好:
核心思路
本质是让主视图(view1)左移让出空间,同时让右侧的view2、view3从宽度0逐渐增加到最大50,所有变化通过Auto Layout的约束来控制,动画只需要刷新布局即可。
方案一:手动设置约束(灵活性更高)
1. 视图初始化与基础约束
先把三个视图加到cell的contentView里,记得关闭自动转译约束:
// 在cell的初始化方法里(init(style:reuseIdentifier:) 或者 awakeFromNib) let view1 = UIView() // 主内容视图,比如放label、imageView等 let view2 = UIView() // 操作视图1,比如删除按钮 let view3 = UIView() // 操作视图2,比如收藏按钮 [view1, view2, view3].forEach { $0.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview($0) }
然后设置约束,重点是要记录需要动态修改的约束引用:
// 主视图view1的约束 NSLayoutConstraint.activate([ view1.leftAnchor.constraint(equalTo: contentView.leftAnchor), view1.topAnchor.constraint(equalTo: contentView.topAnchor), view1.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) ]) // 记录右约束,用来控制左移偏移 var view1RightConstraint: NSLayoutConstraint! view1RightConstraint = view1.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: 0) view1RightConstraint.isActive = true // view2的约束(放在最右侧) var view2WidthConstraint: NSLayoutConstraint! view2WidthConstraint = view2.widthAnchor.constraint(equalToConstant: 0) // 初始隐藏 NSLayoutConstraint.activate([ view2.rightAnchor.constraint(equalTo: contentView.rightAnchor), view2.topAnchor.constraint(equalTo: contentView.topAnchor), view2.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), view2.widthAnchor.constraint(lessThanOrEqualToConstant: 50) // 最大宽度50,优先级默认required ]) view2WidthConstraint.isActive = true // view3的约束(放在view2左侧) var view3WidthConstraint: NSLayoutConstraint! view3WidthConstraint = view3.widthAnchor.constraint(equalToConstant: 0) // 初始隐藏 NSLayoutConstraint.activate([ view3.rightAnchor.constraint(equalTo: view2.leftAnchor), view3.topAnchor.constraint(equalTo: contentView.topAnchor), view3.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), view3.widthAnchor.constraint(lessThanOrEqualToConstant: 50) // 最大宽度50 ]) view3WidthConstraint.isActive = true
2. 添加滑动手势与动画逻辑
给view1加左滑/右滑手势来触发展开/折叠:
// 在初始化方法里添加手势 let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(handleLeftSwipe)) swipeLeft.direction = .left view1.addGestureRecognizer(swipeLeft) let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(handleRightSwipe)) swipeRight.direction = .right view1.addGestureRecognizer(swipeRight) // 手势处理方法 @objc private func handleLeftSwipe() { // 展开:view1左移100(两个操作视图各50),同时把view2、view3宽度设为50 UIView.animate(withDuration: 0.3, curve: .easeInOut) { self.view1RightConstraint.constant = -100 self.view2WidthConstraint.constant = 50 self.view3WidthConstraint.constant = 50 self.contentView.layoutIfNeeded() // 触发布局更新,带动画 } } @objc private func handleRightSwipe() { // 折叠:恢复初始状态 UIView.animate(withDuration: 0.3, curve: .easeInOut) { self.view1RightConstraint.constant = 0 self.view2WidthConstraint.constant = 0 self.view3WidthConstraint.constant = 0 self.contentView.layoutIfNeeded() } }
3. 复用优化
别忘了在cell复用的时候重置状态,避免出现奇怪的布局:
override func prepareForReuse() { super.prepareForReuse() view1RightConstraint.constant = 0 view2WidthConstraint.constant = 0 view3WidthConstraint.constant = 0 }
方案二:用UIStackView简化布局(代码更简洁)
如果view2和view3是并排的操作按钮,用栈视图可以省去很多约束代码:
// 初始化视图 let view1 = UIView() let view2 = UIView() let view3 = UIView() // 用栈视图包裹view2和view3 let actionStack = UIStackView(arrangedSubviews: [view3, view2]) actionStack.translatesAutoresizingMaskIntoConstraints = false actionStack.axis = .horizontal actionStack.spacing = 0 // 按需求设置间距 // 添加到contentView [view1, actionStack].forEach { $0.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview($0) } // 设置约束 NSLayoutConstraint.activate([ // view1的约束 view1.leftAnchor.constraint(equalTo: contentView.leftAnchor), view1.topAnchor.constraint(equalTo: contentView.topAnchor), view1.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), // 栈视图的约束 actionStack.rightAnchor.constraint(equalTo: contentView.rightAnchor), actionStack.topAnchor.constraint(equalTo: contentView.topAnchor), actionStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), ]) // 记录需要修改的约束 var view1RightConstraint: NSLayoutConstraint! view1RightConstraint = view1.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: 0) view1RightConstraint.isActive = true var actionStackWidthConstraint: NSLayoutConstraint! actionStackWidthConstraint = actionStack.widthAnchor.constraint(equalToConstant: 0) // 初始隐藏 actionStackWidthConstraint.isActive = true // 最大宽度100(两个各50) actionStack.widthAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true
然后手势动画就更简单了,只需要修改两个约束:
@objc private func handleLeftSwipe() { UIView.animate(withDuration: 0.3) { self.view1RightConstraint.constant = -100 self.actionStackWidthConstraint.constant = 100 self.contentView.layoutIfNeeded() } } @objc private func handleRightSwipe() { UIView.animate(withDuration: 0.3) { self.view1RightConstraint.constant = 0 self.actionStackWidthConstraint.constant = 0 self.contentView.layoutIfNeeded() } }
关键注意点
- 约束优先级:确保最大宽度约束的优先级是
.required(默认就是),这样就算不小心设置超过50的宽度,也会被限制在50以内,避免布局冲突。 - 动画时机:一定要在
UIView.animate闭包里修改约束常量,然后调用layoutIfNeeded(),这样才会触发带动画的布局更新。 - 手势冲突:如果你的view1里有可滑动的控件(比如UIScrollView),要处理手势冲突,避免滑动操作被拦截。
内容的提问来源于stack exchange,提问作者Pursuing perfection
相关产品推荐
相关产品推荐

