본문 바로가기
IOS - Swift

[iOS/Swift] UISegmentedControl - 스토리보드로 커스텀하기

by 게게겍 2023. 5. 28.

참고

스토리보드에 segmentcontrol를 추가합니다.

레이아웃 설정이 완료된 상태에서

//...
final class UnderlineSegmentedControl: UISegmentedControl {

    private lazy var underlineView: UIView = {
        let view = UIView()
        view.backgroundColor = .black
        addSubview(view)
        return view
    }()

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.removeBackgroundAndDivider()
        self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.gray], for: .normal)
        self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 13, weight: .semibold)], for: .selected)
    }

    private func removeBackgroundAndDivider() {
        let image = UIImage()
        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 layoutSubviews() {
        super.layoutSubviews()

        let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(self.selectedSegmentIndex)
        let height = CGFloat(2.0)
        let yPosition = self.bounds.size.height - height
        underlineView.frame = CGRect(x: underlineFinalXPosition, y: yPosition, width: self.bounds.width / CGFloat(self.numberOfSegments), height: height)
    }

    override func willMove(toSuperview newSuperview: UIView?) {
        super.willMove(toSuperview: newSuperview)
        self.selectedSegmentIndex = 0
    }
}

UnderlineSegmentedControl 는 스토리보드에서 구성한 후에 해당하는 클래스를 지정합니다

init?(coder: NSCoder) 는 segment control에 대한 초기 설정을 수행합니다.

layoutSubviews 는 underlineView의 프레임을 업데이트합니다.

willMove(toSuperview:) 메소드에서 초기 선택된 segment를 0으로 설정했는데, 뷰가 슈퍼뷰에 추가될 때 호출되는 메소드입니다.

자연스럽게 전환되는 애니메이션 추가

UnderlineSegmentedControl 의 선택된 세그먼트가 바뀔 때 underlineView가 자연스럽게 넘어가도록 하려면, valueChanged 이벤트를 감지하고 애니메이션을 추가해야 합니다.

먼저, init?(coder: NSCoder) 메소드 내에 valueChanged 이벤트를 감지하는 코드를 추가

required init?(coder: NSCoder) { super.init(coder: coder) 
self.removeBackgroundAndDivider() self.setTitleTextAttributes([
NSAttributedString.Key.foregroundColor: UIColor.gray], for: .normal)
self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.green, 
.font: UIFont.systemFont(ofSize: 13, weight: .semibold)], for: .selected) self.addTarget(self, action: #selector(segmentedControlValueChanged(_:)), for: .valueChanged) }​

그리고 @objc 어노테이션이 달린 segmentedControlValueChanged(_:) 메소드를 추가합니다. 이 메소드는 valueChanged 이벤트가 발생했을 때 호출되며, underlineView를 새로 선택된 세그먼트 아래로 애니메이션으로 움직이게 합니다.

@objc private func segmentedControlValueChanged(_ sender: UISegmentedControl)
{ let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(self.selectedSegmentIndex) UIView.animate(withDuration: 0.3)
{ self.underlineView.frame.origin.x = underlineFinalXPosition }​

전체코드

final class UnderlineSegmentedControl: UISegmentedControl {

    private lazy var underlineView: UIView = {
        let view = UIView()
        view.backgroundColor = .black
        addSubview(view)
        return view
    }()

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.removeBackgroundAndDivider()
        self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.gray], for: .normal)
        self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 13, weight: .semibold)], for: .selected)

        self.addTarget(self, action: #selector(segmentedControlValueChanged(_:)), for: .valueChanged)
    }

    private func removeBackgroundAndDivider() {
        let image = UIImage()
        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 layoutSubviews() {
        super.layoutSubviews()

        let height = CGFloat(2.0)
        let yPosition = self.bounds.size.height - height
        underlineView.frame = CGRect(x: underlineView.frame.origin.x, y: yPosition, width: self.bounds.width / CGFloat(self.numberOfSegments), height: height)
    }

    @objc private func segmentedControlValueChanged(_ sender: UISegmentedControl) {
        let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(self.selectedSegmentIndex)
        UIView.animate(withDuration: 0.3) {
            self.underlineView.frame.origin.x = underlineFinalXPosition
        }
    }

    override func willMove(toSuperview newSuperview: UIView?) {
        super.willMove(toSuperview: newSuperview)
        self.selectedSegmentIndex = 0
    }