首页 > 解决方案 > 屏幕旋转的编程约束(swift)

问题描述

我有 2 个 UIView,cv1 和 cv2。在纵向时,我希望 cv1 占据屏幕的上半部分,而 cv2 占据下半部分。当旋转成横向时,我希望 cv1 占据左半部分,而 cv2 占据右半部分,如下所示:

在此处输入图像描述 在此处输入图像描述

这是我的设置方式(ChildView1ChildView2类只提供颜色和圆角):

class ViewController: UIViewController {

    let cv1 = ChildView1()
    let cv2 = ChildView2()
        
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        var safeAreaHeight: CGFloat {
            if #available(iOS 11.0, *) {
                return view.safeAreaLayoutGuide.layoutFrame.size.height
            }
            return view.bounds.height
        }
        //debugPrint("height = \(safeAreaHeight)")

        var safeAreaWidth: CGFloat {
            if #available(iOS 11.0, *) {
                return view.safeAreaLayoutGuide.layoutFrame.size.width
            }
            return view.bounds.width
        }
        //debugPrint("width = \(safeAreaWidth)")


        cv1.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        cv1.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
            
        if UIDevice.current.orientation == .portrait || UIDevice.current.orientation == .portraitUpsideDown {

            cv1.heightAnchor.constraint(equalToConstant: safeAreaHeight / 2).isActive = true
            cv1.widthAnchor.constraint(equalToConstant: safeAreaWidth).isActive = true

            cv2.topAnchor.constraint(equalTo: cv1.bottomAnchor, constant: 0).isActive = true
            cv2.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
            
            //debugPrint("Portrait: height = \(safeAreaHeight), width = \(safeAreaWidth)")

        } else if UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight {
            
            cv1.heightAnchor.constraint(equalToConstant: safeAreaHeight).isActive = true
            cv1.widthAnchor.constraint(equalToConstant: safeAreaWidth / 2).isActive = true
            
            cv2.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
            cv2.leftAnchor.constraint(equalTo: cv1.rightAnchor, constant: 0).isActive = true

            //debugPrint("Landscape: height = \(safeAreaHeight), width = \(safeAreaWidth)")
        }

        cv2.heightAnchor.constraint(equalTo: cv1.heightAnchor).isActive = true
        cv2.widthAnchor.constraint(equalTo: cv1.widthAnchor).isActive = true            
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()        
        addCV1()
        addCV2()
    }
    
    func addCV1() {
        self.view.addSubview(cv1)
        cv1.translatesAutoresizingMaskIntoConstraints = false
    }
    
    func addCV2() {
        self.view.addSubview(cv2)
        cv2.translatesAutoresizingMaskIntoConstraints = false
    }

    
}

如果我从纵向或横向开始,它看起来不错。但是当我旋转屏幕时,两个视图都会消失,并且每个约束都会收到以下错误消息:

2020-08-11 10:28:55.328063-0600 RotateScreenTesting[91471:4449618] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
"Portrait: height = 603.0, width = 375.0"
2020-08-11 10:29:15.046153-0600 RotateScreenTesting[91471:4449618] [LayoutConstraints] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x600003d74500 RotateScreenTesting.ChildView1:0x7fceba30a720.height == 301.5   (active)>",
    "<NSLayoutConstraint:0x600003d6d180 RotateScreenTesting.ChildView1:0x7fceba30a720.height == 343   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600003d6d180 RotateScreenTesting.ChildView1:0x7fceba30a720.height == 343   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
(lldb) 

我究竟做错了什么???

标签: iosswift

解决方案


重要的是要记住,我们应该使用尺寸布局,而不是纵向/横向。使用多任务滑动/拆分视图,让您的视图具有与设备本身不同的高度:宽度比并不罕见。

有多种方法可以处理这个问题......这是一种方法。

  • 创建一组“宽布局”约束
  • 创建一组“窄布局”约束
  • 创建一系列“常见”约束——这些约束适用于窄或宽“方向”
  • 根据视图的大小激活/停用宽和窄约束

试试这个例子:

class ChildView1: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        backgroundColor = .blue
        layer.cornerRadius = 16
    }
}

class ChildView2: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        backgroundColor = .red
        layer.cornerRadius = 16
    }
}

class SampleViewController: UIViewController {
    
    let cv1 = ChildView1()
    let cv2 = ChildView2()
    
    // array of constraints for "wide" layout
    var wideConstraints: [NSLayoutConstraint] = []

    // array of constraints for "narrow" layout
    var narrowConstraints: [NSLayoutConstraint] = []
    
    // just for clarity, array of constraints that apply for
    //  both wide and narrow layouts
    var commonConstraints: [NSLayoutConstraint] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        
        cv1.translatesAutoresizingMaskIntoConstraints = false
        cv2.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(cv1)
        view.addSubview(cv2)
        
        let g = view.safeAreaLayoutGuide
        
        commonConstraints = [
            // cv1 will always be constrained top and leading
            cv1.topAnchor.constraint(equalTo: g.topAnchor),
            cv1.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            
            // cv2 will always be constrained trailing and bottom
            cv2.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            cv2.bottomAnchor.constraint(equalTo: g.bottomAnchor),
        ]

        // when narrow, cv1 on top of cv2
        narrowConstraints = [
            // constrain cv1 trailing
            cv1.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            // constrain cv2 leading
            cv2.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            // constrain cv2 top to cv1 bottom
            cv2.topAnchor.constraint(equalTo: cv1.bottomAnchor),
            // make them equal heights
            cv2.heightAnchor.constraint(equalTo: cv1.heightAnchor),
        ]
        
        // when wide, cv1 side-by-side cv2
        wideConstraints = [
            // constrain cv1 bottom
            cv1.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            // constrain cv2 top
            cv2.topAnchor.constraint(equalTo: g.topAnchor),
            // constrain cv2 leading to cv1 trailing
            cv2.leadingAnchor.constraint(equalTo: cv1.trailingAnchor),
            // make them equal widths
            cv2.widthAnchor.constraint(equalTo: cv1.widthAnchor),
        ]
        
        // activate the commonConstraints
        NSLayoutConstraint.activate(commonConstraints)

        if view.frame.width > view.frame.height {
            // wider than tall, so "landscape"
            NSLayoutConstraint.activate(wideConstraints)
        } else {
            // taller than wide
            NSLayoutConstraint.activate(narrowConstraints)
        }
    }
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate(alongsideTransition: { _ in
            if size.width > size.height {
                // we're transitioning to wider than tall
                NSLayoutConstraint.deactivate(self.narrowConstraints)
                NSLayoutConstraint.activate(self.wideConstraints)
            } else {
                // we're transitioning to taller than wide
                NSLayoutConstraint.deactivate(self.wideConstraints)
                NSLayoutConstraint.activate(self.narrowConstraints)
            }
        }, completion: {
            _ in
            // if you want to do somwthing after the transition
        })

    }
    
}

推荐阅读