如何在 swift 中滚动 up/down 的同时 hide/show 查看
How to hide/show view simultaneously while scroll up/down in swift
像这样的故事板设计层次结构design hierarchy
这里如果我向上滚动我只需要隐藏 topView 并且需要在顶部显示 secondView 并且滚动应该从 secondView 的底部执行如果我向下滚动也需要显示 topView
代码:使用此代码,在滚动时 topView 立即 hiding/showing up/down.. 但是当我向上滚动时,topView 应该在向上滚动时明显(同时)隐藏 如何实现。请指导
class ThirdVC: UIViewController, UNUserNotificationCenterDelegate, UIScrollViewDelegate {
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var topView: UIView!
@IBOutlet weak var topviewHeight: NSLayoutConstraint!
@IBOutlet weak var secondViewHeight: NSLayoutConstraint!
@IBOutlet weak var secondViewTopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
UNUserNotificationCenter.current().delegate = self
scrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > 0{
topviewHeight.constant = 0
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
}else{
topviewHeight.constant = 100
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
}
}
}
o/p:
初始o/p 向上滚动前
initial o/p before scroll-up screen
向上滚动后:它会立即隐藏,我需要它应该随着向上滚动而隐藏
after scroll-up screen
编辑: 我需要当滚动 secondView 顶部到达 firstView 顶部然后 firstView 必须隐藏
那么如何计算才能达到呢?是否可以使用 secodeView top constarint 值更改?那怎么计算呢?请指导
此处附上必填o/p录屏
想法是 lock
如果动画已经在进行中,则阻止动画触发。
最简单的方法是使用 bool
来跟踪动画的状态
首先,使用一些变量来帮助您跟踪动画和顶视图的状态
// Persist the top view height constraint
var topViewHeightConstraint: NSLayoutConstraint?
// Original height of the top view
var viewHeight: CGFloat = 100
// Keep track of the
private var isAnimationInProgress = false
然后在执行动画时使用这些变量
extension ScrollViewAnimateVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Check if the animation is locked or not
if !isAnimationInProgress {
guard let topViewHeightConstraint = topViewHeightConstraint
else { return }
// Check if an animation is required
if scrollView.contentOffset.y > .zero &&
topViewHeightConstraint.constant > .zero {
topViewHeightConstraint.constant = .zero
animateTopViewHeight()
}
else if scrollView.contentOffset.y <= .zero
&& topViewHeightConstraint.constant <= .zero {
topViewHeightConstraint.constant = viewHeight
animateTopViewHeight()
}
}
}
// Animate the top view
private func animateTopViewHeight() {
// Lock the animation functionality
isAnimationInProgress = true
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
} completion: { [weak self] (_) in
// Unlock the animation functionality
self?.isAnimationInProgress = false
}
}
}
这会让你像这样更顺畅
根据 OP 评论更新
i am trying to use 2nd view(greenview) top constraint value to change according to scroll-up and down... but here i am unable to calculate correct values to hide and show
如果你想这样做,你真的不需要动画块,你可以继续降低顶部视图的高度,直到它变为 0。
添加这些
// Stores the original offset of the scroll view
var previousOffset: CGPoint?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
previousOffset = scrollView.contentOffset
}
然后更新scrollViewDidScroll
extension ScrollViewAnimateVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let topViewHeightConstraint = topViewHeightConstraint
else { return }
let currentOffset = scrollView.contentOffset
if let startOffset = previousOffset {
// Get the distance scrolled
let delta = abs((startOffset.y - currentOffset.y))
if currentOffset.y > startOffset.y,
currentOffset.y > .zero {
// Scrolling down
// Set the new height based on the amount scrolled
var newHeight = topViewHeightConstraint.constant - delta
// Make sure we do not go below 0
if newHeight < .zero {
newHeight = .zero
}
topViewHeightConstraint.constant
= newHeight
}
else if currentOffset.y < startOffset.y,
currentOffset.y <= viewHeight {
// Scrolling up
var newHeight = topViewHeightConstraint.constant + delta
// Make sure we do not go above the max height
if newHeight > viewHeight {
newHeight = viewHeight
}
topViewHeightConstraint.constant
= newHeight
}
// Update the previous offset
previousOffset = scrollView.contentOffset
self.view.layoutIfNeeded()
}
}
}
这会给你这样的东西:
假设您真的不需要隐藏 黄色视图 - 您只需要用绿色视图覆盖它...
这只能通过约束来完成——不需要任何 scrollViewDidScroll
代码。
我们要做的是将黄色视图限制在滚动视图的框架布局指南中,这样它就不会移动了。
然后我们将绿色视图的 Top greater-than-or-equal 约束到 Frame Layout Guide,并将其约束到具有 less-than-required 优先级的黄色视图底部。
然后我们将绿色视图的底部约束到红色视图的顶部,这样当我们滚动时红色视图将“向上推/向下拉”。不过,我们将再次使用 less-than-required 优先级,以便红色视图可以在下方向上滑动。
最后,我们将红色视图约束到滚动视图的内容布局指南以控制可滚动区域。由于黄色和绿色视图的高度均为 100 磅,因此我们将红色视图的顶部与内容指南的顶部相距 200 磅。
这是一个完整的示例(不需要 @IBOutlet
连接):
class ExampleVC: UIViewController {
let yellowView: UIView = {
let v = UIView()
v.backgroundColor = .systemYellow
return v
}()
let greenView: UIView = {
let v = UIView()
v.backgroundColor = .green
return v
}()
let redView: UIView = {
let v = UIView()
v.backgroundColor = .systemRed
return v
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.backgroundColor = .systemBlue
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// create a vertical stack view
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 16
// let's add some labels to the stack view
// so we have something to scroll
(1...30).forEach { n in
let v = UILabel()
v.backgroundColor = .yellow
v.text = "Label \(n)"
v.textAlignment = .center
stack.addArrangedSubview(v)
}
// add the stack view to the red view
redView.addSubview(stack)
// add these views to scroll view in this order
[yellowView, redView, greenView].forEach { v in
scrollView.addSubview(v)
}
// add scroll view to view
view.addSubview(scrollView)
// they will all use auto-layout
[stack, yellowView, redView, greenView, scrollView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
// always respect safe area
let safeG = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scroll view to safe area
scrollView.topAnchor.constraint(equalTo: safeG.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor),
// we need yellow view to
// fill width of scroll view FRAME
// height: 100-pts
// "stick" to top of scroll view FRAME
yellowView.leadingAnchor.constraint(equalTo: frameG.leadingAnchor),
yellowView.trailingAnchor.constraint(equalTo: frameG.trailingAnchor),
yellowView.heightAnchor.constraint(equalToConstant: 100.0),
yellowView.topAnchor.constraint(equalTo: frameG.topAnchor),
// we need green view to
// fill width of scroll view FRAME
// height: 100-pts
// start at bottom of yellow view
// "stick" to top of scroll view FRAME when scrolled up
greenView.leadingAnchor.constraint(equalTo: frameG.leadingAnchor),
// we'll use a constant of -40 here to leave a "gap" on the right, so it's
// easy to see what's happening...
greenView.trailingAnchor.constraint(equalTo: frameG.trailingAnchor, constant: -40),
greenView.heightAnchor.constraint(equalToConstant: 100.0),
greenView.topAnchor.constraint(greaterThanOrEqualTo: frameG.topAnchor),
// we need red view to
// fill width of scroll view FRAME
// dynamic height (determined by its contents - the stack view)
// start at bottom of green view
// "push / pull" green view when scrolled
// go under green view when green view is at top
// red view will be controlling the scrollable area
redView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
redView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
redView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),
redView.widthAnchor.constraint(equalTo: frameG.widthAnchor),
// let's inset the stack view 16-pts on all 4 sides
stack.topAnchor.constraint(equalTo: redView.topAnchor, constant: 16.0),
stack.leadingAnchor.constraint(equalTo: redView.leadingAnchor, constant: 16.0),
stack.trailingAnchor.constraint(equalTo: redView.trailingAnchor, constant: -16.0),
stack.bottomAnchor.constraint(equalTo: redView.bottomAnchor, constant: -16.0),
])
var c: NSLayoutConstraint!
// these constraints need Priority adjustments
// keep green view above red view, until green view is at top
c = redView.topAnchor.constraint(equalTo: greenView.bottomAnchor)
c.priority = .defaultHigh
c.isActive = true
// since yellow and green view Heights are constant 100-pts each
c = redView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 200.0)
c.isActive = true
}
}
外观如下 - 我将绿色视图设置为比全宽窄 40 磅,以便于查看正在发生的情况:
现在,如果您确实想要真正隐藏黄色视图,而不是仅仅覆盖它,请添加此扩展名:
extension ExampleVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
yellowView.isHidden = scrollView.contentOffset.y >= 100
}
}
并将其添加到视图控制器:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
scrollView.delegate = self
}
我们在 viewDidAppear
中这样做是为了避免在 viewDidLoad
中设置委托时发生错误的帧定位
编辑
如果您希望 yellowView 在被覆盖/显示时“淡出 in/out”,请使用:
extension ExampleVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// set yellowView alpha to the percentage that it is covered
yellowView.alpha = (100.0 - min(100.0, scrollView.contentOffset.y)) / 100.0
}
}
像这样的故事板设计层次结构design hierarchy
这里如果我向上滚动我只需要隐藏 topView 并且需要在顶部显示 secondView 并且滚动应该从 secondView 的底部执行如果我向下滚动也需要显示 topView
代码:使用此代码,在滚动时 topView 立即 hiding/showing up/down.. 但是当我向上滚动时,topView 应该在向上滚动时明显(同时)隐藏 如何实现。请指导
class ThirdVC: UIViewController, UNUserNotificationCenterDelegate, UIScrollViewDelegate {
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var topView: UIView!
@IBOutlet weak var topviewHeight: NSLayoutConstraint!
@IBOutlet weak var secondViewHeight: NSLayoutConstraint!
@IBOutlet weak var secondViewTopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
UNUserNotificationCenter.current().delegate = self
scrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > 0{
topviewHeight.constant = 0
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
}else{
topviewHeight.constant = 100
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
}
}
}
o/p:
初始o/p 向上滚动前
initial o/p before scroll-up screen
向上滚动后:它会立即隐藏,我需要它应该随着向上滚动而隐藏
after scroll-up screen
编辑: 我需要当滚动 secondView 顶部到达 firstView 顶部然后 firstView 必须隐藏
那么如何计算才能达到呢?是否可以使用 secodeView top constarint 值更改?那怎么计算呢?请指导
此处附上必填o/p录屏
想法是 lock
如果动画已经在进行中,则阻止动画触发。
最简单的方法是使用 bool
来跟踪动画的状态
首先,使用一些变量来帮助您跟踪动画和顶视图的状态
// Persist the top view height constraint
var topViewHeightConstraint: NSLayoutConstraint?
// Original height of the top view
var viewHeight: CGFloat = 100
// Keep track of the
private var isAnimationInProgress = false
然后在执行动画时使用这些变量
extension ScrollViewAnimateVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Check if the animation is locked or not
if !isAnimationInProgress {
guard let topViewHeightConstraint = topViewHeightConstraint
else { return }
// Check if an animation is required
if scrollView.contentOffset.y > .zero &&
topViewHeightConstraint.constant > .zero {
topViewHeightConstraint.constant = .zero
animateTopViewHeight()
}
else if scrollView.contentOffset.y <= .zero
&& topViewHeightConstraint.constant <= .zero {
topViewHeightConstraint.constant = viewHeight
animateTopViewHeight()
}
}
}
// Animate the top view
private func animateTopViewHeight() {
// Lock the animation functionality
isAnimationInProgress = true
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
} completion: { [weak self] (_) in
// Unlock the animation functionality
self?.isAnimationInProgress = false
}
}
}
这会让你像这样更顺畅
根据 OP 评论更新
i am trying to use 2nd view(greenview) top constraint value to change according to scroll-up and down... but here i am unable to calculate correct values to hide and show
如果你想这样做,你真的不需要动画块,你可以继续降低顶部视图的高度,直到它变为 0。
添加这些
// Stores the original offset of the scroll view
var previousOffset: CGPoint?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
previousOffset = scrollView.contentOffset
}
然后更新scrollViewDidScroll
extension ScrollViewAnimateVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let topViewHeightConstraint = topViewHeightConstraint
else { return }
let currentOffset = scrollView.contentOffset
if let startOffset = previousOffset {
// Get the distance scrolled
let delta = abs((startOffset.y - currentOffset.y))
if currentOffset.y > startOffset.y,
currentOffset.y > .zero {
// Scrolling down
// Set the new height based on the amount scrolled
var newHeight = topViewHeightConstraint.constant - delta
// Make sure we do not go below 0
if newHeight < .zero {
newHeight = .zero
}
topViewHeightConstraint.constant
= newHeight
}
else if currentOffset.y < startOffset.y,
currentOffset.y <= viewHeight {
// Scrolling up
var newHeight = topViewHeightConstraint.constant + delta
// Make sure we do not go above the max height
if newHeight > viewHeight {
newHeight = viewHeight
}
topViewHeightConstraint.constant
= newHeight
}
// Update the previous offset
previousOffset = scrollView.contentOffset
self.view.layoutIfNeeded()
}
}
}
这会给你这样的东西:
假设您真的不需要隐藏 黄色视图 - 您只需要用绿色视图覆盖它...
这只能通过约束来完成——不需要任何 scrollViewDidScroll
代码。
我们要做的是将黄色视图限制在滚动视图的框架布局指南中,这样它就不会移动了。
然后我们将绿色视图的 Top greater-than-or-equal 约束到 Frame Layout Guide,并将其约束到具有 less-than-required 优先级的黄色视图底部。
然后我们将绿色视图的底部约束到红色视图的顶部,这样当我们滚动时红色视图将“向上推/向下拉”。不过,我们将再次使用 less-than-required 优先级,以便红色视图可以在下方向上滑动。
最后,我们将红色视图约束到滚动视图的内容布局指南以控制可滚动区域。由于黄色和绿色视图的高度均为 100 磅,因此我们将红色视图的顶部与内容指南的顶部相距 200 磅。
这是一个完整的示例(不需要 @IBOutlet
连接):
class ExampleVC: UIViewController {
let yellowView: UIView = {
let v = UIView()
v.backgroundColor = .systemYellow
return v
}()
let greenView: UIView = {
let v = UIView()
v.backgroundColor = .green
return v
}()
let redView: UIView = {
let v = UIView()
v.backgroundColor = .systemRed
return v
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.backgroundColor = .systemBlue
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// create a vertical stack view
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 16
// let's add some labels to the stack view
// so we have something to scroll
(1...30).forEach { n in
let v = UILabel()
v.backgroundColor = .yellow
v.text = "Label \(n)"
v.textAlignment = .center
stack.addArrangedSubview(v)
}
// add the stack view to the red view
redView.addSubview(stack)
// add these views to scroll view in this order
[yellowView, redView, greenView].forEach { v in
scrollView.addSubview(v)
}
// add scroll view to view
view.addSubview(scrollView)
// they will all use auto-layout
[stack, yellowView, redView, greenView, scrollView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
// always respect safe area
let safeG = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scroll view to safe area
scrollView.topAnchor.constraint(equalTo: safeG.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor),
// we need yellow view to
// fill width of scroll view FRAME
// height: 100-pts
// "stick" to top of scroll view FRAME
yellowView.leadingAnchor.constraint(equalTo: frameG.leadingAnchor),
yellowView.trailingAnchor.constraint(equalTo: frameG.trailingAnchor),
yellowView.heightAnchor.constraint(equalToConstant: 100.0),
yellowView.topAnchor.constraint(equalTo: frameG.topAnchor),
// we need green view to
// fill width of scroll view FRAME
// height: 100-pts
// start at bottom of yellow view
// "stick" to top of scroll view FRAME when scrolled up
greenView.leadingAnchor.constraint(equalTo: frameG.leadingAnchor),
// we'll use a constant of -40 here to leave a "gap" on the right, so it's
// easy to see what's happening...
greenView.trailingAnchor.constraint(equalTo: frameG.trailingAnchor, constant: -40),
greenView.heightAnchor.constraint(equalToConstant: 100.0),
greenView.topAnchor.constraint(greaterThanOrEqualTo: frameG.topAnchor),
// we need red view to
// fill width of scroll view FRAME
// dynamic height (determined by its contents - the stack view)
// start at bottom of green view
// "push / pull" green view when scrolled
// go under green view when green view is at top
// red view will be controlling the scrollable area
redView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
redView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
redView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),
redView.widthAnchor.constraint(equalTo: frameG.widthAnchor),
// let's inset the stack view 16-pts on all 4 sides
stack.topAnchor.constraint(equalTo: redView.topAnchor, constant: 16.0),
stack.leadingAnchor.constraint(equalTo: redView.leadingAnchor, constant: 16.0),
stack.trailingAnchor.constraint(equalTo: redView.trailingAnchor, constant: -16.0),
stack.bottomAnchor.constraint(equalTo: redView.bottomAnchor, constant: -16.0),
])
var c: NSLayoutConstraint!
// these constraints need Priority adjustments
// keep green view above red view, until green view is at top
c = redView.topAnchor.constraint(equalTo: greenView.bottomAnchor)
c.priority = .defaultHigh
c.isActive = true
// since yellow and green view Heights are constant 100-pts each
c = redView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 200.0)
c.isActive = true
}
}
外观如下 - 我将绿色视图设置为比全宽窄 40 磅,以便于查看正在发生的情况:
现在,如果您确实想要真正隐藏黄色视图,而不是仅仅覆盖它,请添加此扩展名:
extension ExampleVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
yellowView.isHidden = scrollView.contentOffset.y >= 100
}
}
并将其添加到视图控制器:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
scrollView.delegate = self
}
我们在 viewDidAppear
中这样做是为了避免在 viewDidLoad
编辑
如果您希望 yellowView 在被覆盖/显示时“淡出 in/out”,请使用:
extension ExampleVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// set yellowView alpha to the percentage that it is covered
yellowView.alpha = (100.0 - min(100.0, scrollView.contentOffset.y)) / 100.0
}
}