You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UIStackView实现下拉菜单时动画斜向展开,如何改为垂直向上展开?

我正在使用UIStackView配合UITextField实现下拉菜单功能。点击文本框时,我希望通过动画让StackView垂直向上“展开”,但实际效果却是从左下角向右上角方向扩展。请问如何才能让它实现纯垂直向上的展开效果? 我怀疑问题可能是按钮宽度不等于StackView宽度导致的,但我已经将StackView的alignment属性设置为.fill,也添加了约束将StackView的widthAnchor与文本框的widthAnchor设为相等,因此不清楚还需要修改哪些配置。

以下是原代码:

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var textfield: UITextField!
    @IBOutlet weak var label: UILabel!
    
    var popup: UIStackView = {
        let selectionList = ["Apple",
                             "Grapefriut",
                             "Peach",
                             "Orange",
                             "Pear ",
                            ]
        let pop = UIStackView()
        pop.axis = .vertical
        pop.spacing = 0
        pop.distribution = .fillEqually
        pop.alignment = .fill
        pop.layer.borderWidth = 1
        pop.layer.borderColor = UIColor.darkGray.cgColor
        pop.layer.cornerRadius = 6
        pop.clipsToBounds = true

        for selection in selectionList {
            let button = UIButton()
            button.setTitle(selection, for: .normal)
            button.backgroundColor = #colorLiteral(red: 0.8202667832, green: 0.9491913915, blue: 1, alpha: 1)
            button.setTitleColor(.darkGray, for: .normal)
            button.titleLabel?.font = UIFont.systemFont(ofSize: 14)
            button.addTarget(self, action: #selector(selectionTapped), for: .touchUpInside)
            button.isHidden = true
            pop.addArrangedSubview(button)
        }

        return pop
    }()
        
    override func viewDidLoad() {
        super.viewDidLoad()
        textfield.delegate = self
        
        let imageView = UIImageView(image: UIImage(named: "downarrow20x20"))
        imageView.contentMode = .center
        imageView.frame = CGRect(x: 0, y: 0, width: imageView.image!.size.width + 20.0, height: imageView.image!.size.height)
        textfield.rightView = imageView
        textfield.rightViewMode = .always
        textfield.inputView = UIView() // disable keyboard from popping up
        
        view.addSubview(popup)
        
        popup.translatesAutoresizingMaskIntoConstraints = false
        popup.bottomAnchor.constraint(equalTo: textfield.bottomAnchor).isActive = true
        popup.widthAnchor.constraint(equalTo: textfield.widthAnchor).isActive = true
        popup.centerXAnchor.constraint(equalTo: textfield.centerXAnchor).isActive = true
    }
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
        showHidePopupContents(hide: false)
    }
    
    private func showHidePopupContents(hide: Bool) {
        UIView.animate(withDuration: 0.3) {
            self.popup.subviews.forEach { btn in
                btn.isHidden = hide
            }
        }
    }
    
    @objc func selectionTapped(_ button: UIButton) {
        if let buttonStr = button.title(for: .normal) {
            let str = buttonStr.trimmingCharacters(in: .whitespacesAndNewlines)
            label.text = "\(str) selected"
            textfield.text = str
        }

        // get focus out of textfield
        textfield.isEnabled = false
        textfield.isEnabled = true
        
        showHidePopupContents(hide: true)
    }
}

当前效果:
下拉菜单StackView从左下向右上展开


解决方案

问题出在动画逻辑上:直接修改StackView子视图的isHidden属性时,系统默认的隐式动画会同时计算宽度和高度的变化,导致出现斜向展开的问题。推荐用修改高度约束的方式实现稳定的垂直展开效果,修改步骤如下:

  1. 新增高度约束属性
    在类中新增成员变量存储popup的高度约束:
var popupHeightConstraint: NSLayoutConstraint!
  1. 初始化高度约束
    在viewDidLoad的约束配置部分,添加高度约束,初始值设为0:
popupHeightConstraint = popup.heightAnchor.constraint(equalToConstant: 0)
popupHeightConstraint.isActive = true
  1. 重写showHidePopupContents方法
    替换原来直接修改isHidden的逻辑,改为修改高度约束的数值,配合layoutIfNeeded触发动画:
private func showHidePopupContents(hide: Bool) {
    // 展开前先提前显示所有按钮,避免布局错乱
    self.popup.subviews.forEach { btn in
        btn.isHidden = false
    }
    UIView.animate(withDuration: 0.3) {
        // 单个按钮高度按44计算,可根据实际需求调整
        self.popupHeightConstraint.constant = hide ? 0 : CGFloat(self.popup.subviews.count) * 44
        self.view.layoutIfNeeded()
    } completion: { _ in
        // 收起动画结束后再隐藏按钮,避免误触透明区域
        if hide {
            self.popup.subviews.forEach { btn in
                btn.isHidden = true
            }
        }
    }
}

如果需要适配选项过多的场景,可额外给高度约束添加最大高度限制,避免展开后超出屏幕上边缘。


内容的提问来源于stack exchange,提问作者Kaplan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.08 14:30:03