Home > Blockchain >  How to hide/show view simultaneously while scroll up/down in swift
How to hide/show view simultaneously while scroll up/down in swift

Time:03-29

storyboard design hierarchy like this required o/p screen record gif

CodePudding user response:

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

CodePudding user response:

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
        c = greenView.topAnchor.constraint(equalTo: yellowView.bottomAnchor)
        c.priority = .defaultHigh - 1
        c.isActive = true
        
        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

  • Related