UIScrollView ignores frameLayoutGuide constraints on iOS 12 when built with iOS 14.1 SDK

后端 未结 1 435
滥情空心
滥情空心 2021-01-29 06:15

I just wasted an hour with what looks like an iOS 14 SDK regression when running on iOS 12 so I thought i\'d post this in case it helps others.

Summary:

1条回答
  •  南方客
    南方客 (楼主)
    2021-01-29 06:31

    To help understand UIScrollView, Frame Layout Guide and Content Layout Guide...

    Frame Layout Guide and Content Layout Guide were introduced in iOS 11, in part to eliminate the ambiguity of frame vs content, as well as to allow adding "non-scrolling" elements to the scroll view.

    For each of the following examples, I'm adding and constraining the scroll view like this:

        let scrollView: UIScrollView = UIScrollView()
        
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(scrollView)
        
        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain scrollView 20-pts on each side
            scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
            
        ])
        
        // so we can see the scroll view's frame
        scrollView.backgroundColor = .red
        
    

    Now, consider the "old" way of adding content:

        // call a func to get a vertical stack view with 30 labels
        let stackView = createStackView()
        
        scrollView.addSubview(stackView)
        
        NSLayoutConstraint.activate([
    
            // old method
            // constrain stack view to scroll view itself
            //  this defines the "scrollable" content
            
            // stack view Top / Leading / Trailing / Bottom to scroll view
            stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0.0),
            stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 0.0),
            stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 0.0),
            stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0.0),
            
            // we want vertical scrolling, so we want our content to be only as wide as
            // the scroll view itself
            stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: 0.0),
            
        ])
    

    Fairly straightforward, although a little ambiguous. Am I making the stack view only as tall as the scroll view, by constraining its bottom anchor? That's what I'd expect if it's a subview of any other UIView!

    So, consider the "new" way of doing it:

        // call a func to get a vertical stack view with 30 labels
        let stackView = createStackView()
        
        scrollView.addSubview(stackView)
        
        let contentG = scrollView.contentLayoutGuide
        let frameG = scrollView.frameLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain stack view to scroll view's Content Layout Guide
            //  this defines the "scrollable" content
            
            // stack view Top / Leading / Trailing / Bottom to scroll view's Content Layout Guide
            stackView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 0.0),
            stackView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
            stackView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
            stackView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),
            
            // we want vertical scrolling, so we want our content to be only as wide as
            //  the scroll view's Frame Layout Guide
            stackView.widthAnchor.constraint(equalTo: frameG.widthAnchor, constant: 0.0),
            
        ])
        
    

    Now, it's very clear that I am using the stack view to define the "scrollable content" by constraining it to the .contentLayoutGuide and using the scroll view's actual frame (its .frameLayoutGuide) to define the stack view's width.

    In addition, suppose I want a UI element - such as an image view - to be inside the scroll view, but I don't want it to scroll? The "old way" would require adding the image view as a sibling view overlaid on top of the scroll view.

    But now, by using .frameLayoutGuide, I can add it as a "non-scrolling" element inside the scroll view:

        guard let img = UIImage(named: "scrollSample") else {
            fatalError("could not load image")
        }
        let imgView = UIImageView(image: img)
        imgView.translatesAutoresizingMaskIntoConstraints = false
        
        scrollView.addSubview(imgView)
        
        NSLayoutConstraint.activate([
            
            // constrain image view to the Frame Layout Guide
            //  at top-right, with 20-pts Top / Trailing "padding"
            imgView.topAnchor.constraint(equalTo: frameG.topAnchor, constant: 20.0),
            imgView.trailingAnchor.constraint(equalTo: frameG.trailingAnchor, constant: -20.0),
            // 120 x 120 pts
            imgView.widthAnchor.constraint(equalToConstant: 120.0),
            imgView.heightAnchor.constraint(equalToConstant: 120.0),
            
        ])
        
    

    Now, any elements I constrain to the scrollView's .contentLayoutGuide will scroll, but the image view constrained to the scrollView's .frameLayoutGuide will remain in place.

    0 讨论(0)
提交回复
热议问题