约束不会被停用

Constraint doesn't get deactivated

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

类似 -

所以,我根据方向停用然后激活所需的约束,但是当我改变旋转时,它给我冲突,就好像它没有停用那些,我指定要停用。这是我的完整代码。由于它独立于故事板,因此只需将视图控制器 class 分配给视图控制器即可查看效果。


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

谁能帮我检测一下我的缺陷。

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

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

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

同样,我没有看到任何重要的东西 - activating/deactivating 一个命名的(?)约束数组而不是创建数组,这样做的顺序,你正在使用的覆盖......那个跳出来的东西(对我来说)是看大小 classes。 (可能会找出初始大小 class 是多少?)

我目前正在记录 UISplitViewController 如何决定显示次要或紧凑 VC。事实证明,它至少在 五个 组中表现不同 - iPad(始终是次要),iPad 分屏(在所有 iPad 中都是紧凑的,除了iPad Pro 12.9 在横屏时半屏),iPhone 竖屏(总是紧凑),最后,iPhone 横屏(对于大多数来说紧凑,但对于 (iPhone 8 Plus , iPhone 11, iPhone 11 Pro Max, 和 iPhone 12 Pro Max.)

注意:iPhone 11 Pro、iPhone 12 和 iPhone 12 Pro 非常紧凑!我对此感到惊讶。 (我的下一步是直接测试尺寸 classes。)

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

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

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

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

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

夫妻问题...

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

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

看看这个:

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

通过这种方法,我们为我们想要的约束创建了两个变量 activate/deactivate:

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

然后,在 viewDidLoad() 中,我们将 centeredView 添加到视图,设置其“不变”约​​束 - centerX、centerY、aspect-ratio - 并创建两个 activate/deactivate 约束。

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