Swift:如何设置子视图控制器位置并获取其真实尺寸
在父控制器中指定子控制器视图位置并获取其真实尺寸的实现方案
问题描述
我有父视图控制器和子视图控制器,通过按钮触发展示子视图控制器。需求是展示前设置子视图的x、y位置,同时获取它的真实宽高尺寸——也就是在父控制器里指定位置,从子控制器拿到真实尺寸。
我试过下面这段代码:
child.view.translatesAutoresizingMaskIntoConstraints = false
用这个能获取子视图真实尺寸,但子视图的x、y位置会变成0,没法调整。
父控制器代码
func action() { let child = ChildVC() addChildViewController(child) view.addSubview(child.view) child.didMove(toParentViewController: self) child.view.frame = CGRect( x: button.frame.origin.x, y: button.frame.origin.y, width: ?, height: ? ) }
子控制器代码
var titleLabel: UILabel = { let label = UILabel() label.text = "title" label.translatesAutoresizingMaskIntoConstraints = false return label }() var imageView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "iOS2") imageView.translatesAutoresizingMaskIntoConstraints = false return imageView }() let backgroundView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() private lazy var menu: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 0 stackView.distribution = .fillProportionally stackView.translatesAutoresizingMaskIntoConstraints = false return stackView }() private lazy var section: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 16 stackView.alignment = .center stackView.distribution = .fillProportionally stackView.translatesAutoresizingMaskIntoConstraints = false return stackView }() private lazy var cell: UIStackView = { let stackView = UIStackView() stackView.axis = .horizontal stackView.spacing = 16 stackView.alignment = .fill stackView.distribution = .equalSpacing stackView.translatesAutoresizingMaskIntoConstraints = false return stackView }() let sectionNumber = 3 override func viewDidLoad() { super.viewDidLoad() setupView() setupConstraints() setupMenu() } func setupView() { view.addSubview(backgroundView) view.addSubview(menu) } func setupConstraints() { backgroundView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0).isActive = true backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0).isActive = true backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0).isActive = true backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0).isActive = true menu.topAnchor.constraint(equalTo: backgroundView.topAnchor, constant: 0.0).isActive = true menu.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor, constant: 0.0).isActive = true menu.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor, constant: 0.0).isActive = true menu.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor, constant: 0.0).isActive = true } func setupMenu() { for index in 0...sectionNumber { section = UIStackView() section.tag = index menu.addArrangedSubview(section) cell = UIStackView() cell.tag = index section.addArrangedSubview(cell) titleLabel = UILabel() imageView = UIImageView() cell.addArrangedSubview(titleLabel) cell.addArrangedSubview(imageView) cell.addSubview(button) if cell.tag == 0 { titleLabel.text = "text text text text" imageView.image = UIImage(named: "iOS2") } else if cell.tag == 1 { titleLabel.text = "text text text" imageView.image = UIImage(named: "iOS3") } else if cell.tag == 2 { titleLabel.text = "text" imageView.image = UIImage(named: "iOS1") }else { titleLabel.text = "text text" imageView.image = UIImage(named: "iOS2") } } }
解决方案
核心思路
要实现「父控制器指定位置 + 子控制器提供真实尺寸」,关键是先让子视图完成自身布局计算,拿到正确的尺寸后再应用到父视图的布局中。
方法一:手动触发布局计算,获取尺寸后设置Frame
在父控制器中,先让子视图完成布局计算,拿到真实尺寸后再设置目标位置:
func action() { let child = ChildVC() addChild(child) // 先将子视图临时添加到父视图(放在不可见位置),触发布局计算 child.view.frame = CGRect(x: 0, y: -CGFloat.greatestFiniteMagnitude, width: view.bounds.width, height: 0) view.addSubview(child.view) child.didMove(toParent: self) // 强制子视图完成布局,计算真实尺寸 child.view.layoutIfNeeded() let childSize = child.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) // 设置目标位置和正确的尺寸 child.view.frame = CGRect( x: button.frame.origin.x, y: button.frame.origin.y, width: childSize.width, height: childSize.height ) }
方法二:用自动布局约束同时控制位置和尺寸
如果偏好自动布局,可以直接给子视图设置位置约束,让子视图内部约束自动撑出宽高:
func action() { let child = ChildVC() addChild(child) child.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(child.view) child.didMove(toParent: self) // 仅设置位置约束,宽高由子视图内部约束决定 NSLayoutConstraint.activate([ child.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: button.frame.origin.x), child.view.topAnchor.constraint(equalTo: view.topAnchor, constant: button.frame.origin.y) ]) }
子控制器代码优化
你的子控制器代码存在几个影响尺寸计算的问题,需要调整:
- 在
setupMenu中重复覆盖全局变量(section、cell等),导致布局约束混乱 - StackView的
distribution设置可能导致尺寸计算不稳定 - 未给添加的
button设置约束,引发布局错误
优化后的setupMenu代码:
func setupMenu() { for index in 0...sectionNumber { // 为每个section创建独立实例 let section = UIStackView() section.axis = .vertical section.spacing = 16 section.alignment = .center section.distribution = .fill section.translatesAutoresizingMaskIntoConstraints = false section.tag = index menu.addArrangedSubview(section) // 为每个cell创建独立实例 let cell = UIStackView() cell.axis = .horizontal cell.spacing = 16 cell.alignment = .center cell.distribution = .equalSpacing cell.translatesAutoresizingMaskIntoConstraints = false cell.tag = index section.addArrangedSubview(cell) // 为每个cell创建独立的label和imageView let titleLabel = UILabel() titleLabel.translatesAutoresizingMaskIntoConstraints = false let imageView = UIImageView() imageView.translatesAutoresizingMaskIntoConstraints = false imageView.contentMode = .scaleAspectFit // 确保图片尺寸正常 cell.addArrangedSubview(titleLabel) cell.addArrangedSubview(imageView) // 添加button并设置约束 let button = UIButton(type: .system) button.translatesAutoresizingMaskIntoConstraints = false cell.addSubview(button) NSLayoutConstraint.activate([ button.centerXAnchor.constraint(equalTo: cell.centerXAnchor), button.centerYAnchor.constraint(equalTo: cell.centerYAnchor) ]) // 设置内容 switch index { case 0: titleLabel.text = "text text text text" imageView.image = UIImage(named: "iOS2") case 1: titleLabel.text = "text text text" imageView.image = UIImage(named: "iOS3") case 2: titleLabel.text = "text" imageView.image = UIImage(named: "iOS1") default: titleLabel.text = "text text" imageView.image = UIImage(named: "iOS2") } } }
调整后,子视图的内部约束能正确计算出自身尺寸,父控制器就能拿到准确的宽高值。
内容的提问来源于stack exchange,提问作者user1342352532
相关产品推荐
相关产品推荐

