웰코발
웰코's iOS
웰코발
전체 방문자
오늘
어제
  • 분류 전체보기 (63)
    • Swift (26)
    • rxSwift (13)
    • SwiftUI (3)
    • iOS (12)
    • 기타 (1)
    • 개발관련 용어정리 (6)
    • 면접준비 (0)
    • 공공데이터 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • SWIFT
  • collectionview
  • Coordinator
  • 대기오염통계 현황
  • Observable
  • delay
  • WKWebView
  • UI
  • Scroll
  • alamofire
  • ios
  • uitableview
  • ReactorKit
  • rxswift
  • 주제구독
  • swiftUI
  • content_available
  • cell
  • 측정소정보
  • 디자인

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
웰코발

웰코's iOS

Swift

[Swift] 하이라이트 슬라이드 뷰 만들기 (UICollectionView)

2023. 4. 25. 22:42

스크롤 시 가운데 셀이 하이라이트 되고 양 옆 셀을 클릭 시 스크롤 되는 커스텀 콜렉션 뷰를 만들었다.

 

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

 

https://lsh424.tistory.com/55

 

[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
    'Swift' 카테고리의 다른 글
    • [Swift] enum 의 raw value 에 대한 회고
    • [Swift] 네이버 지도 마커 클러스터링 (Quad Tree Clustering)
    • [Swift] OSLog 사용법
    • [Swift] SkeletonView 사용법
    웰코발
    웰코발
    나의 개발 일지

    티스토리툴바