Reputation: 1
I'm working on an iOS app in Swift where I have a UICollectionView
displaying ads at the top of my screen and another UICollectionView
displaying products below it. I want to implement a smooth transition where, as I scroll up, the ad UICollectionView
gradually transforms into a UISearchBar
. Here are my specific requirements:
UICollectionView
should move up together with the product UICollectionView
.UICollectionView
scrolls up, it should gradually transform into a UISearchBar
.UISearchBar
should transform back into the ad UICollectionView
.UISearchBar
should stick at the top, just below the safe area, with a 5-point offset, and the product UICollectionView
should not overlap it.Problems I am facing:
I've tried implementing this with a UIScrollView
containing both UICollectionView
s and using animations, but I haven't been able to get it to work smoothly. Here’s my current code:
import UIKit
class MainView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UIScrollViewDelegate {
// Temporary data
var adArr = ["ad1", "ad2", "ad3"]
var extendedAdArr: [String] = []
private let cosmetics = [
Cosmetic(imageName: "sary", name: "Ma:Nyo Pure Cleansing Oil", discountedPrice: "14", originalPrice: "27"),
Cosmetic(imageName: "zhasyl", name: "Ma:Nyo Bifida Cica Herb Toner", discountedPrice: "17", originalPrice: "23"),
Cosmetic(imageName: "koz", name: "Ma:Nyo 4GF Eyelash Ampoule", discountedPrice: "16", originalPrice: "25"),
Cosmetic(imageName: "the", name: "The Ordinary Caffeine Solution", discountedPrice: "13", originalPrice: "20")
]
// MARK: - Properties
private var adCollectionViewHeightConstraint: NSLayoutConstraint!
private var adCollectionViewTopConstraint: NSLayoutConstraint!
private var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
private var contentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private var adCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 0.3)
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.minimumLineSpacing = 0
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.backgroundColor = .white
collectionView.isPagingEnabled = true
return collectionView
}()
private var pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.currentPageIndicatorTintColor = .purple
pageControl.pageIndicatorTintColor = .white
return pageControl
}()
private var searchBarView: SearchBarView = {
let view = SearchBarView()
view.translatesAutoresizingMaskIntoConstraints = false
view.alpha = 0 // Initially hidden
return view
}()
private var productsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: UIScreen.main.bounds.width / 2 - 10, height: UIScreen.main.bounds.height * 0.3)
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.backgroundColor = .white
return collectionView
}()
// Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setBackgroudColor()
setupScrollView()
setupAdCollectionView()
setupPageControl()
setupSearchBarView()
extendAdArray()
setupProductCollectionView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
adjustInitialScrollPosition()
}
// MARK: - Private and Public Methods
func setBackgroudColor() {
backgroundColor = UIColor(white: 0.97, alpha: 1)
}
private func setupScrollView() {
addSubview(scrollView)
scrollView.addSubview(contentView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: topAnchor),
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
}
private func setupAdCollectionView() {
contentView.addSubview(adCollectionView)
adCollectionView.dataSource = self
adCollectionView.delegate = self
adCollectionView.register(AdCollectionViewCell.self, forCellWithReuseIdentifier: AdCollectionViewCell.identifier)
adCollectionViewTopConstraint = adCollectionView.topAnchor.constraint(equalTo: contentView.topAnchor)
adCollectionViewHeightConstraint = adCollectionView.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height * 0.3)
NSLayoutConstraint.activate([
adCollectionViewTopConstraint,
adCollectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
adCollectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
adCollectionViewHeightConstraint
])
}
private func setupProductCollectionView() {
contentView.addSubview(productsCollectionView)
productsCollectionView.dataSource = self
productsCollectionView.delegate = self
productsCollectionView.register(CosmeticCollectionViewCell.self, forCellWithReuseIdentifier: CosmeticCollectionViewCell.identifier)
NSLayoutConstraint.activate([
productsCollectionView.topAnchor.constraint(equalTo: adCollectionView.bottomAnchor),
productsCollectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
productsCollectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
productsCollectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
private func setupPageControl() {
contentView.addSubview(pageControl)
NSLayoutConstraint.activate([
pageControl.bottomAnchor.constraint(equalTo: adCollectionView.bottomAnchor, constant: -8),
pageControl.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
])
}
private func setupSearchBarView() {
addSubview(searchBarView)
NSLayoutConstraint.activate([
searchBarView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 5),
searchBarView.leadingAnchor.constraint(equalTo: leadingAnchor),
searchBarView.trailingAnchor.constraint(equalTo: trailingAnchor),
searchBarView.heightAnchor.constraint(equalToConstant: 56)
])
}
private func extendAdArray() {
extendedAdArr = adArr + adArr + adArr // Duplicate the array 3 times
adCollectionView.reloadData()
}
private func adjustInitialScrollPosition() {
let middleIndexPath = IndexPath(item: adArr.count, section: 0)
adCollectionView.scrollToItem(at: middleIndexPath, at: .centeredHorizontally, animated: false)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let maxOffsetY = UIScreen.main.bounds.height * 0.3 - 56 // Height difference between adCollectionView and searchBar
let safeAreaOffset = safeAreaInsets.top + 5
if offsetY <= maxOffsetY {
adCollectionViewTopConstraint.constant = -offsetY
adCollectionViewHeightConstraint.constant = UIScreen.main.bounds.height * 0.3 - offsetY
searchBarView.alpha = offsetY / maxOffsetY
searchBarView.transform = .identity
} else {
adCollectionViewTopConstraint.constant = -maxOffsetY
adCollectionViewHeightConstraint.constant = 56
searchBarView.alpha = 1
searchBarView.transform = CGAffineTransform(translationX: 0, y: safeAreaOffset)
scrollView.contentOffset.y = maxOffsetY
}
}
// MARK: - CollectionView DataSource and Delegate
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == adCollectionView {
return extendedAdArr.count
} else if collectionView == productsCollectionView {
return cosmetics.count
}
return 0
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == adCollectionView {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AdCollectionViewCell.identifier, for: indexPath) as? AdCollectionViewCell else {
fatalError("unable to dequeue the AdCollectionViewCell")
}
let imageName = extendedAdArr[indexPath.row]
if let image = UIImage(named: imageName) {
cell.configure(with: image)
}
return cell
} else if collectionView == productsCollectionView {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CosmeticCollectionViewCell.identifier, for: indexPath) as? CosmeticCollectionViewCell else {
fatalError("unable to dequeue the CosmeticCollectionViewCell")
}
let cosmetic = cosmetics[indexPath.row]
cell.configure(with: cosmetic)
return cell
}
return UICollectionViewCell()
}
}
struct Cosmetic {
let imageName: String
let name: String
let discountedPrice: String
let originalPrice: String
}
extension MainView: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == productsCollectionView {
let padding: CGFloat = 10
let width = (collectionView.frame.size.width - padding * 3) / 2
return CGSize(width: width, height: width * 1.6)
}
return CGSize(width: collectionView.frame.size.width, height: collectionView.frame.size.height)
}
}
class SearchBarView: UIView {
lazy var searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.placeholder = "Search"
return searchBar
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
addSubview(searchBar)
NSLayoutConstraint.activate([
searchBar.topAnchor.constraint(equalTo: topAnchor),
searchBar.leadingAnchor.constraint(equalTo: leadingAnchor),
searchBar.trailingAnchor.constraint(equalTo: trailingAnchor),
searchBar.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
}
in previous section i wrote down everything i am facing:(
Upvotes: 0
Views: 25