일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- task cancel
- swift custom ui
- SWIFT
- 타임라인 포맷팅
- claen architecture
- domain data
- swift 백준
- coordinator
- identifiable
- custombottomsheet
- traits
- swift bottomsheet
- swift 점선
- BFS
- tusit font 추가 방법
- RxSwift
- swift dashed line
- task cancellation
- swift navigationcontroller
- UIKit
- button configuration
- custom navigation bar
- Tuist
- rxdatasources
- uikit toast
- custom ui
- swift concurrency
- paragraph style
- DP
- reactorkit
- Today
- Total
김경록의 앱 개발 여정
[Swift UIKit] 비회원은 못보는 TableView Section, 어떻게 할까? 본문
개요
한창 열심히 운동할 때 애용했던 앱이 있습니다.
BurnFit이라는 앱인데요, 해당 앱은 위 이미지처럼 유료 요금제를 사용하는 유저에게만 제공하는 화면이 있었습니다.
이 화면은 하나의 UICollectionView로 구성되어 있을 것으로 예상되는데, 유저의 상태에 따라 다른 콘텐츠를 보여주는 구조입니다.
저도 최근 팀 프로젝트를 진행하며 비슷한 기능을 구현해야 했습니다.
디자인 시안에 따르면, 카테고리 선택 여부에 따라 보이는 화면이 달라져야 했습니다.
저희 앱은 기본적으로 회원가입 없이 사용할 수 있지만, 글쓰기와 같이 특정 화면 및 기능은 회원만 접근 가능하도록 제한하고 있었습니다.
이번 글에서는 해당 기능을 구현하며 겪었던 고민과 해결 과정을 공유해보려 합니다.
참고: 아래 예제 코드에서는 가독성을 위해 일부 네이밍을 변경하거나 코드 일부를 생략했음을 양해 부탁드립니다.
Section 나누기
위 이미지처럼 화면은 두 가지 섹션으로 구성되어 있었습니다.
- 인기 팁 모아보기
- 관심 카테고리
RxSwift와 ReactorKit 조합을 사용 중이었기 때문에, 섹션 나누기에는 RxDataSource를 활용했습니다.
Section ModleType
// SectionModel
struct HomeTableViewSection {
var sectionType: SectionType
var items: [Item]
enum SectionType {
....
}
}
extension HomeTableViewSection: SectionModelType {
typealias Item = Tip
init(original: HomeTableViewSection, items: [Item]) {
self = original
self.items = items
}
}
RxTableViewSectionedReloadDataSource와 데이터 바인딩
// ViewController
private let dataSource = RxTableViewSectionedReloadDataSource<HomeTableViewSection>(
configureCell: { dataSource, tableView, indexPath, item in
guard let cell = tableView.dequeueReusableCell(
withIdentifier: PostListCell.reuseIdentifier,
for: indexPath
) as? PostListCell else { return UITableViewCell() }
cell.configureTitleLabelText(item.title)
}
)
func bind(reactor: MyReactor) {
reactor.state
.map { $0.sections }
.bind(to: myTableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
기존 DiffableDataSource와 굉장히 유사했기 때문에 데이터 바인딩 방식만 조금 신경 써주면 됐습니다.
UITableViewHeaderFooterView
문제:
헤더는 유지해야 하며, 로그인 상태가 아니거나, 관심사를 설정하지 않은 경우에는 해당 섹션에서 별도의 안내 뷰를 표시해야 했습니다.
해결:
UITableViewHeaderFooterView를 활용하여 각 섹션의 Footer로 표시했습니다.
- Header는 유지하면서 Footer를 추가하면 레이아웃의 복잡성을 줄이고, 각 뷰 간 연관성을 유지할 수 있었습니다.
푸터 뷰 추가하기
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
switch dataSource.sectionModels[section].sectionType {
case .first:
return FirstFooter()
case .second:
let secondFooter = SecondFooter()
secondFooter.delegate = self
return secondFooter
}
}
TableView Delegate
특정 조건에 따라 나오는 푸터이기 때문에, 많은 설정을 거쳤습니다.
// 사실 UIView 형태를 요구하기때문에 일반적인 뷰로 작성해도 괜찮다. 포인트는 '푸터 위치'를 사용하기 위함임
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
switch dataSource.sectionModels[section].sectionType {
case .first:
return FirstFooter()
// 이게 관심 카테고리 선택 푸터!
case .second:
let secondFooter = SecondFooter()
secondFooter.delegate = self
return secondFooter
}
}
섹션별로 각각의 푸터를 제공해 줬습니다.
모든 유저가 해당 푸터를 보게 되는 건 원치 않기 때문에
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
switch dataSource.sectionModels[section].sectionType {
case .first:
return 50
case .second:
if coordinator?.checkIsLoggedin() == false || 선택된 카테고리가 없다면() {
return 250
} else {
return 0
}
}
}
로그인 상태와 관심사 설정 여부를 판단하고, 이를 기반으로 Footer의 높이를 변경하여
원하는 UI를 구성할 수 있었습니다.
또한, Second Footer는 델리게이트를 위임하고 있는 모습을 볼 수 있는데
바로 위 코드에서 보시다시피 로그인 상태 확인의 책임을 코디네이터가 짊어지고 있기 때문입니다.
ViewController에서 관리되는 코디네이터를 사용하여 로그인 여부를 확인하고, 해당 푸터의 버튼을 눌렀을 때 로그인을 유도할 것인지, 관심사를 선택하러 이동시켜 줄지를 선택할 수 있겠죠
마치며..
RxDataSource 활용을 통한 섹션 분리
RxDataSource는 기존 DiffableDataSource와 유사한 점이 많아 학습 부담이 적었습니다. 다만 의도치 않게도 Header에 separator가 생기는 문제가 있었는데, Separotor Style을 변경하고, 커스텀 셀에서 밑줄을 추가하는 방식으로 해결했습니다.
커스텀 Header/Footer의 활용
처음으로 섹션별로 다른 Header와 Footer를 적용해 보며 UI의 유연성을 높이는 경험을 했습니다. 특히 Footer를 활용해 특정 조건에서만 표시되는 뷰를 간단히 추가할 수 있었습니다.
'Trouble Shooting' 카테고리의 다른 글
[Swift] 여러 상황에 대응하는 레이아웃 만들기(with SnapKit, update Layout) (0) | 2025.03.08 |
---|---|
[Swift] Launch Screen에 Asset.xcassets 이미지가 적용되지 않는다면 (0) | 2025.02.01 |
[Swift UIKit] 공백이 포함된 커스텀 뷰 구현기 (0) | 2025.01.10 |
[Swift] 코디네이터 패턴과 JWT 기반 오토로그인 (0) | 2025.01.08 |
[Swift UIKit]present의 주체 (modal을 표시하는건 누구의 역할일까?) (0) | 2025.01.08 |