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

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)
    ])
}

子控制器代码优化

你的子控制器代码存在几个影响尺寸计算的问题,需要调整:

  1. 在setupMenu中重复覆盖全局变量(section、cell等),导致布局约束混乱
  2. StackView的distribution设置可能导致尺寸计算不稳定
  3. 未给添加的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 11:00:57