'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

after scroll-up screen

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

required o/p screen record gif



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

Animate UIView height autolayout to show hide with UIScrollView swift iOS UIView animation block

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:

UIScrollView change height of view on scroll swift iOS

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:

enter image description here

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