62. 스파르타 코딩 클럽 - 최종 팀 프로젝트 #4
🎯 Trouble Shooting
[ 1. 테이블 뷰 구분선 가출 ]
UIView로 구분선을 만들어서 TableView Cell 맨 아래 부분에 표시되도록 구현했는데
셀을 위아래로 스크롤하다가 돌아오면 위 쪽 Cell에 구분선이 사라지는 문제가 발생했다.
이전에도 프로젝트를 진행할 때 팀원분 중 한 분에게 이런 문제가 발생했던 기억이 있는데
셀 재사용 처리의 문제인 줄 알고
// 셀 재사용 시 상태 초기화
override func prepareForReuse() {
super.prepareForReuse()
}
이 메서드를 작성했는데도 구분선이 계속 사라지는 문제가 있었다.
구글링도 하고 Perplexity 한테도 물어보면서 문제를 해결했다.
문제는 AutoLayout이었다.
separatorView.snp.makeConstraints { make in
// make.top.equalTo(openNowLabel.snp.bottom).offset(26)
make.bottom.equalToSuperview() // 해당 코드로 구분선 사라짐 해결
make.leading.trailing.equalToSuperview()
make.height.equalTo(2)
}
나는 구분선의 위치를 top을 통해서 잡고 있었는데
구분선의 위치를 bottom.equalToSuperView()로 수정하면서 문제를 해결했다.
👨🏻💻 오늘의 작업
[ 1. 매장 카운드 라벨과 정렬 버튼 AutoLayout을 비율로 변경 ]
// 매장 카운드
storeCountLabel.snp.makeConstraints { make in
make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top).offset(316)
make.leading.equalToSuperview().offset(20)
}
처음 AutoLayout을 잡았을 때 아이폰 13 mini를 기준으로 safeAreaLayoutGuide의 top으로부터 316만큼 떨어지게 AutoLayout을 잡았다. 그러나 이것의 문제는 다른 기기에서 (화면이 더 크거나 작은 경우) 내가 원하는 sortButton의 리스트들이 아래로 보이게 하는 동작이 안될 수도 있다는 문제가 있다.
따라서 이것을 각 기기의 safeAreaHeight의 비율로 Autolayout을 잡으면 어떤 기기에서도 비슷한 위치에 있기 때문에
내가 원하는 동작을 구현할 수 있다.
storeCountLabel.snp.makeConstraints { make in
let safeAreaHeight = self.view.safeAreaLayoutGuide.layoutFrame.height
// iPhone 13 mini : safeAreaHeight: 812.0
// 원래 원했던 offset인 316을 812로 나눠서 비율 찾기 → 0.3891625...
// 비율을 0.389로 설정
make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top).offset(safeAreaHeight * 0.389)
make.leading.equalToSuperview().offset(20)
}
각 기기마다 Autolayout을 설정할 때 기기 별 safeAreaHeight를 구하고 원하던 위치의 비율인 0.389를 곱해서
storeCountLabel의 top을 설정한다.
그러면 모든 기기에서 내가 원했던 위치에 라벨과 정렬버튼이 위치하게 되며,
원했던 정렬 버튼의 리스트들이 아래로 표시가 구현된다
[ 2. TableView + RxDataSource 리펙토링 ]
2.1 테이블 뷰 셀 등록
tableView.register(DetailTableViewCell.self,
forCellReuseIdentifier: DetailTableViewCell.identifier)
2.2 테이블 뷰 바인딩
private func bindTableView() {
let dataSource = RxTableViewSectionedAnimatedDataSource<StoreSection>(
configureCell: { dataSource, tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: DetailTableViewCell.identifier, for: indexPath) as! DetailTableViewCell
cell.configureView(with: item)
return cell
}
)
storeInfos
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
// 애니메이션 확인을 위한 테이블 뷰 클릭시 이벤트
tableView.rx.itemSelected // 웹뷰 모달이 될 예정 Action
.withUnretained(self)
.bind { vc, indexPath in
var test = vc.storeInfos.value
let random = Int.random(in: 1...1000)
test[0].items.insert(.init(displayName: "애니메이션 테스트 \(random)", formattedAddress: "ㅇㅇㅇㅇ", latitude: 33.44, longitude: 44.55, rating: 4.5, googleMapsURI: "ㅇㅇㅇㅇㅇ", userRatingCount: random, photosNames: ["ㄴㅇㄹㄴㅇ"]), at: indexPath.row)
vc.storeInfos.accept(test)
}.disposed(by: disposeBag)
}
2.3 데이터소스 작업
import Foundation
import RxDataSources
struct StoreInfo: Equatable {
let displayName: String
let formattedAddress: String
let latitude: Double
let longitude: Double
let rating: Double
let googleMapsURI: String
let userRatingCount: Int
let photosNames: [String]
}
extension StoreInfo: IdentifiableType {
var identity: String { return displayName } // 고유 식별자로 가게명 사용
}
struct StoreSection {
var items: [StoreInfo]
}
extension StoreSection: AnimatableSectionModelType {
typealias Item = StoreInfo
typealias Identity = String
var identity: String { "default" }
init(original: StoreSection, items: [StoreInfo]) {
self = original
self.items = items
}
}
2.4 DetailTableViewCell에서 configureView() 작업
func configureView(with storeInfo: StoreInfo) {
storeNameLabel.text = storeInfo.displayName
rateLabel.text = "\(storeInfo.rating)"
userRateCountLabel.text = "(\(storeInfo.userRatingCount))"
addressLabel.text = storeInfo.formattedAddress
// openNowLabel.text =
}