屏幕旋转的编程约束(swift)

Posted

技术标签:

【中文标题】屏幕旋转的编程约束(swift)【英文标题】:Programmatic constraints with screen rotation (swift) 【发布时间】:2020-08-11 16:32:59 【问题描述】:

我有 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) 

我做错了什么???

【问题讨论】:

在更新约束之前,你必须先清除旧的。 你做错了很多事情,但首先......你认为“纵向方向”是什么?如果应用在 iPad 上并且用户使用 SplitView / SlideOver 进行多任务处理,则设备可能处于“横向”方向,但您的应用可能又高又窄。 @DonMag:到目前为止,我只为 iPhone 做这件事,为 iPad 整理想法以备后用……我做错了什么? (我对此很陌生......) @Niraj Solanki:我最初尝试通过设置 cv1.translatesAutoresizingMaskIntoConstraints = true 来做到这一点,然后再次设置为 false - 但这不起作用。您可能有以下解决方案... 【参考方案1】:

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

有多种方法可以处理这个问题……这里有一种方法。

创建一个“宽布局”约束数组 创建一个“窄布局”约束数组 创建一组“通用”约束 - 无论是窄“方向”还是宽“方向”,这些约束都适用 根据视图的大小激活/停用宽和窄约束

试试这个例子:

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
        )

    
    

【讨论】:

【参考方案2】:

参考:- 更新约束之前检查已应用的约束。如果已经有约束,请获取它们并更新其他约束。

例子:-

     //Height
        if let heightConstraint = cv1.constraints.first(where:  $0.firstAttribute == .height )
        
            heightConstraint.constant = safeAreaHeight
        
        else
        
            NSLayoutConstraint.activate([
                cv1.heightAnchor.constraint(equalToConstant: safeAreaHeight)
            ])
        
        
        //Width
        if let widthConstraint = cv1.constraints.first(where:  $0.firstAttribute == .width )
                  
                      widthConstraint.constant = safeAreaWidth / 2
                  
                  else
                  
                      NSLayoutConstraint.activate([
                          cv1.widthAnchor.constraint(equalToConstant: safeAreaWidth / 2)
                      ])
                  

【讨论】:

以上是关于屏幕旋转的编程约束(swift)的主要内容,如果未能解决你的问题,请参考以下文章

在屏幕旋转之前,自动布局约束不正确? [复制]

swift 在iOS上只旋转一个屏幕

Swift 2:仅在全屏视频上进行屏幕旋转

牛B的swift屏幕旋转经验终结者(OC统一思路)

暂时,以编程方式禁用 Qt 中的屏幕旋转

如何在快速屏幕旋转后获得视图的高度?