UIPickerView 커스텀 구현

JG Ahn·2025년 2월 11일

UIKit

목록 보기
4/4
post-thumbnail

구현 목표

위와 같은 와이어프레임의 형태로 구현하려고 한다.

UIDatePicker는 시간 단위(시간:분)의 설정까지만 제공하기 때문에 분 단위로 사용하기 위해서는 UIPickerView를 사용해서 UI를 직접 커스텀해야했다.

구현

ViewController

    private let pickerView = UIPickerView().then {
        $0.backgroundColor = .clear
        $0.setValue(UIColor.starPrimaryText, forKey: "textColor")
    }
    
    override func viewDidLoad() {
        setupUI()
    }

    private func setupUI() {
        view.backgroundColor = .starModalBG
        
        view.addSubview(titleLabel)
        view.addSubview(textLabel)
        view.addSubview(pickerView)
        
        titleLabel.snp.makeConstraints {
            $0.top.equalToSuperview().inset(32)
            $0.centerX.equalToSuperview()
            $0.width.equalTo(280)
            $0.height.equalTo(36)
        }
        
        textLabel.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.width.equalTo(280)
            $0.height.equalTo(24)
            $0.top.equalTo(titleLabel.snp.bottom).offset(16)
        }
        
        pickerView.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(textLabel.snp.bottom)
            $0.width.equalTo(280)
            $0.height.equalTo(140)
        }
    }

기본적인 UI를 구성했다.


가장 어려웠던 부분은 시간을 나타내는 휠 옆에 "분" 글자를 나타내는 라벨을 그리는 작업이었다.

계속 구글링을 해봤지만 명확한 답을 찾을수 없었는데 setupUI()에서 pickerView 위에 라벨을 addSubview 하고 레이아웃을 잡아줌으로서 원하는 형태의 UI를 완성할 수 있었다.


코드

ViewController

import UIKit
import Then
import RxSwift
import RxCocoa

final class RestSettingModalViewController: UIViewController {
    
    private let disposeBag = DisposeBag()
    
    // picker 데이터
    private let pickerData = Observable.just((1...20).map { String($0) })
    
    private let titleLabel = UILabel().then {
        $0.text = "휴식하기"
        $0.font = UIFont.systemFont(ofSize: 24, weight: .semibold)
        $0.textColor = .starPrimaryText
        $0.textAlignment = .center
    }
    
    private let textLabel = UILabel().then {
        $0.numberOfLines = 0 // 여러 줄 표시
        $0.text = "최대 20분까지 설정할 수 있습니다."
        $0.font = Fonts.modalSubtitle
        $0.textColor = .starPrimaryText
        $0.textAlignment = .center
    }
    
    private let pickerView = UIPickerView().then {
        $0.backgroundColor = .clear
        $0.setValue(UIColor.starPrimaryText, forKey: "textColor")
    }
    
    private let pickerLabel = UILabel().then {
        $0.text = "분"
        $0.textColor = .starPrimaryText
    }
    
    let restButton = GradientButton(type: .system).then {
        $0.setTitle("휴식하기", for: .normal)
        $0.setTitleColor(.starButtonNavy, for: .normal)
        $0.titleLabel?.font = Fonts.buttonTitle
        $0.backgroundColor = .starDisabledTagBG
        $0.layer.cornerRadius = 28
        $0.clipsToBounds = true
        $0.applyGradient(colors: [.starButtonWhite, .starButtonYellow], direction: .horizontal)
    }
        
    override func viewDidLoad() {
        setupUI()
        bind()
    }

    private func setupUI() {
        view.backgroundColor = .starModalBG

        view.addSubview(titleLabel)
        view.addSubview(textLabel)
        pickerView.addSubview(pickerLabel)
        view.addSubview(pickerView)
        view.addSubview(restButton)

        titleLabel.snp.makeConstraints {
            $0.top.equalToSuperview().inset(32)
            $0.centerX.equalToSuperview()
            $0.width.equalTo(280)
            $0.height.equalTo(36)
        }

        textLabel.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.width.equalTo(280)
            $0.height.equalTo(24)
            $0.top.equalTo(titleLabel.snp.bottom).offset(16)
        }

        pickerView.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(textLabel.snp.bottom)
            $0.width.equalTo(280)
            $0.height.equalTo(140)
        }

        pickerLabel.snp.makeConstraints {
            $0.centerY.equalToSuperview()
            $0.leading.equalTo(pickerView.snp.centerX).offset(16)
        }

        restButton.snp.makeConstraints {
            $0.top.equalTo(pickerView.snp.bottom).offset(40)
            $0.height.equalTo(56)
            $0.leading.trailing.equalToSuperview().inset(20)
        }
    }
}

// MARK: - bind

extension RestSettingModalViewController {
    
    private func bind() {
        // UIPickerView에 data 연결
        pickerData.bind(to: pickerView.rx.itemTitles) { _, item in
            return "\(item)"
        }.disposed(by: disposeBag)
        
        // 휴식하기 버튼 클릭 이벤트
        restButton.rx.tap
            .withUnretained(self)
            .subscribe(onNext: { owner, _ in
                print(owner.pickerView.selectedRow(inComponent: 0))
            }).disposed(by: disposeBag)
    }
}
  • selectedRow(inComponent: 0) : UIPickerView가 선택한 Row값을 가져온다.

0개의 댓글