2

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

8
  • That's because as you scroll, the animation is kicking off several times. What you need to do is lock the view from animating once an animation is in progress and open it up once it is completed. If you can show an image of how top view, second view and scroll are laid out on your storyboard (not just the view hierarchy), I might be able to suggest an example. Commented Mar 27, 2022 at 7:25
  • @ShawnFrank, I have edited my post with o/p before scroll and after scroll Commented Mar 27, 2022 at 14:43
  • @ShawnFrank, the yellowview hides but i need it should hide along with scroll up Commented Mar 27, 2022 at 14:44
  • @JsonSwift - it's not quite clear what your goal is... Do you want the Green view to "slide up" and cover the Yellow view? Or, should it "squeeze" the Yellow view? And, when the Green view reaches the top, is it supposed to stay there and the red view scrolls under it? Commented Mar 28, 2022 at 13:50
  • @DonMag, when i scroll-up then yellow view should hide and green view should be on top and scrolling should work under green view... Commented Mar 28, 2022 at 13:59

3 Answers 3

3

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

Sign up to request clarification or add additional context in comments.

10 Comments

here only animation status changed... but i dont need like this
I have edited my post with required out put screen record gif.. so could you guide like that
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
here you are taking care of animation but thats not the issue.. could you update your according to my question edited screen o/p
@JsonSwift - please see my updated answer
|
3

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
    }
}

11 Comments

Oops! still miss understanding.. when you scroll up yellowview immediately hiding.. which i dont want.. here the green view should move from(on top of) yellow view means while scrolling-up greenview need to reach middle of yellowview visabily as well while hiding yellowview
could you plz update your answer according too
@JsonSwift - I don't understand... The green view is "sliding up over" the yellow view. Do you want it to be translucent?
@JsonSwift - it's difficult to show in an animated gif... did you try running that Example code?
@JsonSwift - do you want the yellow view to "fade out" as it is being covered by the green view?
|
-1

Hope this might help.

class HideShowTopViewScrollUpDownViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    var topYellowViewHeight: CGFloat = 100

    override func viewDidLoad() {
        super.viewDidLoad()

        setAllThreeViews()
        setUpTable()
    }
    
    let topYellowview: UIView = {
        let v = UIView()
        v.backgroundColor = .yellow
        return v
    }()
    
    let secondTopGreenView: UIView = {
        let v = UIView()
        v.backgroundColor = .green
        return v
    }()
    
    let redScrollView: UITableView = {
        let tbl = UITableView()
        tbl.backgroundColor = .red
        return tbl
    }()


    
    private func setAllThreeViews(){
        view.addSubview(topYellowview)
        view.addSubview(secondTopGreenView)
        view.addSubview(redScrollView)
        
        topYellowview.snp.makeConstraints { make in
            make.top.left.right.equalTo(view.safeAreaLayoutGuide)
            make.height.equalTo(topYellowViewHeight)
        }
        
        secondTopGreenView.snp.makeConstraints { make in
            make.top.equalTo(topYellowview.snp.bottom)
            make.left.right.equalTo(view)
            make.height.equalTo(100)
        }
        
        redScrollView.snp.makeConstraints { make in
            make.top.equalTo(secondTopGreenView.snp.bottom)
            make.left.right.bottom.equalTo(view)
        }
    }
    
    private func setUpTable(){
        redScrollView.delegate = self
        redScrollView.dataSource = self
        redScrollView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 20
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = "\(indexPath.row+1).   Hi GJ!"
        cell.textLabel?.textColor = UIColor.black
        cell.textLabel?.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
        cell.selectionStyle = .none
        cell.backgroundColor = .clear
        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 50;
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {

        //Without this if conditions, it will still work.
        //But we should not do the same task again, when it is already satisfies our condition
        
        let minOffsetOfSecondViewTop = topYellowview.frame.origin.y
        let maxOffsetOfSecondViewTop = topYellowview.frame.origin.y+topYellowViewHeight
        
        if((scrollView.contentOffset.y > 0 && secondTopGreenView.frame.origin.y > minOffsetOfSecondViewTop) ||
           (scrollView.contentOffset.y < topYellowViewHeight && secondTopGreenView.frame.origin.y < maxOffsetOfSecondViewTop)){
            
            var topOffset = scrollView.contentOffset.y > topYellowViewHeight ? topYellowViewHeight : scrollView.contentOffset.y
            topOffset = scrollView.contentOffset.y < 0 ? 0 : topOffset
            
            topYellowview.alpha = (topYellowViewHeight-topOffset)*(1.4)/topYellowViewHeight

            secondTopGreenView.snp.updateConstraints { make in
                make.top.equalTo(topYellowview.snp.bottom).inset(topOffset)
            }
        }
        
    }
}

1 Comment

Code-only answers are frowned upon on SO. A good answer explains how the answer solves the OP's question. In this case it would be helpful to explain what was wrong with the OP's code and to explain how this code resolves the issue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.