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子视图的isHidden属性时,系统默认的隐式动画会同时计算宽度和高度的变化,导致出现斜向展开的问题。推荐用修改高度约束的方式实现稳定的垂直展开效果,修改步骤如下:
- 新增高度约束属性
在类中新增成员变量存储popup的高度约束:
var popupHeightConstraint: NSLayoutConstraint!
- 初始化高度约束
在viewDidLoad的约束配置部分,添加高度约束,初始值设为0:
popupHeightConstraint = popup.heightAnchor.constraint(equalToConstant: 0) popupHeightConstraint.isActive = true
- 重写
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
相关产品推荐
相关产品推荐

