관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 2. UISegmentedControl - 커스텀 방법, PageViewController와 사용 방법 본문

iOS 응용 (swift)

[iOS - swift] 2. UISegmentedControl - 커스텀 방법, PageViewController와 사용 방법

jake-kim 2022. 5. 6. 23:11

1. UISegmentedControl - 기본 사용 방법

2. UISegmentedControl - 커스텀 방법, PageViewController와 사용 방법

 

UISegmentedControl 커스텀 방법

선택 시 밑줄이 생기고, 문구 색상이 초록색으로 변경되도록 커스텀한 UISegmentedControl

  • 클래스 준비
import UIKit

final class UnderlineSegmentedControl: UISegmentedControl {

}
  • UISegementedControl은 아래와 같이 배경색과 divider가 존재

  • 회색 배경과 divider를 지우는 코드 추가
  override init(frame: CGRect) {
    super.init(frame: frame)
    self.removeBackgroundAndDivider()
  }
  override init(items: [Any]?) {
    super.init(items: items)
    self.removeBackgroundAndDivider()
  }
  required init?(coder: NSCoder) {
    fatalError()
  }
  
  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)
  }

배경과 divider가 삭제된 모습

  • underlineView 추가
    • autolayout이 아닌, frame을 이용하므로 layoutSubviews에서 호출
  private 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 - 1.0
    let frame = CGRect(x: xPosition, y: yPosition, width: width, height: height)
    let view = UIView(frame: frame)
    view.backgroundColor = .green
    self.addSubview(view)
    return view
  }()
  
  override func layoutSubviews() {
    super.layoutSubviews()
    
    let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(self.selectedSegmentIndex)
    UIView.animate(
      withDuration: 0.1,
      animations: {
        self.underlineView.frame.origin.x = underlineFinalXPosition
      }
    )
  }

* 초록색은 문구 지정 방법은 사용하는쪽에서 설정

    // ViewController.swift
    self.segmentedControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.gray], for: .normal)
    self.segmentedControl.setTitleTextAttributes(
      [
        NSAttributedString.Key.foregroundColor: UIColor.green,
        .font: UIFont.systemFont(ofSize: 13, weight: .semibold)
      ],
      for: .selected
    )
    self.segmentedControl.selectedSegmentIndex = 0

ViewController에서 PageViewController와 결합

  • PageViewController와 예제로 사용할 VC 3개와 관련 프로퍼티 준비
    • pageViewController 개념은 이전 포스팅 글 참고 
    • dataViewContoller 프로퍼티: pageViewController의 델리게이트에서 dataSource로 사용할 값
    • currentPage 프로퍼티: segmentedControl에서 값이 변경될때 해당 프로퍼티를 업데이트하여, didSet에서 pageViewController에도 업데이트 시켜줄 용도
  // ViewController.swift
  private let vc1: UIViewController = {
    let vc = UIViewController()
    vc.view.backgroundColor = .red
    return vc
  }()
  private let vc2: UIViewController = {
    let vc = UIViewController()
    vc.view.backgroundColor = .green
    return vc
  }()
  private let vc3: UIViewController = {
    let vc = UIViewController()
    vc.view.backgroundColor = .blue
    return vc
  }()
  private lazy var pageViewController: UIPageViewController = {
    let vc = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
    vc.setViewControllers([self.dataViewControllers[0]], direction: .forward, animated: true)
    vc.delegate = self
    vc.dataSource = self
    vc.view.translatesAutoresizingMaskIntoConstraints = false
    return vc
  }()
  
  var dataViewControllers: [UIViewController] {
    [self.vc1, self.vc2, self.vc3]
  }
  var currentPage: Int = 0 {
    didSet {
      // from segmentedControl -> pageViewController 업데이트
      print(oldValue, self.currentPage)
      let direction: UIPageViewController.NavigationDirection = oldValue <= self.currentPage ? .forward : .reverse
      self.pageViewController.setViewControllers(
        [dataViewControllers[self.currentPage]],
        direction: direction,
        animated: true,
        completion: nil
      )
    }
  }
  • segmentedControl 값이 변경될 때 pageViewController에도 적용시켜주기 위해서 selector 추가
// in viewDidLoad()
self.changeValue(control: self.segmentedControl)

@objc private func changeValue(control: UISegmentedControl) {
  self.currentPage = control.selectedSegmentIndex
}
  • pageViewController 데이터 소스 처리
// pageViewController.dataSource = self
extension ViewController: UIPageViewControllerDataSource {
  func pageViewController(
    _ pageViewController: UIPageViewController,
    viewControllerBefore viewController: UIViewController
  ) -> UIViewController? {
    guard
      let index = self.dataViewControllers.firstIndex(of: viewController),
      index - 1 >= 0
    else { return nil }
    return self.dataViewControllers[index - 1]
  }
  func pageViewController(
    _ pageViewController: UIPageViewController,
    viewControllerAfter viewController: UIViewController
  ) -> UIViewController? {
    guard
      let index = self.dataViewControllers.firstIndex(of: viewController),
      index + 1 < self.dataViewControllers.count
    else { return nil }
    return self.dataViewControllers[index + 1]
  }
}
  • pageViewController에서 값이 변경될 때 segmentedControl에도 적용하기 위해, delegate 처리
    • 위 dataSource에서 처리하면 캐싱이 되어 index값이 모두 불리지 않으므로, delegate에서 따로 처리가 필요
extension ViewController: UIPageViewControllerDelegate {  
  func pageViewController(
    _ pageViewController: UIPageViewController,
    didFinishAnimating finished: Bool,
    previousViewControllers: [UIViewController],
    transitionCompleted completed: Bool
  ) {
    guard
      let viewController = pageViewController.viewControllers?[0],
      let index = self.dataViewControllers.firstIndex(of: viewController)
    else { return }
    self.currentPage = index
    self.segmentedControl.selectedSegmentIndex = index
  }
}

* 전체 코드: https://github.com/JK0369/ExCustomSegmentedControl

 

* 참고

https://stackoverflow.com/questions/42755590/how-to-display-only-bottom-border-for-selected-item-in-uisegmentedcontrol

Comments