如何在 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
    }
}