Skip to content

Instantly share code, notes, and snippets.

@TuenTuenna
Last active September 3, 2021 07:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TuenTuenna/48104c85f4ffe19caa46a504fafee7de to your computer and use it in GitHub Desktop.
Save TuenTuenna/48104c85f4ffe19caa46a504fafee7de to your computer and use it in GitHub Desktop.
콤포지셔널 레이아웃

Swift UIKit 콤포지셔널 레이아웃 설정 기록용

//
//  MyCollectionVC.swift
//  dynamic_table_view
//
//  Created by Jeff Jeong on 2020/11/14.
//  Copyright © 2020 Tuentuenna. All rights reserved.
//

import Foundation
import UIKit
import ViewAnimator


class MyCollectionVC: UIViewController {
    
    
    @IBOutlet var mySegmentControl: UISegmentedControl!
    @IBOutlet var myCollectionView: UICollectionView!
    
    // 임시배열
    var tempArray : [Any] = []
    
    // 아래에서 위로 올라오는 애니메이션 배열
    private let animations = [
        AnimationType.vector(CGVector(dx: 0, dy: 50)),
//        AnimationType.zoom(scale: 0.8),
//        AnimationType.rotate(angle: CGFloat.pi/2)
    ]
    
    fileprivate let systemImageNameArray = [
        "moon", "zzz", "sparkles", "cloud", "tornado", "smoke.fill", "tv.fill", "gamecontroller", "headphones", "flame", "bolt.fill", "hare", "tortoise", "moon", "zzz", "sparkles", "cloud", "tornado", "smoke.fill", "tv.fill", "gamecontroller", "headphones", "flame", "bolt.fill", "hare", "tortoise", "ant", "hare", "car", "airplane", "heart", "bandage", "waveform.path.ecg", "staroflife", "bed.double.fill", "signature", "bag", "cart", "creditcard", "clock", "alarm", "stopwatch.fill", "timer"
    ]
    
    
    let dummyArray = [
        ["하하하1", "호호호1", "히히히1"],
        ["하하하2", "호호호2", "히히히2"],
        ["하하하3", "호호호3", "히히히3"],
        ["하하하4", "호호호4", "히히히4"],
        ["하하하5", "호호호5", "히히히5"]
    ]
    
    
    //MARK: - Lifecycles
    override func viewDidLoad() {
        // 정대리 유튜브 커뮤니티 상속 13번 글 참고
        super.viewDidLoad()
        print("MyCollectionVC - viewDidLoad() called")
        
        // 콜렉션 뷰에 대한 설정
        myCollectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        myCollectionView.dataSource = self
        myCollectionView.delegate = self
        
    
        // 닙파일을 가져온다
        let myCustomCollectionViewCellNib = UINib(nibName: String(describing: MyCustomCollectionViewCell.self), bundle: nil)
        
        // 가져온 닙파일로 콜렉션뷰에 쎌로 등록한다
        self.myCollectionView.register(myCustomCollectionViewCellNib, forCellWithReuseIdentifier: String(describing: MyCustomCollectionViewCell.self))
        
        // 콜렉션뷰의 콜렉션뷰 레이아웃을 설정한다.
        self.myCollectionView.collectionViewLayout = createCompositionalLayoutForFirst()
     
        
        // 리프레시 컨트롤 테이블뷰에 달기
        let refreshControl = UIRefreshControl()
        
        refreshControl.tintColor = #colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1)
        
        refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
        
        let boldFont = UIFont.boldSystemFont(ofSize: 20)
        let attributes : [NSAttributedString.Key : Any] = [
            .font : boldFont,
            .foregroundColor : UIColor.init(cgColor: #colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1))
        ]
        
        refreshControl.attributedTitle = NSAttributedString(string: "땡겨요~!", attributes: attributes)
        
        self.myCollectionView.refreshControl = refreshControl
        
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
            [weak self] in
            guard let self = self else { return }
            // 테이블뷰와 연결된 데이터 변경
            self.tempArray = self.systemImageNameArray
            
            // 테이블뷰의 UI 갱신
            self.myCollectionView.reloadData()
            
            self.myCollectionView.performBatchUpdates({
                // 애니메이션 돌리기
                UIView.animate(views: self.myCollectionView.orderedVisibleCells,
                               animations: self.animations)
            }, completion: nil)
            
           
        })
        
    } // viewDidLoad()
    
    
    @objc fileprivate func handleRefresh(sender: AnyObject){
        print("MyCollectionVC - handleRefresh() called")
        // 기존 데이터 지우기
        self.tempArray.removeAll()
        // 사라지는 애니메이션 처리
        UIView.animate(views: self.myCollectionView.orderedVisibleCells,
                       animations: self.animations,
                       reversed: true, // 애니메이션 반전
                       initialAlpha: 1.0, // 처음에는 보였다가
                       finalAlpha: 0.0, // 끝에 안보임,
                       options: [.curveEaseIn],
                       completion: {
                        self.myCollectionView.reloadData()
                       })
        self.myCollectionView.refreshControl?.endRefreshing()
        
        //TODO: - API 땡겨서 데이터 가져오기
        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { [weak self] in
            guard let self = self else { return }
            
            // 진동주기
            let vibrate = UIImpactFeedbackGenerator(style: .light)
            vibrate.impactOccurred()
            
            // 콜렉션뷰와 연결된 데이터 변경
            self.tempArray = self.systemImageNameArray
            // 콜렉션뷰의 UI 갱신
            self.myCollectionView.reloadData()
            self.myCollectionView.performBatchUpdates({
                // 애니메이션 돌리기
                UIView.animate(views: self.myCollectionView.orderedVisibleCells,
                               animations: self.animations,
                               reversed: false,
                               initialAlpha: 0.0,
                               finalAlpha: 1.0,
    //                           options: [.curveLinear],
                               completion: nil)
            }, completion: nil)
        })
    }

    
    @IBAction func onCollectionViewTypeChanged(_ sender: UISegmentedControl) {
        print("MyCollectionVC - onCollectionViewTypeChanged() called / sender.selectedSegmentIndex : \(sender.selectedSegmentIndex)")
        switch sender.selectedSegmentIndex {
        case 0:
            // 테이블뷰 형태
            self.myCollectionView.collectionViewLayout = createCompositionalLayoutForFirst()
        case 1:
            // 2 x 2 그리드 형태
            self.myCollectionView.collectionViewLayout = createCompositionalLayoutForSecond()
        case 2: // 3 x 3 그리드 형태
            self.myCollectionView.collectionViewLayout = createCompositionalLayoutForThird()
        default:
            break
        }
    }
    
} //


//MARK: - 콜렉션뷰 콤포지셔널 레이아웃 관련
extension MyCollectionVC {
    
    enum MySection {
        case one, two
    }
    
  
    
    fileprivate func configLayout() -> UICollectionViewLayout{
        print("MyCollectionVC - configLayout() called")
        
        let layout = UICollectionViewCompositionalLayout {
            sectionIndex, _ in
//            let sectionData = dummyArray[sectionIndex]
            if sectionIndex == 1 {
                return firstSectionLayout()
            } else {
                return firstSectionLayout()
            }
        }
        let config = UICollectionViewCompositionalLayoutConfiguration()
        config.interSectionSpacing = 10
        layout.configuration = config
        return layout
    }
    
    fileprivate func firstSectionLayout () -> NSCollectionLayoutSection{
            print("MyCollectionVC - makeLayoutSection() called ")
    
            // 아이템사이즈
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
    
            let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
        
        
    
            // 아이템 간격
            layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
    
            // 아이템 그룹 크기
            var layoutGroupHeight = NSCollectionLayoutDimension.fractionalHeight(1/3)
            var layoutGroupWidth = NSCollectionLayoutDimension.fractionalWidth(1)
            
            let layoutGroupSize = NSCollectionLayoutSize(widthDimension: layoutGroupWidth, heightDimension: layoutGroupHeight)
//        let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem, layoutItem])
            let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitem: layoutItem, count: 10)
        
            layoutGroup.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
        
//            let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
            let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
            return layoutSection
        }
    
//    fileprivate func makeLayoutSection (with section: Int) -> NSCollectionLayoutSection{
//        print("MyCollectionVC - makeLayoutSection() called / section: \(section)")
//
//        // 아이템사이즈
//        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
//
//        let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
//
//        // 아이템 간격
//        layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
//
//        var layoutGroupHeight = NSCollectionLayoutDimension.fractionalWidth(1)
//
//        if section == 1 {
//
//        } else if section == 2 {
//
//        } else {
//
//        }
//
//    }
    
    
    // 콤포지셔널 레이아웃 설정
    fileprivate func createCompositionalLayoutForFirst() -> UICollectionViewLayout {
        print("createCompositionalLayoutForFirst() called")
        // 콤포지셔널 레이아웃 생성
        let layout = UICollectionViewCompositionalLayout{
            // 만들게 되면 튜플 (키: 값, 키: 값) 의 묶음으로 들어옴 반환 하는 것은 NSCollectionLayoutSection 콜렉션 레이아웃 섹션을 반환해야함
            (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            
            // 아이템에 대한 사이즈 - absolute 는 고정값, estimated 는 추측, fraction 퍼센트
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
            
            // 위에서 만든 아이템 사이즈로 아이템 만들기
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            
            // 아이템 간의 간격 설정
            item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
            
            // 변경할 부분
            let groupHeight =  NSCollectionLayoutDimension.fractionalWidth(1/3)
            
            // 그룹사이즈
            let grouSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: groupHeight)
            
            // 그룹사이즈로 그룹 만들기
//            let group = NSCollectionLayoutGroup.horizontal(layoutSize: grouSize, subitems: [item, item, item])
            
            // 변경할 부분
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: grouSize, subitem: item, count: 1)
            
            // 그룹으로 섹션 만들기
            let section = NSCollectionLayoutSection(group: group)
//            section.orthogonalScrollingBehavior = .groupPaging
            
            // 섹션에 대한 간격 설정
            section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
            return section
        }
        return layout
    }
    
    // 콤포지셔널 레이아웃 설정
    fileprivate func createCompositionalLayoutForSecond() -> UICollectionViewLayout {
        print("createCompositionalLayoutForSecond() called")
        // 콤포지셔널 레이아웃 생성
        let layout = UICollectionViewCompositionalLayout{
            // 만들게 되면 튜플 (키: 값, 키: 값) 의 묶음으로 들어옴 반환 하는 것은 NSCollectionLayoutSection 콜렉션 레이아웃 섹션을 반환해야함
            (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            
            // 아이템에 대한 사이즈 - absolute 는 고정값, estimated 는 추측, fraction 퍼센트
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
            
            // 위에서 만든 아이템 사이즈로 아이템 만들기
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            
            // 아이템 간의 간격 설정
            item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
            
            // 변경할 부분
            let groupHeight =  NSCollectionLayoutDimension.fractionalWidth(1/2)
            
            // 그룹사이즈
            let grouSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: groupHeight)
            
            // 그룹사이즈로 그룹 만들기
//            let group = NSCollectionLayoutGroup.horizontal(layoutSize: grouSize, subitems: [item, item, item])
            
            // 변경할 부분
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: grouSize, subitem: item, count: 2)
            
            // 그룹으로 섹션 만들기
            let section = NSCollectionLayoutSection(group: group)
//            section.orthogonalScrollingBehavior = .groupPaging
            
            // 섹션에 대한 간격 설정
            section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
            return section
        }
        return layout
    }
    
    // 콤포지셔널 레이아웃 설정
    fileprivate func createCompositionalLayoutForThird() -> UICollectionViewLayout {
        print("createCompositionalLayoutForThird() called")
        // 콤포지셔널 레이아웃 생성
        let layout = UICollectionViewCompositionalLayout{
            // 만들게 되면 튜플 (키: 값, 키: 값) 의 묶음으로 들어옴 반환 하는 것은 NSCollectionLayoutSection 콜렉션 레이아웃 섹션을 반환해야함
            (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            
            // 아이템에 대한 사이즈 - absolute 는 고정값, estimated 는 추측, fraction 퍼센트
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
            
            // 위에서 만든 아이템 사이즈로 아이템 만들기
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            
            // 아이템 간의 간격 설정
            item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
            
            // 변경할 부분
            let groupHeight =  NSCollectionLayoutDimension.fractionalWidth(1/3)
            
            // 그룹사이즈
            let grouSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: groupHeight)
            
            // 그룹사이즈로 그룹 만들기
//            let group = NSCollectionLayoutGroup.horizontal(layoutSize: grouSize, subitems: [item, item, item])
            
            // 변경할 부분
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: grouSize, subitem: item, count: 3)
            
            // 그룹으로 섹션 만들기
            let section = NSCollectionLayoutSection(group: group)
//            section.orthogonalScrollingBehavior = .groupPaging
            
            // 섹션에 대한 간격 설정
            section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
            return section
        }
        return layout
    }
    
}


// 데이터 소스 설정 - 데이터와 관련된 것들
extension MyCollectionVC: UICollectionViewDataSource {
    
    // 각 섹션에 들어가는 아이템 갯수
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.tempArray.count
    }
    
    // 각 콜렉션뷰 쎌에 대한 설정
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: MyCustomCollectionViewCell.self), for: indexPath) as! MyCustomCollectionViewCell
        
        if self.tempArray.count > 0 {
            cell.imageName = self.tempArray[indexPath.item] as! String
        }
        
        return cell
    }
    
    
}

// 콜렉션뷰 델리겟 - 액션과 관련된 것들
extension MyCollectionVC: UICollectionViewDelegate {
    
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment