Swift - 带有 UIScrollView 的粘性标题

时间:2021-01-29 18:58:35

标签: ios swift

我正在尝试开发一个带有 scrollView 的标头,有点像 Revolut 应用程序:

Revolut app screen recording

我想将一个 scrollview 添加到另一个中,但我想不会得到相同的结果。

这是我的代码:

import UIKit
import SnapKit

class ItemDetailViewController: UIViewController, UIScrollViewDelegate {
        
        
        lazy var topBar = CryptoDetailTopBarView()
        lazy var header = UIView()
        lazy var chartView = UIView()
        
        lazy var scrollView: UIScrollView = {
            let view = UIScrollView()
            view.contentSize = CGSize(width: 414, height: 2000)
            view.backgroundColor = .systemGreen
            view.bounces = true
            return view
        }()
        
        lazy var name = UILabel()
        lazy var symbol = UILabel()
        lazy var type = UILabel()
        
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemGray
            
            view.addSubview(topBar)
            view.addSubview(header)
            view.addSubview(scrollView)
            
            header.addSubview(name)
            header.addSubview(symbol)
            header.addSubview(type)
            
            scrollView.addSubview(chartView)
            
            topBar.layer.zPosition = 20
            topBar.backgroundColor = .blue
            
            header.backgroundColor = .systemRed
            
            name.textColor = .label
            name.font = .boldSystemFont(ofSize: 34)
            
            symbol.textColor = .label
            type.textColor = .systemGray
            
            
            chartView.backgroundColor = .white
            chartView.isUserInteractionEnabled = true
    
    
            self.topBar.title.text = "Title"
            self.name.text = "Title"
            self.symbol.text = "Text"
            self.type.text = "Type"
    
            
            scrollView.delegate = self
            
            updateViewConstraints()
        }
        
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            
            if header.frame.minY < topBar.frame.maxY - name.frame.minY - name.frame.height/2 {
                topBar.title.alpha = 1
            } else {
                topBar.title.alpha = 0
            }
            
            let y: CGFloat = scrollView.contentOffset.y
            
            header.snp.remakeConstraints { make in
                make.left.equalToSuperview()
                make.top.equalTo(topBar.snp.bottom).inset(y)
                make.width.equalToSuperview()
                make.height.equalTo(150)
            }
            
        }
        
        
        override func updateViewConstraints() {
            
            topBar.snp.makeConstraints { make in
                make.left.top.equalToSuperview()
                make.width.equalToSuperview()
                make.height.equalTo(90)
            }
            
            header.snp.makeConstraints { make in
                make.left.equalToSuperview()
                make.top.equalTo(topBar.snp.bottom)
                make.width.equalToSuperview()
                make.height.equalTo(150)
            }
            
            scrollView.snp.makeConstraints { make in
                make.top.equalTo(header.snp.bottom)
                make.centerX.bottom.width.equalToSuperview()
            }
            
            name.snp.makeConstraints { make in
                make.top.equalToSuperview().offset(10)
                make.left.equalToSuperview().offset(16)
                make.width.equalTo(200)
                make.height.equalTo(41)
            }
            
            symbol.snp.makeConstraints { make in
                make.top.equalTo(name.snp.bottom).offset(10)
                make.left.equalTo(name)
                make.width.equalTo(50)
                make.height.equalTo(20)
            }
            
            type.snp.makeConstraints { make in
                make.top.equalTo(symbol)
                make.left.equalTo(symbol.snp.right).offset(10)
                make.width.equalTo(50)
                make.height.equalTo(20)
            }
            
            chartView.snp.makeConstraints { make in
                make.left.equalTo(scrollView).offset(16)
                make.width.equalTo(scrollView).offset(-32)
                make.top.equalTo(15)
                make.height.equalTo(100)
            }
            
            
            super.updateViewConstraints()
        }
        
        fileprivate func getHeightOfHeaderView() -> CGFloat {
            return header.frame.height
        }
        
    }

topBar 视图是带有较小标题的 view,当我向下滚动时会替换大标题(如 Revolut)。

我正在根据我的 SnapKit 偏移量使用 scrollView 更新约束。

这是我得到的:

My app screen recording

您可以看到我的白色 view 称为 chartViewscrollView 的一部分)比我的标题移动得更快,这消除了 Revolut 上的“推动”效果。

另外,当我向下拖动 view 时,我的白色 scrollView 卡住了:应该有“下拉”效果。

基本上我想做和 Revolut 一样的东西。 App Store 对于非特色应用的标题非常相同。

我尝试使用 collectionView、tableView 和 scrollView(使用导航栏大标题),但它没有做我想要的。我需要像我想的 Revolut 一样从头开始制作。

顺便说一句,您可以轻松执行我的代码,因为不需要故事板。

2 个答案:

答案 0 :(得分:0)

您可以使用带有 UITableView / UICollectionView 标题的大导航栏作为标题效果。

已编辑: 您可以在导航栏上使用模糊效果,如下所示:

let statusBarHeight = UIApplication.shared.statusBarFrame.height
// iOS13 and later:
let statusBarHeight = view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
// the size of the blur effect
let bounds = navigationController?.navigationBar.bounds.insetBy(dx: 0, dy: -(statusBarHeight)).offsetBy(dx: 0, dy: -(statusBarHeight))
// Create blur effect.
let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
blurEffectView.frame = bounds
// Set navigation bar up.
navigationController?.navigationBar.isTranslucent = true
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationController?.navigationBar.addSubview(blurEffectView)
navigationController?.navigationBar.sendSubview(toBack: blurEffectView)

答案 1 :(得分:0)

我已经实施过几次这样的事情,你真的很接近。

scrollViewDidScroll 做出反应是正确的做法,但我不建议更新约束。我不会说自动布局旨在实时更新,即使它正常工作,您也可能会看到性能影响。

改为使用视图的 transform 属性。

您必须调整这些值,但它应该看起来像这样:

header.transform = CGAffineTransform(translateX: 0, y: max(0, scrollView.contentOffset.y - header.bounds.y))

这个数学的推理比看起来简单:

我们希望我们的视图在内容偏移量到达我们视图的位置时立即向下移动。这发生在 scrollView.contentOffset.y - header.bounds.y == 0 时。在此之前,此数字将小于 0,这就是我们使用 max 的原因。这样,在发生这种情况之前,我们将应用平移为 0 的变换,因此我们的视图将被正常拖动。

一旦我们的滚动视图被拖到我们的视图位于顶部的点之外,那就是我们想要使它具有粘性的时候。 scrollView.contentOffset.y - header.bounds.y 告诉我们用户滚动到我们的视图多远,因此将我们的视图向下移动该值。

如果您使用带有内容插入的视图,您可能需要调整您的数学,它可能看起来像这样,但如果它不起作用,我会留给您使用这些值看看什么是有效的。

header.transform = CGAffineTransform(translateX: 0, y: max(0, scrollView.contentOffset.y - (header.bounds.y + scrollView.adjustedContentInsets.top)))

提示:使用带有 scrollViewDidScroll 上的内容偏移量等值的打印语句,以确定您想要的确切数字。