목표시간 Picker 구현

hyun·2025년 8월 26일
0

iOS

목록 보기
44/54

기본 높이 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 순서로 처리

0개의 댓글