네이버 웹툰 홈 화면 (세그먼트 컨트롤 사용, 하단 바 중점)
페이지 뷰 컨트롤러를 사용하여 페이징되는 화면을 구현하고
좌우 스크롤시 x값에 따라 움직이는 언더바를 어떻게 구현할지 생각해본다.
구현 아이디어
SegnmentControl 과 UIPageViewController를 이용하여 제스처나 버튼을 통해 페이지 이동시 segmentControl 의 언더바 또한 페이징 동작에 맞춰 동적으로 이동할 수 있게 하였다.
구현과정
먼저 세그먼트 컨트롤을 커스텀 하는 과정입니다 (참고: https://ios-development.tistory.com/963)
import UIKit
class UnderlineSegmentedControl: UISegmentedControl {
//underlineView 추가(autolayout이 아닌, frame을 이용하므로 layoutSubviews에서 호출
lazy var underlineView: UIView = {
let width = self.bounds.size.width / CGFloat(self.numberOfSegments)
let height = 2.0
let xPosition = CGFloat(self.selectedSegmentIndex * Int(width))
let yPosition = self.bounds.size.height - 4.0
let frame = CGRect(x: xPosition, y: yPosition, width: width, height: height)
let view = UIView(frame: frame)
view.restorationIdentifier = "underbar"
view.backgroundColor = .green
self.addSubview(view)
return view
}()
func removeBackgroundAndDivider() {
let image = UIImage()
//회색 배경과 divider를 지우는 코드
self.setBackgroundImage(image, for: .normal, barMetrics: .default)
self.setBackgroundImage(image, for: .selected, barMetrics: .default)
self.setBackgroundImage(image, for: .highlighted, barMetrics: .default)
self.setDividerImage(image, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
}
override func awakeFromNib() {
}
override func layoutSubviews() {
super.layoutSubviews()
self.bringSubviewToFront(underlineView)
}
}
다음과 같이 segmentControl의 기본UI인 회색배경과 구분선을 삭제하고 underlineVIew를 추가하였습니다.
(# 처음부터 let image = UIImage를 녹색 언더바로 만든다면?)
이 과정에서 underlineView가 가려지는 현상이 발생해 self.bringSubviewToFront(underlineView)를 통해 underlineView를 앞쪽으로 가져왔습니다
다음으로 SegmentControl과 UIPageViewController가 세팅되어있다는 전제하에서 2가지를 처리해주어야 합니다.
- segmentControl 클릭시 언더바의 이동
- 뷰 스크롤시 segmentControl의 언더바 이동
먼저 segmentControl 클릭시 언더바의 이동 입니다.
@IBAction func clickSegment(_ sender: UnderlineSegmentedControl) {
let segmentIndex = self.segmentControl.selectedSegmentIndex
pageViewController.setViewcontrollersFromIndex(index: segmentIndex)
let underlineFinalXPosition = (self.view.frame.width / CGFloat(segmentControl.numberOfSegments)) * CGFloat(segmentControl.selectedSegmentIndex)
print(underlineFinalXPosition)
UIView.animate(
withDuration: 0.1,
animations: {
//restorationID를 통해 underlineView의 위치 지정
if let underline = self.segmentControl.subviews.filter({$0.restorationIdentifier == "underbar"}).first{
underline.frame.origin.x = underlineFinalXPosition
} else {
print("cannot find underlineView")
}
}
)
}
뷰 스크롤시 segmentControl의 언더바 이동
extension PageViewController: UIPageViewControllerDelegate, UIScrollViewDelegate {
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
completeHandler?(currentPageIndex)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = self.view.frame.width
// 각각의 뷰의 위치를 변환
for vc in vcArray {
let pagePoint = vc.view.convert(CGPoint(), to: view)
if pagePoint.x > CGFloat(0.0) && pagePoint.x < pageWidth {
if let estimatePage = vcArray.firstIndex(where: {$0 == vc}) {
estimateOffSetX = CGFloat(estimatePage) * pageWidth - pagePoint.x
// 계산된 x값을 확인해보면 살짝씩 오차가 있음
print(estimateOffSetX)
if let underline = self.viewController.segmentControl.subviews.filter({$0.restorationIdentifier == "underbar"}).first{
underline.frame.origin.x = estimateOffSetX / 10
}
}
}
}
}
}
PageViewController에 UIScrollViewDelegate를 채택후 scrollViewDidScroll 메서드 내부에 다음과 같이 구현합니다 이때 estimateOffSetX값의 계산과 gesture로 인한 UI변경에 약간의 시간차로 인해 오차가 존재 합니다. 또한 segmentControl에서 정방향( ex. 월 → 수)으로는 잘 작동하지만 역방향(수 → 월)으로 클릭시 underlineView가 과도하게 움직이는 현상이 발생합니다 이 부분에 대해 서는 공부가 필요합니다.
결과화면
'ios > UI구현' 카테고리의 다른 글
[iOS, Swift] 네이버지도의 BottomSheet (0) | 2023.07.27 |
---|---|
[iOS, Swift] AppStore layout만들어보기(UICollectionView, CompositionalLayout 활용) (0) | 2023.07.27 |
[iOS, Swift] Sticky Header(배달의민족, 당근마켓) (0) | 2023.07.27 |
[Swift] UICollectionView - Compositional Layout section의 동적인 높이 조절 (0) | 2023.05.30 |