【问题标题】:Constraint doesn't get deactivated约束不会被停用
【发布时间】:2021-07-01 06:40:02
【问题描述】:

我正在以编程方式练习自动布局。我想在控制器中放置一个 UIView 居中,其宽度在纵向模式下为 4/5,但是当它进入横向模式时,我需要高度为超级视图高度的 4/5,而不是宽度.

类似的东西-

因此,我正在停用然后根据方向激活所需的约束,但是当我更改旋转时,它会给我带来冲突,就好像它没有停用那些我指定停用的约束一样。这是我的完整代码。由于它是独立于情节提要的,因此可以将视图控制器类分配给视图控制器并查看效果。


class MyViewController: UIViewController {
    
    var widthSizeClass = UIUserInterfaceSizeClass.unspecified
    
    var centeredView : UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.systemGreen
        return view
    }()
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.view.addSubview(centeredView)
        centeredView.translatesAutoresizingMaskIntoConstraints = false
    }
    
    override func viewWillLayoutSubviews(){
        super.viewWillLayoutSubviews()
        widthSizeClass = self.traitCollection.horizontalSizeClass
        addConstrainsToCenterView()
    }
    
    func addConstrainsToCenterView() {
        
        centeredView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor).isActive = true
        centeredView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor).isActive = true
       
        let compactWidthAnchor = centeredView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 4/5)
        let compactHeightAnchor = centeredView.heightAnchor.constraint(equalTo: centeredView.widthAnchor)
       
        
        let regularHeightAnchor = centeredView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 4/5)
        let regularWidthAnchor = centeredView.widthAnchor.constraint(equalTo: centeredView.heightAnchor)
        
        if widthSizeClass == .compact{
            NSLayoutConstraint.deactivate([regularWidthAnchor, regularHeightAnchor])
            NSLayoutConstraint.activate([compactWidthAnchor, compactHeightAnchor])
        }
        else{
            NSLayoutConstraint.deactivate([compactWidthAnchor, compactHeightAnchor])
            NSLayoutConstraint.activate([regularWidthAnchor, regularHeightAnchor])
        }
    }
}

谁能帮我找出我的缺陷。

【问题讨论】:

  • 在大多数情况下,这就是我成功做到这一点的方式。 (我实际上创建了数组而不是明确命名的约束。我不认为这是一个问题。)但是,如果使用大小类,我唯一​​会跳出来的事情。它们与不同的覆盖更紧密相关 - 如果您使用的是 iPad,除非将 iPad 放入分屏,否则它们不会改变。一个建议,一个可能需要小的重写?检查viewWillLayoutSubviews(或更好的viewDidLayoutSubviews)上的屏幕宽度/高度,并像您一样激活/停用。
  • 尺码等级不是这里的问题。如果我只是在 iPhone 12 pro max 上运行它,它应该可以正常工作而不会给我带来任何冲突。但是,这给了我冲突。您还可以通过将整个代码复制到视图控制器中来检查,因为它是视图控制器的整个代码。
  • 发布一些适合我的代码。也许会有所帮助?

标签: ios autolayout nslayoutconstraint programmatically


【解决方案1】:

几个问题...

1 - 许多 iPhone 型号具有 wC hR(纵向)和 wC hC(横向)尺寸类别。因此,如果您在这些设备上检查.horizontalSizeClass,它将始终是.compact。您可能想查看.verticalSizeClass

2 - 您拥有代码的方式,每次调用addConstrainsToCenterView() 时都会创建 约束。您没有激活/停用现有约束。

看看这个:

class MyViewController: UIViewController {
    
    var heightSizeClass = UIUserInterfaceSizeClass.unspecified

    var centeredView : UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.systemGreen
        return view
    }()

    // constraints to activate/deactivate
    var compactAnchor: NSLayoutConstraint!
    var regularAnchor: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.addSubview(centeredView)
        centeredView.translatesAutoresizingMaskIntoConstraints = false

        // centeredView is Always centerX and centerY
        centeredView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor).isActive = true
        centeredView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor).isActive = true
        
        // for a square (1:1 ratio) view, it doesn't matter whether we set
        //  height == width
        // or
        //  width == height
        // so we can set this Active all the time
        centeredView.heightAnchor.constraint(equalTo: centeredView.widthAnchor).isActive = true
        
        // create constraints to activate / deactivate
        
        // for regular height, set the width to 4/5ths the width of the view
        regularAnchor = centeredView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 4/5)

        // for compact height, set the height to 4/5ths the height of the view
        compactAnchor = centeredView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 4/5)
        
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        
        // use .verticalSizeClass
        heightSizeClass = self.traitCollection.verticalSizeClass

        updateCenterViewConstraints()
    }
    
    func updateCenterViewConstraints() {
        
        if heightSizeClass == .compact {
            // if height is compact
            regularAnchor.isActive = false
            compactAnchor.isActive = true
        }
        else{
            // height is regular
            compactAnchor.isActive = false
            regularAnchor.isActive = true
        }
    }
}

使用这种方法,我们为要激活/停用的约束创建两个变量:

    // constraints to activate/deactivate
    var compactAnchor: NSLayoutConstraint!
    var regularAnchor: NSLayoutConstraint!

然后,在viewDidLoad() 中,我们将centeredView 添加到视图中,设置其“不变”约​​束——centerX、centerY、纵横比——并创建两个激活/停用约束。

当我们改变大小类时,只需要处理var这两个约束即可。

【讨论】:

  • 感谢您指出我的错误的确切原因。
【解决方案2】:

可能不是答案,但为了配合我的评论,这是我成功使用的代码:

var p = [NSLayoutConstraint]()
var l = [NSLayoutConstraint]()

注意,pl 是数组,分别代表纵向和横向。

override func viewDidLoad() {
    super.viewDidLoad()
    setupConstraints()
}

这里没什么,只是展示加载视图时可以设置约束。

func setupConstraints() {        

    // for constraints that do not change, set `isActive = true`
    // for constants that do change, use `p.append` and `l.append`
    // for instance:

    btnLibrary.widthAnchor.constraint(equalToConstant: 100.0).isActive = true         

    p.append(btnLibrary.topAnchor.constraint(equalTo: safeAreaView.topAnchor, constant: 10))
    l.append(btnLibrary.bottomAnchor.constraint(equalTo: btnCamera.topAnchor, constant: -10))

再一次,这里没什么 - 看起来你正在这样做。这是我在您的视图控制器覆盖中看到的差异:

var initialOrientation = true
var isInPortrait = false

override func viewWillLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if initialOrientation {
        initialOrientation = false
        if view.frame.width > view.frame.height {
            isInPortrait = false
        } else {
            isInPortrait = true
        }
        view.setOrientation(p, l)
    } else {
        if view.orientationHasChanged(&isInPortrait) {
            view.setOrientation(p, l)
        }
    }
}

public func orientationHasChanged(_ isInPortrait:inout Bool) -> Bool {
    if self.frame.width > self.frame.height {
        if isInPortrait {
            isInPortrait = false
            return true
        }
    } else {
        if !isInPortrait {
            isInPortrait = true
            return true
        }
    }
    return false
}
public func setOrientation(_ p:[NSLayoutConstraint], _ l:[NSLayoutConstraint]) {
    NSLayoutConstraint.deactivate(l)
    NSLayoutConstraint.deactivate(p)
    if self.bounds.width > self.bounds.height {
        NSLayoutConstraint.activate(l)
    } else {
        NSLayoutConstraint.activate(p)
    }
}

其中一些可能对您的使用来说太过分了。但实际上,我不是检查大小类,而是检查边界以及检测初始方向。给我用?我实际上是在设置侧边栏或底栏。适用于所有 iPhone 和 iPad。

再次,我没有看到任何重大的 - 激活/停用一个命名的(?)约束数组而不是创建数组,这样做的顺序,你正在使用的覆盖......跳出来的一件事(对我来说)正在查看大小课程。 (可能找出初始尺寸等级是什么?)

我目前正在记录 UISplitViewController 如何决定显示辅助 VC 或紧凑型 VC。事实证明,它在至少 五个 组中的行为有所不同 - iPad(始终为次要)、iPad 分屏(除 iPad Pro 12.9 之外的所有 iPad 均在半屏时为紧凑型)、iPhone 纵向(始终为紧凑型) ,最后是 iPhone Landscape(大多数情况下是紧凑型,但对于(iPhone 8 Plus、iPhone 11、iPhone 11 Pro Max 和 iPhone 12 Pro Max)来说是次要的。)

注意:它非常适合 iPhone 11 Pro、iPhone 12 和 iPhone 12 Pro!我对此感到惊讶。 (接下来对我来说是直接测试尺寸等级。)

我的观点?也许您需要在屏幕边界处确定您想要的布局而不是尺寸等级。这比尺寸等级更受您的控制。不管怎样,祝你好运!

【讨论】:

    【解决方案3】:

    这很简单。每次布局发生,每次你说例如:

    let regularWidthAnchor = centeredView.widthAnchor.constraint(equalTo: centeredView.heightAnchor)
    NSLayoutConstraint.deactivate([regularWidthAnchor, regularHeightAnchor])
    

    您没有停用界面中现有的活动约束。您正在创建一个全新的约束,然后将其停用(这是毫无意义的,因为它从未激活过),然后将其丢弃。

    您只需创建一次这些约束并保留对它们的引用。

    【讨论】:

    猜你喜欢
    • 2021-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-22
    相关资源
    最近更新 更多