iOS开发:如何实现UIViewController的水平折叠展开效果?
Hey there! 作为iOS新手能想到这种状态切换的需求真的很棒,我来给你一步步拆解怎么实现这个功能~
核心思路
其实很简单:把两种状态的内容放在同一个被present出来的ViewController里,通过控制视图的显示/隐藏(搭配动画提升体验)来实现切换,再给两个箭头按钮绑定点击事件就行。
具体实现步骤(以代码布局为例,Storyboard/XIB思路一致)
1. 定义UI元素
首先在你的PresentedViewController里,分别创建两种状态的内容视图,以及两个切换按钮:
class PresentedViewController: UIViewController { // 状态1的内容视图(你可以往里加自己的控件,比如Label、Image等) private let state1View: UIView = { let view = UIView() view.backgroundColor = .lightGray view.translatesAutoresizingMaskIntoConstraints = false return view }() // 状态2的内容视图 private let state2View: UIView = { let view = UIView() view.backgroundColor = .systemBlue view.translatesAutoresizingMaskIntoConstraints = false view.isHidden = true // 初始默认隐藏状态2 return view }() // 切换到状态2的按钮(显示">") private let nextButton: UIButton = { let btn = UIButton(type: .system) btn.setTitle(">", for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 24, weight: .bold) btn.translatesAutoresizingMaskIntoConstraints = false return btn }() // 切换回状态1的按钮(显示"<") private let backButton: UIButton = { let btn = UIButton(type: .system) btn.setTitle("<", for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 24, weight: .bold) btn.translatesAutoresizingMaskIntoConstraints = false btn.isHidden = true // 初始默认隐藏返回按钮 return btn }() override func viewDidLoad() { super.viewDidLoad() setupUI() setupButtonActions() } }
2. 布局UI元素
接下来把这些视图添加到控制器里,设置合理的约束(这里我把状态视图铺满安全区上方,按钮放在底部中间,你可以根据自己的需求调整):
private func setupUI() { view.backgroundColor = .white // 添加所有子视图 view.addSubview(state1View) view.addSubview(state2View) view.addSubview(nextButton) view.addSubview(backButton) // 状态1视图的约束 NSLayoutConstraint.activate([ state1View.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), state1View.leadingAnchor.constraint(equalTo: view.leadingAnchor), state1View.trailingAnchor.constraint(equalTo: view.trailingAnchor), state1View.bottomAnchor.constraint(equalTo: nextButton.topAnchor, constant: -20) ]) // 状态2视图和状态1视图布局一致,保证切换时位置统一 NSLayoutConstraint.activate([ state2View.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), state2View.leadingAnchor.constraint(equalTo: view.leadingAnchor), state2View.trailingAnchor.constraint(equalTo: view.trailingAnchor), state2View.bottomAnchor.constraint(equalTo: backButton.topAnchor, constant: -20) ]) // 切换按钮的约束 NSLayoutConstraint.activate([ nextButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), nextButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), nextButton.widthAnchor.constraint(equalToConstant: 60), nextButton.heightAnchor.constraint(equalToConstant: 60) ]) NSLayoutConstraint.activate([ backButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), backButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), backButton.widthAnchor.constraint(equalToConstant: 60), backButton.heightAnchor.constraint(equalToConstant: 60) ]) }
3. 绑定按钮点击事件,实现状态切换
给两个按钮添加点击事件,通过动画平滑切换视图的显示状态(直接隐藏/显示会有点生硬,加个0.3秒的动画会更友好):
private func setupButtonActions() { nextButton.addTarget(self, action: #selector(switchToState2), for: .touchUpInside) backButton.addTarget(self, action: #selector(switchToState1), for: .touchUpInside) } @objc private func switchToState2() { // 先让状态1和前进按钮淡出 UIView.animate(withDuration: 0.3) { self.state1View.alpha = 0 self.nextButton.alpha = 0 } completion: { _ in // 完全隐藏后,显示状态2和返回按钮,再淡入 self.state1View.isHidden = true self.nextButton.isHidden = true self.state2View.isHidden = false self.backButton.isHidden = false UIView.animate(withDuration: 0.3) { self.state2View.alpha = 1 self.backButton.alpha = 1 } } } @objc private func switchToState1() { // 和上面逻辑反过来,先淡出状态2,再淡入状态1 UIView.animate(withDuration: 0.3) { self.state2View.alpha = 0 self.backButton.alpha = 0 } completion: { _ in self.state2View.isHidden = true self.backButton.isHidden = true self.state1View.isHidden = false self.nextButton.isHidden = false UIView.animate(withDuration: 0.3) { self.state1View.alpha = 1 self.nextButton.alpha = 1 } } }
额外提示
- 如果你用Storyboard/XIB开发:只需要拖两个View和两个Button,设置好初始隐藏状态,然后给按钮添加IBAction,在方法里写同样的切换逻辑就行。
- 如果两种状态的内容非常复杂,也可以考虑用子ViewController来管理每种状态,但对于新手来说,先从简单的视图切换入手更易理解。
内容的提问来源于stack exchange,提问作者lypw2009




