티스토리 뷰
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