You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何实现App Store风格导航栏的滚动动画效果?

解决App Store风格导航栏动画的核心问题

我来帮你拆解并解决这两个导航栏动画的关键问题,同时还原原生的半透明导航栏与边框效果:

一、修复滑动返回时导航栏的异常显示问题

你当前在viewWillDisappear中强制设置alpha = 1,但交互式滑动返回是持续的过渡过程,这个时机设置会导致视觉跳变。更平滑的处理方式是:

  1. 在页面出现时,根据当前滚动位置重新计算导航栏状态;
  2. 利用导航控制器的转场协调器,在滑动返回过程中同步更新导航栏,避免跳变。

修改代码如下:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // 页面出现时,根据当前滚动位置恢复导航栏状态
    scrollViewDidScroll(collectionView)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    if let coordinator = navigationController?.transitionCoordinator {
        // 监听滑动返回的过渡过程
        coordinator.animate(alongsideTransition: { context in
            self.navigationController?.navigationBar.alpha = 1
            self.updateNavigationBarAppearance(percentage: 1)
        }, completion: { context in
            if context.isCancelled {
                // 用户取消滑动返回时,恢复当前页面的导航栏状态
                self.scrollViewDidScroll(self.collectionView)
            }
        })
    } else {
        // 非交互式返回(如点击返回按钮),直接恢复默认状态
        navigationController?.navigationBar.alpha = 1
        updateNavigationBarAppearance(percentage: 1)
    }
}

二、实现返回按钮的原生切换效果

Apple的原生实现确实是通过叠加两个按钮完成的:一个自定义圆形箭头按钮(导航栏透明时显示),一个系统默认返回按钮(导航栏出现时显示)。具体实现步骤:

  1. 初始化自定义返回按钮并添加到导航栏;
  2. 通过navigationItem.hidesBackButton控制系统按钮的显示/隐藏;
  3. 根据滚动百分比,同步调整两个按钮的透明度,实现平滑过渡。

添加以下代码:

override func viewDidLoad() {
    super.viewDidLoad()
    
    // 创建自定义返回按钮(可替换为你需要的样式)
    let customBackButton = UIButton(type: .system)
    customBackButton.setImage(UIImage(systemName: "arrow.left.circle"), for: .normal)
    customBackButton.tintColor = .white
    customBackButton.addTarget(self, action: #selector(customBackTapped), for: .touchUpInside)
    navigationItem.leftBarButtonItem = UIBarButtonItem(customView: customBackButton)
    
    // 初始隐藏系统返回按钮
    navigationItem.hidesBackButton = true
}

@objc func customBackTapped() {
    navigationController?.popViewController(animated: true)
}

// 修改scrollViewDidScroll方法,加入按钮切换逻辑
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let currentVerticalOffset = scrollView.contentOffset.y
    let maxOffset = collectionView.frame.size.height
    // 限制百分比在0-1之间,避免超出范围
    let percentage = min(max(currentVerticalOffset / maxOffset, 0), 1)
    
    // 更新标题显示
    title = percentage > 0.5 ? "The Barn Owl" : ""
    
    // 更新导航栏透明度
    navigationController?.navigationBar.alpha = percentage
    
    // 切换返回按钮:自定义按钮随百分比降低透明度,系统按钮随百分比显示
    navigationItem.leftBarButtonItem?.customView?.alpha = 1 - percentage
    navigationItem.hidesBackButton = percentage < 0.5
    
    // 更新导航栏半透明与边框效果
    updateNavigationBarAppearance(percentage: percentage)
}

三、还原原生半透明导航栏与边框效果

直接调整backgroundColoralpha会丢失半透明特性和边框,正确的做法是利用导航栏的barTintColorbackgroundImageshadowImage实现:

func updateNavigationBarAppearance(percentage: CGFloat) {
    guard let navBar = navigationController?.navigationBar else { return }
    
    navBar.isTranslucent = true
    // 设置半透明白色背景,透明度随滚动百分比变化
    navBar.barTintColor = UIColor.white.withAlphaComponent(percentage)
    
    // 控制导航栏边框显示:百分比超过阈值时显示边框
    // 这里的"navBarShadow"是自定义的边框图片(与系统默认样式一致即可)
    let shadowImage = percentage > 0.1 ? UIImage(named: "navBarShadow") : UIImage()
    navBar.shadowImage = shadowImage
    
    // 适配文字与按钮颜色,随背景变化
    let textColor = percentage > 0.5 ? UIColor.black : UIColor.white
    navBar.titleTextAttributes = [.foregroundColor: textColor]
    navBar.tintColor = textColor
    navigationItem.leftBarButtonItem?.customView?.tintColor = textColor
}

完整优化后的代码

整合以上所有逻辑,完整的Swift 5实现代码如下:

import UIKit

class OwlViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
    
    @IBOutlet weak var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 初始化自定义返回按钮
        let customBackButton = UIButton(type: .system)
        customBackButton.setImage(UIImage(systemName: "arrow.left.circle"), for: .normal)
        customBackButton.tintColor = .white
        customBackButton.addTarget(self, action: #selector(customBackTapped), for: .touchUpInside)
        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: customBackButton)
        
        // 初始隐藏系统返回按钮
        navigationItem.hidesBackButton = true
        
        // 初始设置导航栏为透明状态
        updateNavigationBarAppearance(percentage: 0)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        scrollViewDidScroll(collectionView)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        if let coordinator = navigationController?.transitionCoordinator {
            coordinator.animate(alongsideTransition: { context in
                self.navigationController?.navigationBar.alpha = 1
                self.updateNavigationBarAppearance(percentage: 1)
            }, completion: { context in
                if context.isCancelled {
                    self.scrollViewDidScroll(self.collectionView)
                }
            })
        } else {
            navigationController?.navigationBar.alpha = 1
            updateNavigationBarAppearance(percentage: 1)
        }
    }
    
    @objc func customBackTapped() {
        navigationController?.popViewController(animated: true)
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let currentVerticalOffset = scrollView.contentOffset.y
        let maxOffset = collectionView.frame.size.height
        let percentage = min(max(currentVerticalOffset / maxOffset, 0), 1)
        
        title = percentage > 0.5 ? "The Barn Owl" : ""
        navigationController?.navigationBar.alpha = percentage
        
        navigationItem.leftBarButtonItem?.customView?.alpha = 1 - percentage
        navigationItem.hidesBackButton = percentage < 0.5
        
        updateNavigationBarAppearance(percentage: percentage)
    }
    
    func updateNavigationBarAppearance(percentage: CGFloat) {
        guard let navBar = navigationController?.navigationBar else { return }
        
        navBar.isTranslucent = true
        navBar.barTintColor = UIColor.white.withAlphaComponent(percentage)
        
        let shadowImage = percentage > 0.1 ? UIImage(named: "navBarShadow") : UIImage()
        navBar.shadowImage = shadowImage
        
        let textColor = percentage > 0.5 ? UIColor.black : UIColor.white
        navBar.titleTextAttributes = [.foregroundColor: textColor]
        navBar.tintColor = textColor
        navigationItem.leftBarButtonItem?.customView?.tintColor = textColor
    }
    
    // MARK: - CollectionView DataSource
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 20
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = .lightGray
        return cell
    }
}

这样就能完美还原App Store的导航栏动画效果,包括滑动返回的平滑过渡、返回按钮的自然切换,以及半透明导航栏与边框的正确显示。

内容的提问来源于stack exchange,提问作者Junaid

火山引擎 最新活动