我有一个非常烦人的细节问题,我不知道如何使用 UIScrollView 正确解决。
我有一个基本的 UIViewController 类,其中有一个 scrollView 和 contentView 设置如下:
import UIKit
/*
Controller used where content will be scrollable once it extends past the size of the screen,
for example when the keyboard is shown.
This controller will automatically avoid the keyboard if needed.
For this to scroll properly you need to either create a constraint between the last child view
and the bottom of the contentView or constraint the contentView height with a low/medium priority.
When using this as the parent controller use the `contentView` property instead of `view`
when adding content.
*/
class ScrollableContentController: KeyboardAwareViewController {
private class ScrollView: UIScrollView {
convenience init() {
self.init(frame: .init())
translatesAutoresizingMaskIntoConstraints = false
keyboardDismissMode = .interactive
delaysContentTouches = false
}
override func touchesShouldCancel(in view: UIView) -> Bool { true }
}
let scrollView: UIScrollView = ScrollView()
let contentView = UIView()
override func loadView() {
super.loadView()
contentView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.addSubview(contentView)
NSLayoutConstraint.activate([
scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.frameLayoutGuide.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
contentView.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor),
])
scrollView.delegate = self
}
override func keyboardWillChangeFrame(_ frame: CGRect) {
if isTopViewController {
scrollView.contentInset.bottom = frame.height
}
}
}
到目前为止,我通过将最底部视图的底部约束到 contentView 的底部来使用此类。这将导致滚动视图仅在 contentSize 超过滚动视图的大小时才可滚动。然而,我最近开始对那些因为很小而无法反弹的视图感到恼火。例如,如果您浏览“系统设置”应用程序,您会注意到所有视图都会反弹,无论是否需要。也许这通常是使用 UICollectionView 或 UITableView 完成的,但我讨厌在视图数量较少且预定义的情况下使用这些视图,因此我坚持使用 UIScrollView 来代替。 我想我可以通过将 contentSize 设置为比我的 safeArea 高度至少高 0.5px 来模仿这种弹跳行为,如下所示:
let height = view.safeAreaLayoutGuide.layoutFrame.size.height.roundUpToNearestHalf()
contentView.snp.updateConstraints { make in
make.height.equalTo(max(section.maxY, height)).priority(500)
}
而且效果非常好!好吧几乎.. 非常烦人的问题是,当我向下滚动并且它弹回来时,它会弹回到初始位置,在我的手机上和我的情况下恰好是(0.0,-97.66666666666667)。我在scrollViewDidEndDecelerating 委托方法中获取该信息。 然而,当我向上滚动并向下弹回时,它将落在 (0.0, -97.33333333333333) 上。它很难看到,但会导致导航视图的底部边框永远不会完全消失,而且一旦看到它就真的很烦人。这个问题在系统设置应用程序中不存在,您会看到导航栏的底部边框完全淡出。
如何在不使用 Table 或 CollectionView 的情况下解决此问题?另外, contentOffset 为负值是否合理,或者我一开始就做的所有事情都完全错误?
将内容大小设置为略大于安全区域是确保始终有内容可滚动的巧妙方法。您的方法似乎是合理的,但精确反弹位置的问题可能与如何设置约束以及如何计算安全区域有关。 并确保您正确考虑了安全区域插图,尤其是在处理带有凹口或不同屏幕尺寸的设备时。