구현 목표
위와 같은 와이어프레임의 형태로 구현하려고 한다.
UIDatePicker는 시간 단위(시간:분)의 설정까지만 제공하기 때문에 분 단위로 사용하기 위해서는 UIPickerView를 사용해서 UI를 직접 커스텀해야했다.
구현

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를 완성할 수 있었다.
코드
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값을 가져온다.