스크롤 시 가운데 셀이 하이라이트 되고 양 옆 셀을 클릭 시 스크롤 되는 커스텀 콜렉션 뷰를 만들었다.
flowLayout을 커스텀하는데 있어 이해하는 시간이 좀 필요했다.
3D적으로도 생각하다보니 더 오래걸린 것 같다.
예시는 다음과 같다.
UICollectionViewFlowLayout 정의
import Foundation
import UIKit
class ZoomAndSnapFlowLayout: UICollectionViewFlowLayout {
let activeDistance: CGFloat = 200
// 하이라이트 된 셀 크기 확대 비율
let zoomFactor: CGFloat = 0.2
override init() {
super.init()
// 가로 방향 스크롤링
scrollDirection = .horizontal
// 셀 간의 간격
minimumLineSpacing = 40
// 셀 사이즈
itemSize = CGSize(width: 230, height: 300)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// CollectionView가 해당 콘텐츠를 처음으로 표시할 때와 view가 변경되어 레이아웃이 무효화 될때 발생
// 콜렉션뷰의 사이즈 및 item의 위치 등을 결정하기 위한 초기 계산 등을 수행
// 즉 초기에 collectionview의 레이아웃을 설정할 때 호출
override func prepare() {
guard let collectionView = collectionView else { fatalError() }
let verticalInsets = (collectionView.frame.height - collectionView.adjustedContentInset.top - collectionView.adjustedContentInset.bottom - itemSize.height) / 2
let horizontalInsets = (collectionView.frame.width - collectionView.adjustedContentInset.right - collectionView.adjustedContentInset.left - itemSize.width) / 2
// 각 라인별 cell의 수를 제한 할 수 있도록 inset을 지정해줌
sectionInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets)
super.prepare()
}
// CollectionView안의 모든 요소에 대한 Layout요소들을 리턴함.
// 현재 기준 보여지는 요소들만 해당함.
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = collectionView else { return nil }
let rectAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size)
// Make the cells be zoomed when they reach the center of the screen
for attributes in rectAttributes where attributes.frame.intersects(visibleRect) {
let distance = visibleRect.midX - attributes.center.x
let normalizedDistance = distance / activeDistance
if distance.magnitude < activeDistance {
let zoom = 1 + zoomFactor * (1 - normalizedDistance.magnitude)
attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1)
attributes.zIndex = Int(zoom.rounded())
}
}
return rectAttributes
}
// 스크롤 시 스크롤이 중지되는 지점을 변경 할 수 있는 메서드
// proposedContentOffset: 스크롤이 자연스럽게 중지되는 값, velocity: 스크롤 속도
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return .zero }
// Add some snapping behaviour so that the zoomed cell is always centered
let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height)
guard let rectAttributes = super.layoutAttributesForElements(in: targetRect) else { return .zero }
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
let horizontalCenter = proposedContentOffset.x + collectionView.frame.width / 2
for layoutAttributes in rectAttributes {
let itemHorizontalCenter = layoutAttributes.center.x
if (itemHorizontalCenter - horizontalCenter).magnitude < offsetAdjustment.magnitude {
offsetAdjustment = itemHorizontalCenter - horizontalCenter
}
}
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
}
// Bounds에 변화가 있을때 마다, 함수를 호출할 지 결정함. -> InvalidateLayout(with:)을 호출.
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
// Invalidate layout so that every cell get a chance to be zoomed when it reaches the center of the screen
return true
}
override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
// invalidate된 셀이 크기를 재설정 해야할경우 No가 default, Yes일 경우 다시 계산
context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size
return context
}
}
UICollectionViewFlowLayout를 사용한 CollectionView 사용
import Foundation
import UIKit
import SnapKit
class ZoomAndSnapCollectionView: UIView {
let collectionView: UICollectionView = {
// 커스텀 UICollectionViewFlowLayout
let flowLayout = ZoomAndSnapFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
// 스크롤 감속 속도
cv.decelerationRate = .fast
//true로 할 시 커스텀 flowLayout과 꼬임
// cv.isPagingEnabled = true
cv.showsHorizontalScrollIndicator = false
return cv
}()
override init(frame: CGRect) {
super.init(frame: frame)
viewConfigure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
constraintConfigure()
}
private func viewConfigure() {
self.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.contentInsetAdjustmentBehavior = .always
collectionView.register(TestCollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
}
private func constraintConfigure() {
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
extension ZoomAndSnapCollectionView: UICollectionViewDataSource {
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) as! TestCollectionViewCell
return cell
}
}
extension ZoomAndSnapCollectionView: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
}
셀 정의
import Foundation
import UIKit
class TestCollectionViewCell: UICollectionViewCell {
let colors: [UIColor] = [.red, .blue, .brown, .gray, .lightGray, .black , .darkGray, .green, .orange]
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = colors[Int.random(in: 0...colors.count-1)]
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
해당 뷰를 컨트롤러단에 올려서 사용하면 된다.
그부분의 코드는 생략!
참고 사이트
https://demian-develop.tistory.com/21
iOS) UICollectionView custom layout에 대한 고찰- 1 (UICollectionViewFlowLayout, UICollectionViewLayout)
Collection View에서 복잡한 레이아웃을 다루기 위해선 Custom Layout을 적용시켜야 합니다. 오늘은 Custom Layout을 탐구해보려 합니다. 데이터 레이어와 프레젠테이션 레이어가 분리되어있고 레이아웃으
demian-develop.tistory.com
[iOS] CollectionView 3D 전환, carousel effect 주기 (2)
이전 글에서 간단한(?) carousel effect가 들어간 컬렉션뷰를 만들어봤었는데요. 이제 우리가 최종적으로 만들려고 하는 위와 같은 컬렉션뷰를 만들어 보겠습니다. 기존 코드에서 우리가 만들었던 se
lsh424.tistory.com
'Swift' 카테고리의 다른 글
[Swift] enum 의 raw value 에 대한 회고 (0) | 2023.05.30 |
---|---|
[Swift] 네이버 지도 마커 클러스터링 (Quad Tree Clustering) (0) | 2023.05.16 |
[Swift] OSLog 사용법 (0) | 2023.03.30 |
[Swift] SkeletonView 사용법 (0) | 2023.03.28 |
[Swift] iOS 기기 내 PDF 혹은 파일 첨부 하는 방법 (0) | 2023.03.24 |