iOS:如何滚动导航栏作为视图控制器滚动

发布于 2025-01-25 09:49:28 字数 277 浏览 3 评论 0原文

我想在用户在视图控制器上滚动时滚动导航栏。这应该类似于YouTube应用程序的主页运行方式。当用户向下滚动时,应使导航栏可见。导航栏应移动到滚动量。

我知道HIDESBARONSWIPEsetNavigationbarhidden,但是这些并不能准确控制y轴。我还读到Apple不支持直接修改导航杆框架。

那么,YouTube如何做到这一点?我正在寻找一个MVP,展示导航栏位置更改以及uiscrollview偏移更改。

I want to scroll the navigation bar as the user scrolls on the view controller. This should be similar to how the YouTube app's home page is working. When the user scrolls down, the navigation bar should be made visible. The navigation bar should move as much as the scroll amount.

I'm aware of hidesBarOnSwipe and setNavigationBarHidden, but these do not give precise control of the y-axis. I'm also reading that Apple does not support directly modifying the navigation bar frame.

So, how does YouTube do this? I'm looking for an MVP demonstrating navigation bar position change along with a UIScrollView offset change.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

无法言说的痛 2025-02-01 09:49:28

如果您想做什么,我将做出一些猜测。

首先,YouTube应用程序主页的顶部几乎可以肯定是 不是 a uinavigationbar - 它的行为不像一个推动/弹出控制器正在进行,它在标签栏控制器设置等中。

因此,假设它是带有子视图的视图 - 我们将其称为“滑动标头视图” - 您的目标是:

  • 不要让 标头视图的顶部滚动
  • 当向下滚动时, “向上滚动”滚动时,
  • 向下滚动,

我们可以通过将标头视图的顶部限制到滚动视图的框架布局指南的顶部来完成此操作。

  • 当我们 start 滚动时,我们将保存当前.contentoffset.y
  • 当我们滚动时,如果我们滚动,我们将获得相对滚动y的距离
  • ,我们将更改顶部约束.constant值,以将标题视图移动,
  • 如果我们向下滚动,我们将更改顶部约束.constant标题查看以下

是从开始的样子:

​nofollow noreferrer”> “在此处输入映像”

我们向上滚动:

当我们向下滚动一点时:

”输入图像描述在此处

我们向下滚动后:

“在此处输入图像说明”

这是该示例代码:

简单的两标签“标头”标头“ “查看

class SlidingHeaderView: UIView {
    
    // simple view with two labels
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        backgroundColor = .systemBlue
        
        let v1 = UILabel()
        v1.translatesAutoresizingMaskIntoConstraints = false
        v1.text = "Label 1"
        v1.backgroundColor = .yellow
        addSubview(v1)

        let v2 = UILabel()
        v2.translatesAutoresizingMaskIntoConstraints = false
        v2.text = "Label 2"
        v2.backgroundColor = .yellow
        addSubview(v2)

        NSLayoutConstraint.activate([
            
            v1.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12.0),
            v1.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
            v2.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12.0),
            v2.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
            
            v1.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
            v2.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
            
            v2.topAnchor.constraint(equalTo: v1.bottomAnchor, constant: 4.0),
            
            v2.heightAnchor.constraint(equalTo: v1.heightAnchor),
            
        ])
        
    }
    
}

示例查看控制器

class SlidingHeaderViewController: UIViewController {
    
    let scrollView: UIScrollView = {
        let v = UIScrollView()
        v.contentInsetAdjustmentBehavior = .never
        return v
    }()
    let slidingHeaderView: SlidingHeaderView = {
        let v = SlidingHeaderView()
        return v
    }()
    let contentView: UIView = {
        let v = UIView()
        v.backgroundColor = .systemYellow
        return v
    }()

    // Top constraint for the slidingHeaderView
    var slidingViewTopC: NSLayoutConstraint!

    // to track the scroll activity
    var curScrollY: CGFloat = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        [scrollView, slidingHeaderView, contentView].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
        }
        
        // add contentView and slidingHeaderView to the scroll view
        [contentView, slidingHeaderView].forEach { v in
            scrollView.addSubview(v)
        }
        
        // add scroll view to self.view
        view.addSubview(scrollView)
        
        let safeG = view.safeAreaLayoutGuide
        let contentG = scrollView.contentLayoutGuide
        let frameG = scrollView.frameLayoutGuide
        
        // we're going to change slidingHeaderView's Top constraint relative to the Top of the scroll view FRAME
        slidingViewTopC = slidingHeaderView.topAnchor.constraint(equalTo: frameG.topAnchor, constant: 0.0)
        
        NSLayoutConstraint.activate([
            
            // scroll view Top to view Top
            scrollView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 0.0),
            
            // scroll view Leading/Trailing/Bottom to safe area
            scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 0.0),
            scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: 0.0),
            scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: 0.0),
            
            // constrain slidingHeaderView Top to scroll view's FRAME
            slidingViewTopC,
            
            // slidingHeaderView to Leading/Trailing of scroll view FRAME
            slidingHeaderView.leadingAnchor.constraint(equalTo: frameG.leadingAnchor, constant: 0.0),
            slidingHeaderView.trailingAnchor.constraint(equalTo: frameG.trailingAnchor, constant: 0.0),
            
            // no Height or Bottom constraint for slidingHeaderView
            
            // content view Top/Leading/Trailing/Bottom to scroll view's CONTENT GUIDE
            contentView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 0.0),
            contentView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
            contentView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
            contentView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),
            
            // content view Width to scroll view's FRAME
            contentView.widthAnchor.constraint(equalTo: frameG.widthAnchor, constant: 0.0),
            
        ])
        
        // add some content to the content view so we have something to scroll
        addSomeContent()
        
        // because we're going to track the scroll offset
        scrollView.delegate = self
        
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        if slidingHeaderView.frame.height == 0 {
            // get the size of the slidingHeaderView
            let sz = slidingHeaderView.systemLayoutSizeFitting(CGSize(width: scrollView.frame.width, height: .greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
            // use its Height for the scroll view's Top contentInset
            scrollView.contentInset = UIEdgeInsets(top: sz.height, left: 0, bottom: 0, right: 0)
        }
    }
    
    func addSomeContent() {
        // create a vertical stack view with a bunch of labels
        //  and add it to our content view so we have something to scroll
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 32
        stack.backgroundColor = .gray
        stack.translatesAutoresizingMaskIntoConstraints = false
        for i in 1...20 {
            let v = UILabel()
            v.text = "Label \(i)"
            v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
            v.heightAnchor.constraint(equalToConstant: 48.0).isActive = true
            stack.addArrangedSubview(v)
        }
        contentView.addSubview(stack)
        NSLayoutConstraint.activate([
            stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16.0),
            stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16.0),
            stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16.0),
            stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16.0),
        ])
    }
    
}

extension SlidingHeaderViewController: UIScrollViewDelegate {
    
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        curScrollY = scrollView.contentOffset.y
    }
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let diffY = scrollView.contentOffset.y - curScrollY
        var newY: CGFloat = slidingViewTopC.constant - diffY
        if diffY < 0 {
            // we're scrolling DOWN
            newY = min(newY, 0.0)
        } else {
            // we're scrolling UP
            if scrollView.contentOffset.y <= -slidingHeaderView.frame.height {
                newY = 0.0
            } else {
                newY = max(-slidingHeaderView.frame.height, newY)
            }
        }
        // update slidingHeaderView Top constraint constant
        slidingViewTopC.constant = newY
        curScrollY = scrollView.contentOffset.y
    }
    
}

所有内容都是通过代码完成的 - @iboutlet@ibaction需要连接。

Without additional detail about what you want to do, I'll make some guesses.

First, the top of the YouTube app's home page is almost certainly not a UINavigationBar -- it doesn't behave like one, there is no pushing/popping of controllers going on, it's in a tab bar controller setup, etc.

So, let's assume it's a view with subviews - we'll call it a "sliding header view" - and your goal is:

  • don't let the header view's top scroll down
  • "push it up" when scrolling up
  • "pull it down" when scrolling down

We can accomplish this by constraining the Top of that header view to the Top of the scroll view's Frame Layout Guide.

  • when we start to scroll, we'll save the current .contentOffset.y
  • when we scroll, we'll get the relative scroll y distance
  • if we're scrolling Up, we'll change the Top Constraint .constant value to move the header view up
  • if we're scrolling Down, we'll change the Top Constraint .constant value to move the header view down

Here's how it will look at the start:

enter image description here

as we scroll up just a little:

enter image description here

after we've scrolled up farther:

enter image description here

as we scroll down just a little:

enter image description here

after we've scrolled down farther:

enter image description here

Here's the example code for that:

Simple two-label "header" view

class SlidingHeaderView: UIView {
    
    // simple view with two labels
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        backgroundColor = .systemBlue
        
        let v1 = UILabel()
        v1.translatesAutoresizingMaskIntoConstraints = false
        v1.text = "Label 1"
        v1.backgroundColor = .yellow
        addSubview(v1)

        let v2 = UILabel()
        v2.translatesAutoresizingMaskIntoConstraints = false
        v2.text = "Label 2"
        v2.backgroundColor = .yellow
        addSubview(v2)

        NSLayoutConstraint.activate([
            
            v1.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12.0),
            v1.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
            v2.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12.0),
            v2.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
            
            v1.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
            v2.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
            
            v2.topAnchor.constraint(equalTo: v1.bottomAnchor, constant: 4.0),
            
            v2.heightAnchor.constraint(equalTo: v1.heightAnchor),
            
        ])
        
    }
    
}

example view controller

class SlidingHeaderViewController: UIViewController {
    
    let scrollView: UIScrollView = {
        let v = UIScrollView()
        v.contentInsetAdjustmentBehavior = .never
        return v
    }()
    let slidingHeaderView: SlidingHeaderView = {
        let v = SlidingHeaderView()
        return v
    }()
    let contentView: UIView = {
        let v = UIView()
        v.backgroundColor = .systemYellow
        return v
    }()

    // Top constraint for the slidingHeaderView
    var slidingViewTopC: NSLayoutConstraint!

    // to track the scroll activity
    var curScrollY: CGFloat = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        [scrollView, slidingHeaderView, contentView].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
        }
        
        // add contentView and slidingHeaderView to the scroll view
        [contentView, slidingHeaderView].forEach { v in
            scrollView.addSubview(v)
        }
        
        // add scroll view to self.view
        view.addSubview(scrollView)
        
        let safeG = view.safeAreaLayoutGuide
        let contentG = scrollView.contentLayoutGuide
        let frameG = scrollView.frameLayoutGuide
        
        // we're going to change slidingHeaderView's Top constraint relative to the Top of the scroll view FRAME
        slidingViewTopC = slidingHeaderView.topAnchor.constraint(equalTo: frameG.topAnchor, constant: 0.0)
        
        NSLayoutConstraint.activate([
            
            // scroll view Top to view Top
            scrollView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 0.0),
            
            // scroll view Leading/Trailing/Bottom to safe area
            scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 0.0),
            scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: 0.0),
            scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: 0.0),
            
            // constrain slidingHeaderView Top to scroll view's FRAME
            slidingViewTopC,
            
            // slidingHeaderView to Leading/Trailing of scroll view FRAME
            slidingHeaderView.leadingAnchor.constraint(equalTo: frameG.leadingAnchor, constant: 0.0),
            slidingHeaderView.trailingAnchor.constraint(equalTo: frameG.trailingAnchor, constant: 0.0),
            
            // no Height or Bottom constraint for slidingHeaderView
            
            // content view Top/Leading/Trailing/Bottom to scroll view's CONTENT GUIDE
            contentView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 0.0),
            contentView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
            contentView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
            contentView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),
            
            // content view Width to scroll view's FRAME
            contentView.widthAnchor.constraint(equalTo: frameG.widthAnchor, constant: 0.0),
            
        ])
        
        // add some content to the content view so we have something to scroll
        addSomeContent()
        
        // because we're going to track the scroll offset
        scrollView.delegate = self
        
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        if slidingHeaderView.frame.height == 0 {
            // get the size of the slidingHeaderView
            let sz = slidingHeaderView.systemLayoutSizeFitting(CGSize(width: scrollView.frame.width, height: .greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
            // use its Height for the scroll view's Top contentInset
            scrollView.contentInset = UIEdgeInsets(top: sz.height, left: 0, bottom: 0, right: 0)
        }
    }
    
    func addSomeContent() {
        // create a vertical stack view with a bunch of labels
        //  and add it to our content view so we have something to scroll
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 32
        stack.backgroundColor = .gray
        stack.translatesAutoresizingMaskIntoConstraints = false
        for i in 1...20 {
            let v = UILabel()
            v.text = "Label \(i)"
            v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
            v.heightAnchor.constraint(equalToConstant: 48.0).isActive = true
            stack.addArrangedSubview(v)
        }
        contentView.addSubview(stack)
        NSLayoutConstraint.activate([
            stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16.0),
            stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16.0),
            stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16.0),
            stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16.0),
        ])
    }
    
}

extension SlidingHeaderViewController: UIScrollViewDelegate {
    
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        curScrollY = scrollView.contentOffset.y
    }
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let diffY = scrollView.contentOffset.y - curScrollY
        var newY: CGFloat = slidingViewTopC.constant - diffY
        if diffY < 0 {
            // we're scrolling DOWN
            newY = min(newY, 0.0)
        } else {
            // we're scrolling UP
            if scrollView.contentOffset.y <= -slidingHeaderView.frame.height {
                newY = 0.0
            } else {
                newY = max(-slidingHeaderView.frame.height, newY)
            }
        }
        // update slidingHeaderView Top constraint constant
        slidingViewTopC.constant = newY
        curScrollY = scrollView.contentOffset.y
    }
    
}

Everything is done via code - no @IBOutlet or @IBAction connections needed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文