你是不是想实现这样的效果?
collectionView有背景图,且为headerView;headerView可跟随collectionView滚动;headerView在滚动过程中有缩放效果;collectionView滚动时,navBar渐变。
这样的设计很常见,代码逻辑如下:
import UIKit
import Combine
import SnapKit
class DanceListController: UIViewController {
typealias NavigationView = TemplateListController.NavigationView
// 跟随scrollView滑动且可缩放的headerView
private lazy var topView = TopView()
// 自定义导航视图(初始化时为透明)
private lazy var navigationView = NavigationView()
// 滚动视图
private lazy var collectionView = UICollectionView(
frame: view.bounds,
collectionViewLayout: createCompositionalLayout()
)
private(set) var viewModel: ViewModel
var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
private var cancellables = Set<AnyCancellable>()
var topImgHeight: CGFloat {
(view.width * (44.0 + 375) / 375) * (560.0 / 420)
}
var topImgOffset: CGFloat {
-(topImgHeight * (200.0 / 560))
}
var blankHeight: CGFloat {
(topImgHeight + topImgOffset) * 0.6
}
init(id: String, type: Int, title: String? = nil, models: [Template] = []) {
self.viewModel = ViewModel(cid: id, ctype: type, title: title, models: models)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var preferredStatusBarStyle: UIStatusBarStyle {
if #available(iOS 17.0, *) {
.default
} else {
.lightContent
}
}
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
configureDataSource()
bindData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
viewModel.loadPlayer(model: nil)
}
func configureUI() {
navigationView.titleLabel.alpha = 0
navigationView.titleLabel.text = viewModel.title
navigationView.titleLabel.textColor = .white
navigationView.backButton.setImage(UIImage(named: "ic_navigation_back")?.withTintColor(.white), for: .normal)
view.backgroundColor = .hex(0x03141A)
//
view.addSubview(topView)
topView.width = view.width * (44 + 375) / 375
topView.height = topView.width * 560 / 420
topView.centerX = view.width / 2
topView.top = topImgOffset
//
view.addSubview(navigationView)
navigationView.snp.makeConstraints {
$0.leading.trailing.equalToSuperview()
$0.top.equalToSuperview().offset(Const.safeAreaTop)
$0.height.equalTo(44)
}
//
view.addSubview(collectionView)
collectionView.snp.makeConstraints {
$0.top.equalToSuperview().offset(44 + Const.safeAreaTop)
$0.leading.trailing.bottom.equalToSuperview()
}
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.backgroundColor = .clear
collectionView.delegate = self
let refresh = UIRefreshControl()
refresh.tintColor = .init(white: 1, alpha: 0.7)
refresh.addAction(.init(handler: { [unowned self] _ in
refreshNewDataAction()
}), for: .valueChanged)
collectionView.refreshControl = refresh
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
topView.top = topImgOffset - offsetY
let threshold = (topImgHeight + topImgOffset) / 2
if offsetY > threshold {
let ratio = (offsetY - threshold) / threshold
navigationView.titleLabel.alpha = min(ratio, 1)
} else {
navigationView.titleLabel.alpha = 0
}
}
// .............. controller中的其他代码,请自行添加!
class TopView: UIView {
private lazy var bgImageView = UIImageView().then {
$0.image = UIImage(named: "pexels_top_image")
$0.contentMode = .scaleAspectFill
$0.backgroundColor = .clear
}
private(set) lazy var titleLabel = UILabel().then {
$0.font = .poppins(24, weight: .bold)
$0.textAlignment = .center
$0.textColor = .white
}
private(set) lazy var tipsLabel = UILabel().then {
$0.textColor = .white.withAlphaComponent(0.7)
$0.font = .poppins(16, weight: .regular)
$0.textAlignment = .center
}
lazy var gradientLayer: CAGradientLayer = .init().then {
$0.colors = [
UIColor(hex: 0x03141A).withAlphaComponent(1).cgColor,
UIColor(hex: 0x000000).withAlphaComponent(0).cgColor
]
$0.startPoint = CGPoint(x: 0.5, y: 1)
$0.endPoint = CGPoint(x: 0.5, y: 0)
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
addSubview(bgImageView)
bgImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
layer.addSublayer(gradientLayer)
titleLabel.text = "Dance Like Them".localized
addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(-116)
}
tipsLabel.text = "Full body portrait to dancing video".localized
addSubview(tipsLabel)
tipsLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(2)
}
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = CGRect(x: 0, y: frame.size.height - 160, width: frame.size.width, height: 161)
}
}
关于视图的设置:
- 各个视图的层级顺序:self.view >> topView >> navigationView >> collectionView >>;
- collectionView的起始位置,在navigationView以下;
- 其中 navigationView底色透明,title的alpha初始为0;
- topView的四边向屏幕外有延伸;
navBar的渐变效果
- 其本身是透明的,但是topView底部有gradientLayer;
- title的透明度跟随滚动变化;
监听滚动事件:
// 设置topView随之滚动
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
topView.top = topImgOffset - offsetY
let threshold = (topImgHeight + topImgOffset) / 2
if offsetY > threshold {
let ratio = (offsetY - threshold) / threshold
navigationView.titleLabel.alpha = min(ratio, 1)
} else {
navigationView.titleLabel.alpha = 0
}
}
如果想设置滚动同时使topView有缩放效果,则计算一个scale并设置topView的transform即可。
网友评论