问题
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: When pinning the frame of the UIScrollView
with the frameLayoutGuide
as we've always done, this breaks on iOS 12 when the scrollview is contained within another view (in my case in a child viewcontroller, not sure if that matters).
Setup: View setup, all views using autolayout:
UIView "scrollviewContainerView" [fixed/unchanging size]
|
\- UIScrollView "scrollView"
|
\ UIView "contentView"
Constraints that lead to Symptom:
- scrollview's
frameLayoutGuide
pinned to top/bottom/leading/trailing of scrollviewContainerView - contentView pinned to top/bottom/leading/trailing of scrollview's
contentLayoutGuide
- because this is a vertical scrollview: contentView's
widthAnchor
pinned to scrollView'sframeLayoutGuide
Symptom: On iOS 14, everything looks fine. On iOS 12 when scrolling, the size of the UIScrollView
itself changes, seemingly ignoring the frameLayoutGuide
constraints (even though they're still there: I couldn't see any difference when debugging the view hierarchy between iOS 12's resulting set of constraints and iOS 14). This makes things look very very broken.
回答1:
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.
来源:https://stackoverflow.com/questions/64556853/uiscrollview-ignores-framelayoutguide-constraints-on-ios-12-when-built-with-ios