-
[iOS] 테이블 뷰 셀 안에 컬렉션 뷰 넣기iOS/EARLY BUDDY | iOS 2020. 8. 28. 13:14
안녕하세요 :)
이번 포스팅은 얼리버디 프로젝트에서 절 힘들게 했던 가로 경로를 구현한 과정을 적어보려고 합니다.
구현할 뷰
위의 뷰를 구현할건데요. 저 뷰가 테이블 뷰 셀이에요..ㅠ
셀 안에 가로로 된 경로를 넣으려면 저는 컬렉션 뷰로 하는 게 재사용하기 편할 것 같다는 생각이 들어서
컬렉션으로 구현을 했습니다.
TableViewCell
다른 뷰에서도 똑같은 셀이 사용되기 때문에 nib로 구현을 했습니다.
이런 식으로 테이블 뷰 셀을 그리고 하단에 컬렉션 뷰를 넣었습니다.
CollectionViewCell
이번에는 컬렉션 뷰 셀을 살펴볼건데요.
저는 도보 부분의 셀과 교통수단 부분의 셀 두 개를 만들었어요.
위의 셀이 도보 부분 셀이고 아래 셀이 대중교통 부분 셀입니다!!
UITableViewCell.swift
우선 테이블 뷰 셀 파일을 살펴볼 거예요.
func registerCVCell() { horizontalPathCV.dataSource = self horizontalPathCV.delegate = self horizontalPathCV.register(UINib(nibName: "WalkingCell", bundle: nil), forCellWithReuseIdentifier: "WalkingCell") horizontalPathCV.register(UINib(nibName: "TransportationCell", bundle: nil), forCellWithReuseIdentifier: "TransportationCell") guard let subPaths = subPaths, let totalTime = totalTime else { return } for route in subPaths { if route.sectionTime > 0 { if route.trafficType == 3 { walkingCount += 1 } else { tansitionCount += 1 } } } pathTotalLength = horizontalPathCV.frame.width - CGFloat(0.5) lengthComputation(totalTime: CGFloat(totalTime)) }
위의 함수는 cellForRow 함수가 호출되었을 때 호출될 컬렉션 뷰 함수입니다.
컬렉션 뷰에 nib 파일 두 개를 register 해줍니다.
그리고 subPaths, totalTime은 서버 통신으로 받아온 경로들이 될 것이고,
walkingCount와 transitionCount를 설정하는 이유는 경로들의 아래에 ~분 이렇게 설정되는데 그것이 잘리지 않도록 컬렉션 뷰 셀의 최소 길이를 구하기 위해서 카운팅 합니다!그리고 셀의 길이는 서버 통신으로 받아오는 값에 따라 다르기 때문에 컬렉션 뷰의 총길이를 구해줍니다.
아래는 lengthComputation(totalTime:) 함수입니다.
// MARK:- 컬렉션 뷰 길이 func lengthComputation(totalTime: CGFloat) { leastWalkingLength = (pathTotalLength * 0.05).rounded() leastTransitionLength = (pathTotalLength * 0.05).rounded() remainTotalLength = pathTotalLength - (leastWalkingLength * walkingCount + leastTransitionLength * tansitionCount) oneMinuteLength = remainTotalLength / totalTime }
주석에 적어놓은 것과 같이 1분당 셀에 할당할 길이와, 셀의 최소 길이를 구하는 함수입니다.
이 값도 경로에 따라 다를 것이기 때문에 셀을 만들 때 같이 계산해줍니다.그다음에는 CollectionViewDelegateFlowLayout을 살펴볼게요.
extension SearchPathCell: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { guard let subPaths = subPaths else { return CGSize() } if subPaths[indexPath.item].sectionTime == 0 { return CGSize(width: 0.0, height: 0.0) } else if subPaths[indexPath.item].trafficType == 3 { return CGSize(width: leastWalkingLength + oneMinuteLength * CGFloat(subPaths[indexPath.item].sectionTime), height: 30.0) } else { return CGSize(width: leastTransitionLength + oneMinuteLength * CGFloat(subPaths[indexPath.item].sectionTime), height: 30.0) } } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 0 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return 0 } }
우선 셀 사이 간격이 없어야 하기 때문에 minimumLineSpacinngForSectionAt과
minimumInteritemSpacingForSectionAt 함수들에 return 0을 설정해줍니다.그리고 계속 언급했듯이 서버 통신한 경로에 따라 셀들의 width가 유동적으로 변화해야 하기 때문에
최소 길이 + 1분당 길이 * 소요시간을 계산해서 리턴해줍니다.아래는 CollectionViewDataSource 부분입니다.
// MARK:- UICollectionViewDataSource extension SearchPathCell: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { guard let subPaths = subPaths else { return 0 } return subPaths.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let subPaths = subPaths else { return UICollectionViewCell() } if subPaths[indexPath.item].trafficType == 3 { let walkingCell = collectionView.dequeueReusableCell(withReuseIdentifier: "WalkingCell", for: indexPath) as! WalkingCell walkingCell.sectionTime = subPaths[indexPath.item].sectionTime walkingCell.layoutWalkingCell() return walkingCell } else { let transportationCell = collectionView.dequeueReusableCell(withReuseIdentifier: "TransportationCell", for: indexPath) as! TransportationCell transportationCell.subPath = subPaths[indexPath.item] transportationCell.layoutTransportationCell() return transportationCell } } }
셀 개수는 경로 개수로 리턴하고, trafficType에 따라 셀을 구분해줍니다.
layout~() 함수는 셀 안의 라인 뷰 색깔과 라벨 색을 결정해주는 함수인데 컬렉션 뷰의 셀 레이아웃 작업이라서
컬렉션 뷰 셀 파일로 따로 분리해놨습니다.셀 재사용 문제
자 위와 같이 구현하면 저는 잘 구현될 것이라고 생각했는데, 시뮬을 돌려보니까 문제점이 보이더라고요.
자세히 보시면 맨 위 최적 부분 경로가 계속 바뀌는 마술이 나타납니다 ^_^
재사용 문제로 버그가 생긴 것인데요..ㅠ
재사용 문제 해결
저 문제를 해결하기 위해서 테이블 뷰 셀 부분을 수정해주었습니다.
var subPaths: [SubPath]?
원래 코드는 이렇게 통신된 값을 받아오는 것인데 한 번 통신된 후에 계속 경로가 바뀌는 것을 확인했습니다.
그래서 아래와 같이 바꿨습니다.
var subPaths: [SubPath]? { didSet { horizontalPathCV.reloadData() } }
이렇게 바꾸면 잘 작동하더라고요.
값이 들어왔다면 컬렉션 뷰를 다시 reload 하도록! 했습니다.
구현 완성
네 이렇게 저를 며칠 동안 힘들게 했던 가로경로! 구현 완성했습니다.
버그들이 며칠동안 계속 발생해서 많이 애를 먹었던 뷰라서 애증의 감정만 남았습니다...ㅎㅎ
오늘도 포스팅 끝까지 봐주셔서 감사합니다 :)
'iOS > EARLY BUDDY | iOS' 카테고리의 다른 글
[iOS] FMDB를 사용한 최근 장소 검색 기능 구현 (0) 2020.08.15 [iOS] FMDB 사용하기 (0) 2020.08.14 [iOS] Coordinator Pattern 적용 (0) 2020.07.31