티스토리 뷰

Collapsed TableView

기본 코드 ⬇️

더보기
import UIKit

class ViewController: UIViewController {
    
    let tableViewData = [
        ["1", "2", "3", "4", "5"],
        ["1", "2", "3", "4", "5"],
        ["1", "2", "3", "4", "5"],
        ["1", "2", "3", "4", "5"],
        ["1", "2", "3", "4", "5"],
    ]
    
    // MARK: -Properties
    let tableView: UITableView = {
        let tableView = UITableView()
        return tableView
    }()
    
    // MARK: -Life Cycles
    override func viewDidLoad() {
        super.viewDidLoad()
        setUI()
    }
}

// MARK: -UI
extension ViewController {
    final private func setUI() {
        setLayout()
        setBasics()
    }
    
    final private func setLayout() {
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
        
    }
    
    final private func setBasics() {
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.dataSource = self
        tableView.delegate = self
    }
}

//MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        tableViewData.count
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tableViewData[section].count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        var configuration = cell.defaultContentConfiguration()
        configuration.text = tableViewData[indexPath.section][indexPath.row]
        cell.contentConfiguration = configuration
        return cell
    }
}

extension UIViewController: UITableViewDelegate {
    public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let button = UIButton()
        button.setTitle(String(section), for: .normal)
        button.backgroundColor = .systemPurple
        button.tag = section
        button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
        return button
    }
}

extension UIViewController {
    @objc func buttonTapped(_ sender: UIButton) {
        print(sender.tag)
    }
}

터치한 섹션이 저장될 변수 하나 설정

  • 이럴 때는 Array보다 Set이 더 빠름
  • 똑같은 숫자가 들어올 가능성이 없기 때문에 Set을 쓸 수 있음
var hiddenSection = Set<Int>()

 


 

TableView의 Section Header 버튼의 event를 설정

  • 위에 만든 변수(hiddenSection)에 해당 섹션이 있으면(=섹션의 셀들이 접혀 있으면) => 해당 섹션 보여줌
  • 해당 섹션이 없으면(=섹션의 셀들이 펼쳐져 있으면) => 해당섹션 숨김
extension ViewController {
    @objc func buttonTapped(_ sender: UIButton) {
        let sectionTag = sender.tag
        if hiddenSection.contains(sectionTag) {
            showSection(section: sectionTag)
        } else {
            hideSection(section: sectionTag)
        }
        tableView.reloadData()
    }
    
    func hideSection(section: Int) {
        hiddenSection.insert(section)
    }
    
    func showSection(section: Int) {
        hiddenSection.remove(section)
    }
}

 

버튼 이벤트에 따라 numberOfRowsInSection을 다르게 구성

  • 위에 만든 변수(hiddenSection)에 해당 섹션이 있으면(=섹션의 셀들이 접혀 있으면) => 해당 섹션에 셀을 0개 보여줌
  • 해당 섹션이 없으면(=섹션의 셀들이 펼쳐져 있으면) => 해당섹션이 가진 셀 다 보여줌
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return hiddenSection.contains(section) ? 0 : tableViewData[section].count
    }


애니메이션 추가

  • insertRows, deleteRows는 Rows를 수정할 수 있을 뿐만 아니라 tableView를 reload하는 기능까지 가지고 있음
extension ViewController {
    func hideSection(section: Int, indexPaths: [IndexPath]) {
        hiddenSection.insert(section)
        tableView.deleteRows(at: indexPaths, with: .fade)
    }
    
    func showSection(section: Int, indexPaths: [IndexPath]) {
        hiddenSection.remove(section)
        tableView.insertRows(at: indexPaths, with: .fade)
    }
}

for-in 반복문을 통해 함수에 indexPath 배열을 전달

extension ViewController {
    @objc func buttonTapped(_ sender: UIButton) {
        let sectionTag = sender.tag
        var indexPaths = [IndexPath]()
        for row in 0..<tableViewData[sectionTag].count {
            let indexPath = IndexPath(row: row, section: sectionTag)
            indexPaths.append(indexPath)
        }
        
        hiddenSection.contains(sectionTag)
        ? showSection(section: sectionTag, indexPaths: indexPaths)
        : hideSection(section: sectionTag, indexPaths: indexPaths)
    }
}

고차함수로 함수 줄이기

extension ViewController {
    @objc func buttonTapped(_ sender: UIButton) {
        let sectionTag = sender.tag
        var indexPaths = tableViewData[sectionTag].indices.map { IndexPath(row: $0, section: sectionTag) }
        
        hiddenSection.contains(sectionTag)
        ? showSection(section: sectionTag, indexPaths: indexPaths)
        : hideSection(section: sectionTag, indexPaths: indexPaths)
    }
    
    func hideSection(section: Int, indexPaths: [IndexPath]) {
        hiddenSection.insert(section)
        tableView.deleteRows(at: indexPaths, with: .fade)
    }
    
    func showSection(section: Int, indexPaths: [IndexPath]) {
        hiddenSection.remove(section)
        tableView.insertRows(at: indexPaths, with: .fade)
    }
}

결과물


InfiniteScrollView

 

더보기
import UIKit

class ViewController: UIViewController {
    
    private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
    private let colors: [UIColor] = [.orange]
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        flowLayout()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }


}

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        colors.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        cell.backgroundColor = colors[indexPath.item]
        return cell
    }
}

//MARK: -UI
extension ViewController {
    final private func configureUI() {
        flowLayout()
        setConstraints()
    }
    
    final private func flowLayout() {
        guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { fatalError() }
        let width = collectionView.frame.width
        let height = collectionView.frame.height
        flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        flowLayout.itemSize = CGSize(width: width, height: height)
        flowLayout.minimumLineSpacing = 0
        flowLayout.minimumInteritemSpacing = 0
        flowLayout.scrollDirection = .horizontal
    }
    
    final private func setConstraints() {
        view.backgroundColor = .systemBackground
        collectionView.backgroundColor = .black
        collectionView.dataSource = self
        collectionView.isPagingEnabled = true
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        view.addSubview(collectionView)
        
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 70),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            collectionView.heightAnchor.constraint(equalTo: collectionView.widthAnchor, multiplier: 1)
        ])
    }
}

큰 개념

눈속임을 통해 inifite 느낌을 줄 수 있도록

실제 시작은 red지만, red 앞에 마지막 색인 yellow를 두고 마지막색인 yellow 앞에 red를 두어 마치 돌아가는 것처럼 보여주면 됨


dataSource 추가

⭐️ yellow, red, green이 중복되도록 구성

private let colors: [UIColor] = [.yellow, .red, .green, .blue, .purple, .yellow, .red, .green]

 

Delegate

collectionView.delegate = self
//MARK: - UICollectionViewDelegate
extension ViewController: UICollectionViewDelegate {
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        <#code#>
    }
}

 

CellWidth 설정

1. cell의 넓이를 담을 변수 하나 생성 (cellWidth)

private var cellWidth: CGFloat = 0

2. cellWidth에 collectionView의 width를 할당 후, collectionView의 시작점(x)을 cellWidth로 설정

이렇게 되면 colors 배열의 두번째에 있던 red가 collectionView의 시작점이 됨

    final private func flowLayout() {
        guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { fatalError() }
        let width = collectionView.frame.width
        let height = collectionView.frame.height
        flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        flowLayout.itemSize = CGSize(width: width, height: height)
        flowLayout.minimumLineSpacing = 0
        flowLayout.minimumInteritemSpacing = 0
        flowLayout.scrollDirection = .horizontal
        cellWidth = width //🍎
        collectionView.contentOffset.x = width // 🍎시작점을 2번째(red)로 바꾸는 코드
    }

 

Delegate 메서드 구현

만약 현재 페이지가 가짜 1번 페이지라면 (= colors 배열에서 두 번째 red)

=> scrollView의 스크롤을 (우리가 설정한) 첫 번째 페이지로 가고 (= colors 배열에 첫 번째 red)

 

만약 현재 페이지가 가짜 마지막 페이지라면 (= colors 배열에서 여섯번째 yellow)

=>  scrollView의 스크롤을 진짜 첫 번째 페이지로 가고 (= colors 배열에 첫 번째 red)

//MARK: - UICollectionViewDelegate
extension ViewController: UICollectionViewDelegate {
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        performInfiniteScroll(scrollView: scrollView)
    }
    
    func performInfiniteScroll(scrollView: UIScrollView) {
  	 	guard colors.count > 1 else { return }
    
        let currentPage = scrollView.contentOffset.x
        
        let firstPage = cellWidth
        let fakeFirstPage = cellWidth * CGFloat(colors.count - 2)

        let lastPage = cellWidth * CGFloat(colors.count - 3)
        let fakeLastPage: CGFloat = 0

        if currentPage == fakeFirstPage {
            // 만약 현재 첫 번째 페이지(=red)라면?
            scrollView.contentOffset.x = firstPage
        } else if currentPage == fakeLastPage {
            scrollView.contentOffset.x = lastPage
        }
    }
}

 

UIEdgeInset

다음 셀이 살짝 보이게 만들기 위해 UIEdgeInset의 값을 조정하면 됨

    final private func flowLayout() {
        guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { fatalError() }
        let width = collectionView.frame.width
        let height = collectionView.frame.height
        let left: CGFloat = colors.count > 1 ? -20 : 0 // 🍎🍎🍎
        flowLayout.sectionInset = UIEdgeInsets(top: 0, left: left, bottom: 0, right: 0) // 🍎🍎🍎
        flowLayout.itemSize = CGSize(width: width, height: height)
        flowLayout.minimumLineSpacing = 0
        flowLayout.minimumInteritemSpacing = 0
        flowLayout.scrollDirection = .horizontal
        cellWidth = width
        collectionView.contentOffset.x = width // 시작점을 2번째(red)로 바꾸는 코드
    }

indicator를 숨겨야 진짜 무한 스크롤처럼 보임

collectionView.showsHorizontalScrollIndicator = false

'순진이의 하루 > study정리' 카테고리의 다른 글

15. MapKit2_2022.09.26 (1)  (0) 2022.09.26
13. async/await_2022.09.19  (0) 2022.09.19
11. Result Type_2022.09.07  (0) 2022.09.19
9. URLSession_2022.08.31  (0) 2022.08.31
8. 고차함수, UIViewPropertyAnimator_2022.08.29  (0) 2022.08.29
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크