기본 높이 113pt에서 값만 보이다가 탭하면 312pt로 확장돼서 UIPickerView로 시간을 고르는 UX 필요.
분은 고정 라벨, 숫자만 스크롤. 기본 선택은 50분, 항목은 5분 단위.
중앙 흰색 밴드(Selection Band) 제거 → 시스템 피커의 중앙 5행 자연스러운 강조 사용.
분은 피커 외부의 고정 UILabel로 처리 → 행간/정렬 이슈 제거
접힘/펼침 전환은 height 제약 업데이트 + 애니메이션
가시성은 피커/고정라벨/접힘라벨 show/hide로 전환
// 데이터
let minuteOptions = Array(stride(from: 5, through: 180, by: 5))
var selectedMinutes = 50
// 접힘/펼침 상태
private var isPickerExpanded = false
private var goalHeightConstraint: Constraint?
// 접힘용 중앙 라벨
private let collapsedValueLabel = UILabel()
// 고정 "분" 라벨
private let unitLabel = UILabel().then {
$0.attributedText = Typography.attributed("분", style: .headingXl(weight: .bold), color: .appBlack)
}
// 피커
private lazy var minutePicker = UIPickerView().then {
$0.dataSource = self
$0.delegate = self
$0.showsSelectionIndicator = false
}
// 제약: 컨테이너 높이(113 → 312로 전환)
goalContainerView.snp.makeConstraints {
goalHeightConstraint = $0.height.equalTo(113).constraint
}
// “분” 고정 라벨은 오른쪽, 피커는 왼쪽-중앙
unitLabel.snp.makeConstraints {
$0.centerY.equalToSuperview()
$0.trailing.equalToSuperview().inset(16)
}
minutePicker.snp.makeConstraints {
$0.centerY.equalToSuperview()
$0.leading.equalToSuperview()
$0.trailing.equalTo(unitLabel.snp.leading).offset(-8)
}
// 토글 액션
@objc private func togglePicker() {
isPickerExpanded.toggle()
goalHeightConstraint?.update(offset: isPickerExpanded ? 312 : 113)
unitLabel.isHidden = !isPickerExpanded
minutePicker.isHidden = !isPickerExpanded
collapsedValueLabel.isHidden = isPickerExpanded
UIView.animate(withDuration: 0.25) { self.view.layoutIfNeeded() }
}
// Picker 표시 스타일: 선택행만 진하게
private func minuteAttributed(_ value: Int, selected: Bool) -> NSAttributedString {
Typography.attributed("\(value)",
style: .displayMd(weight: .bold),
color: selected ? .appBlack : Palette.Gray.g400)
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent comp: Int, reusing view: UIView?) -> UIView {
let label = (view as? UILabel) ?? UILabel()
label.attributedText = minuteAttributed(minuteOptions[row],
selected: row == pickerView.selectedRow(inComponent: comp))
label.textAlignment = .center
return label
}
// 스크롤 중 가시 행들 리프레시
private func updateVisibleRowStyles() {
let sel = minutePicker.selectedRow(inComponent: 0)
for off in -3...3 {
let r = sel + off
guard (0..<minuteOptions.count).contains(r),
let label = minutePicker.view(forRow: r, forComponent: 0) as? UILabel else { continue }
label.attributedText = minuteAttributed(minuteOptions[r], selected: r == sel)
}
}
밴드를 쓰면 행간/정렬 흔들림 → 고정 분 라벨로 해결.
viewForRow에서 라벨 재사용 시 속성 누락 주의 → attributedText 매번 갱신
저장/선택시 collapsedValueLabel 동기화 필요 → updateCollapsedLabelText() 호
높이 변경 전 isHidden만 바꾸면 오토레이아웃 충돌 가능 → 제약 업데이트 + animate 순서로 처리