오늘의 집 어플을 보면 위와같이 선택에 따라 좌우로 밑줄도 같이 움직이는 메뉴가 있다

개인 프로젝트에서도 위와 같은 메뉴를 적용시키고 싶어서 찾아봤는데 버튼을 이용해 구현할 수도 있고 콜렉션뷰를 통해서 구현할 수도 있고 기존 세그먼트 컨트롤을 커스텀 하는 방법도 있고 여러 방법들이 있었으나 개인적으로는 세그먼트 컨트롤을 커스텀 하는 것이 가장 쉽게(?) 느껴져서 이 방법을 선택했다.

(기본적으로 레이아웃 구성에 있어 SnapKit을 사용하였지만 사용하지 않는다고 해도 오토레이아웃에만 차이가 있을 뿐 나머지는 동일)

1. SegmentCotrol 선언

    private(set) lazy var segmentController: UISegmentedControl = {
        let segment = UISegmentedControl()
        
        // 백그라운트 이미지 제거
        segment.setBackgroundImage(UIImage(), for: .normal, barMetrics: .default)
        // 구분선 제거
        segment.setDividerImage(UIImage(), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
        
        // 선택안된 버튼 폰트
        segment.setTitleTextAttributes([
            NSAttributedString.Key.foregroundColor: UIColor.systemGray,
            NSAttributedString.Key.font: UIFont.one(size: 15, family: .Light)
        ], for: .normal)
        
        // 선택된 버튼 폰트
        segment.setTitleTextAttributes([
            NSAttributedString.Key.foregroundColor: #colorLiteral(red: 1, green: 0.6752033234, blue: 0.5361486077, alpha: 1),
            NSAttributedString.Key.font: UIFont.one(size: 15, family: .Bold)
        ], for: .selected)
        
        // 필요한 segment 추가
        segment.insertSegment(withTitle: "기본 정보", at: 0, animated: true)
        segment.insertSegment(withTitle: "스킬", at: 1, animated: true)
        segment.insertSegment(withTitle: "카드", at: 2, animated: true)
        segment.insertSegment(withTitle: "보유캐릭터", at: 3, animated: true)

        segment.selectedSegmentIndex = 0
        
        segment.addTarget(self, action: #selector(changeUnderLinePosition), for: .valueChanged)
        
        return segment
    }()

위와 같이 일단 SegmentControl을 선언해 준 뒤 addTarget을 통해 changeUnderLinePosition이라는 메서드를 할당해줬는데 일단 이 메서드는 아래에서 다룰 예정

2. UnderLineView 선언

    private lazy var underLineView: UIView = {
        let view = UIView()
        view.backgroundColor = #colorLiteral(red: 1, green: 0.6752033234, blue: 0.5361486077, alpha: 1)
        
        return view
    }()

3. 오토레이아웃

 private func setLayout() {
        self.addSubview(segmentController)
        self.addSubview(underLineView)
        
        segmentController.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
        
        underLineView.snp.makeConstraints {
            $0.top.equalTo(segmentController.snp.bottom)
            $0.height.equalTo(2)
            
            $0.leading.equalTo(segmentController)
            
            $0.width.equalTo(segmentController.snp.width).dividedBy(segmentController.numberOfSegments)
            //이 부분이 중요한데 밑줄의 width는 위에서 선언된 segment control의 길이를 segment의 개수로 나눈 길이 이다
        }
	}

4. changeUnderLinePosition 메서드 정의

    @objc private func changeUnderLinePosition() {
        let segmentIndex = CGFloat(segmentController.selectedSegmentIndex)
        let segmentWidth = segmentController.frame.width / CGFloat(segmentController.numberOfSegments)
        let leadingDistance = segmentWidth * segmentIndex
// leadingDistance는 밑줄의 위치로 segment의 길이(segmentControl아님) * 선택된 segment의 index 위치가 밑줄의 leading이다
// 예를 들어 segment가 4개가 있고 segment control이 100이라고 하면 각 segment의 길이는 25일 것이고 
// 첫 번째 segment를 선택 했을 때 밑줄의 leading은 0이 되어야 하며 두 번째는 첫번째 segment의 끝
// 즉 25가 밑줄의 leading이 되어야 한다


// 아래는 0.3초의 시간 동안 view의 애니메이션을 주면서 밑줄의 constraints를 업데이트 해준다
        UIView.animate(withDuration: 0.3, animations: { [weak self] in
            guard let self = self else {
                return
            }

            self.underLineView.snp.updateConstraints {
                $0.leading.equalTo(self.segmentController).inset(leadingDistance)
            }
            self.layoutIfNeeded()
        })
    }

결과

참고문서
https://hoonha.tistory.com/4

profile
끊임없이 문을 여는 개발자

0개의 댓글

Powered by GraphCDN, the GraphQL CDN