'How to hide/show view simultaneously while scroll up/down in swift
storyboard design hierarchy like this design hierarchy
here if i scroll-up i need to hide only topView and need to show secondView on top and scroll should perform from the bottom of secondView and if i scroll-down need to show topView as well
code: with this code topView is hiding/showing immediately while scroll up/down.. but when i scroll-up then topView should hide visibly(simultaneously) with scroll-up how to achieve that. please guide.
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:
initial o/p before scroll-up
initial o/p before scroll-up screen
after scroll-up: its hiding immediately, i need it should hide along with scrolling up
EDIT: I need when scrolling secondView top reaches firstView top then firstView has to hide
so how to calculate to achive? is it possible to use secodeView top constarint value changes? then how to calculate? please guide
here attached required o/p screen record
Solution 1:[1]
The idea is to lock the animation block from firing if it is already in progress.
The simplest way to do this is using a bool to keep track of the status of the animation
First, use some variables to help you keep track of the animation and top view's status
// 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
Then use these variables when performing the animation
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
}
}
}
This will give you something smoother like this
Update based on OPs comments
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
If you want to do that, you don't really need the animation block, you can just keep reducing the top views height till it becomes 0.
Add these
// Stores the original offset of the scroll view
var previousOffset: CGPoint?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
previousOffset = scrollView.contentOffset
}
Then update the 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()
}
}
}
That will give you something like this:
Solution 2:[2]
Assuming you don't really need to hide the yellow view - you only need to cover it with the green view...
This can be done with constraints only -- no need for any scrollViewDidScroll code.
What we'll do is constrain the yellow view to the scroll view's Frame Layout Guide so it doesn't move at all.
Then we'll constrain the green view's Top greater-than-or-equal to the Frame Layout Guide, and constrain it to the bottom of the yellow view with a less-than-required Priority.
Then we'll constrain the Bottom of the green view to the Top of the red view, so the red view will "push it up / pull it down" when we scroll. Again, though, we'll use a less-than-required Priority so the red view can slide up underneath.
Finally, we'll constrain the red view to the scroll view's Content Layout Guide to control the scrollable area. Since the yellow and green views each have a constant height of 100-pts, we'll constrain the Top of the red view 200-pts from the Top of the content guide.
Here's a complete example (no @IBOutlet connections needed):
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
}
}
Here's how it looks - I set the green view to be 40-pts narrower than the full width to make it easy to see what's happening:
Now, if you do want to actually hide the yellow view, instead of just covering it, add this extension:
extension ExampleVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
yellowView.isHidden = scrollView.contentOffset.y >= 100
}
}
and add this to the view controller:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
scrollView.delegate = self
}
We do this in viewDidAppear to avoid erroneous frame positioning that occurs if we set the delegate in viewDidLoad
Edit
If you want the yellowView to "fade in/out" as it's being covered / revealed, use this:
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
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 |




