You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动