如何在设备旋转后重新计算约束

Posted

技术标签:

【中文标题】如何在设备旋转后重新计算约束【英文标题】:How to recalculate constraints after device rotation 【发布时间】:2019-04-01 21:14:57 【问题描述】:

在我的表格视图的单元格中,我有包含视图的堆栈视图。我有堆栈视图的尾随约束。我根据要在堆栈视图中显示的视图数量来计算此尾随约束的值。

我们有什么方法可以重新计算这个尾随约束值并显示视图,而无需在设备旋转后重新加载viewWillTransition 中的表视图?

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) 
    super.viewWillTransition(to: size, with: coordinator)

    self.tableView.reloadData()

    // self.view.layoutIfNeeded() // This doesn't recalculate constraints

【问题讨论】:

目前发生了什么,试过self.view.layoutIfNeeded() 它不会重新计算约束值。它只能通过在旋转后重新加载表格视图来工作。 我添加了执行 self.view.layoutIfNeeded() 时发生的屏幕截图,您可以看到它不会重新计算约束。 【参考方案1】:

你应该实现这个方法

func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)

当设备转换到新视图(横向或纵向)时调用。在此您可以更改约束并调用

view.setNeedsLayout() 标记该布局需要在下一个 Runloop 中重新计算。

【讨论】:

【参考方案2】:

有几种方法可以做到这一点——在旋转(或改变尺寸)时重新计算尺寸可能不是最好的方法。

利用UIStackView 处理所有布局的一种方法是将“空白”视图添加到您的堆栈视图中。如果您设置仅显示 1 或 2 个“真实”视图,您还可以显示“空白”视图以填充这 3 个“列”。

因此,假设您可能会显示零实际视图的行,请保持原型不变,但是:

向堆栈视图添加 IBOutlet 引用 在awakeFromNib() 中,创建并添加3 个清除UIViews 到堆栈视图 跟踪数组中的真实视图和空白视图

现在,当您设置要显示的视图时,隐藏要显示的视图显示足够的空白视图以保持 3 个视图可见。

例子:

[1, 2] 隐藏view3 并显示blank1 [2] 隐藏 view1view3 并显示 blank1blank2

因此,您的堆栈中将始终有 3 个子视图,并且不需要计算...自动布局将使它们保持排列。

这是一个示例实现:

class ThreeColCell: UITableViewCell 

    @IBOutlet var mainStackView: UIStackView!

    @IBOutlet var view1: UIView!
    @IBOutlet var view2: UIView!
    @IBOutlet var view3: UIView!

    var arrayOfRealViews: [UIView] = [UIView]()
    var arrayOfBlankViews: [UIView] = [UIView]()

    var myData: [Int] = [Int]() 
        didSet 
            // hide all the views in the stack
            mainStackView.arrangedSubviews.forEach 
                $0.isHidden = true
            

            // show the specified "real" views
            myData.forEach  i in
                arrayOfRealViews[i - 1].isHidden = false
            

            // if fewer than 3 "real" views, show "blank" view(s)
            for i in 0..<(3 - myData.count) 
                arrayOfBlankViews[i].isHidden = false
            
        
    

    override func awakeFromNib() 
        super.awakeFromNib()
        commonInit()
    

    func commonInit() -> Void 
        // ordered array of views 1 to 3
        arrayOfRealViews = [view1, view2, view3]

        // add 3 "blank" views to the stack view
        //  and to array of blank views
        for _ in 0..<3 
            let v = UIView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .clear
            mainStackView.addArrangedSubview(v)
            arrayOfBlankViews.append(v)
        
    



class ThreeColTableViewController: UITableViewController 

    var theData = [
        [1, 2],
        [1, 2, 3],
        [1],
        [1, 2, 3],
        [2],
        [2, 3],
        [1, 2, 3],
        [3],
        [2, 3],
        [1, 2, 3],
    ]


    override func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return theData.count
    

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "ThreeColCell", for: indexPath) as! ThreeColCell
        cell.myData = theData[indexPath.row]
        return cell
    


导致纵向:

和风景:


编辑:

这是另一个示例,使用具有 .isDisabled1(以及 2 和 3)属性的结构数组:

struct Profile 

    var isDisabled1 = false
    var isDisabled2 = false
    var isDisabled3 = false



class ThreeColCell: UITableViewCell 

    @IBOutlet var mainStackView: UIStackView!

    @IBOutlet var view1: UIView!
    @IBOutlet var view2: UIView!
    @IBOutlet var view3: UIView!

    var arrayOfRealViews: [UIView] = [UIView]()
    var arrayOfBlankViews: [UIView] = [UIView]()

    var myProfile: Profile = Profile() 
        didSet 
            // hide all the views in the stack
            mainStackView.arrangedSubviews.forEach 
                $0.isHidden = true
            

            // I don't know how you have your button/label views set up, but here
            // you would set button titles and label texts based on myProfile properties

            // create a [1, 2, 3] array based on the .isDisabled# properties of the Profile object
            var a = [Int]()

            if !myProfile.isDisabled1 
                a.append(1)
            
            if !myProfile.isDisabled2 
                a.append(2)
            
            if !myProfile.isDisabled3 
                a.append(3)
            

            // you now have an array "a" that will be
            //  [1, 2, 3]    or
            //  [1, 2]       or
            //  [2]          or
            //  [2, 3]       etc

            // show the specified "real" views (arrays are Zero-based)
            a.forEach  i in
                arrayOfRealViews[i - 1].isHidden = false
            

            // pad stackview to 3 using "blank" view(s)
            // if 1 real view, show 2 blank views
            // if 2 real views, show 1 blank view
            // if 3 real views, don't show any blank views
            for i in 0..<(3 - a.count) 
                arrayOfBlankViews[i].isHidden = false
            

        
    

    override func awakeFromNib() 
        super.awakeFromNib()

        // ordered array of views 1 to 3
        arrayOfRealViews = [view1, view2, view3]

        // add 3 "blank" views to the stack view
        //  and to array of blank views
        for _ in 0..<3 
            let v = UIView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .clear
            mainStackView.addArrangedSubview(v)
            arrayOfBlankViews.append(v)
        
    



class ThreeColTableViewController: UITableViewController 

    var profilesArray: [Profile] = [Profile]()

    override func viewDidLoad() 
        super.viewDidLoad()

        // create a few Profiles
        // all 3 views are "enabled" by default

        var p = Profile()

        profilesArray.append(p)

        // Profile with views 2 and 3 disabled
        p = Profile()
        p.isDisabled2 = true
        p.isDisabled3 = true

        profilesArray.append(p)

        // Profile with view 3 disabled
        p = Profile()
        p.isDisabled3 = true

        profilesArray.append(p)

        // Profile with view 1 disabled
        p = Profile()
        p.isDisabled1 = true

        profilesArray.append(p)

        // Profile with views 1 and 2 disabled
        p = Profile()
        p.isDisabled1 = true
        p.isDisabled2 = true

        profilesArray.append(p)

    

    override func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return profilesArray.count
    

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "ThreeColCell", for: indexPath) as! ThreeColCell

        cell.myProfile = profilesArray[indexPath.row]

        return cell
    


【讨论】:

看起来像一个解决方案,但它太复杂了,我头晕目眩。我的数据不是 int 数组,而是结构数组,它有很多字段。 “结构数组,它有很多字段” --- 没有任何区别。据推测,您的数据结构中的某些内容指示视图是否应该可见,因此使用该值来显示/隐藏视图和“空白”视图。 @Davis - 我编辑了我的答案以包含另一个示例......这一次,使用“配置文件”对象数组而不是简单的 Ints 数组。

以上是关于如何在设备旋转后重新计算约束的主要内容,如果未能解决你的问题,请参考以下文章

设备旋转后动画点重新定位

如何计算两个约束段的旋转角度?

旋转设备后 safeAreaLayoutGuide 未更新

旋转后不采用 Swift 约束

屏幕旋转后如何在 UICollectionView 中跟踪单元格的移动

Swift:如何在设备旋转后刷新UICollectionView布局