애플 타이머 앱을 따라 만들어보자
구현해줄 뷰는 타이머 탭!
VStack으로 전체를 감싸줘야 될 거 같고,
상단에서부터 Picker 뷰,
HStack{ 취소 , 시작(일시 정지, 재개로 바뀜) 버튼}
그리고 다시 Picker뷰!
요렇게 크게보면 3개로 구성되야함
제일 먼저 시간을 선택할 PickerView가 필요함
시간, 분, 초 텍스트는 고정이고
시간은 23/ 분은 59 / 초도 59 까지 중에 선택 가능함
enum Flavor: String, CaseIterable, Identifiable {
case chocolate, vanila, strawberry
var id: Self { self }
}
struct TimerPickerView: View {
@State private var selectedFlavor: Flavor = .chocolate
var body: some View {
List {
Picker("Flavor", selection: $selectedFlavor) {
Text("Chocolate").tag(Flavor.chocolate)
Text("Vanila").tag(Flavor.vanila)
Text("Strawberry").tag(Flavor.strawberry)
}
}
}
}
enum Flavor: String, CaseIterable, Identifiable {
case chocolate, vanila, strawberry
var id: Self { self }
}
struct TimerPickerView: View {
@State private var selectedFlavor: Flavor = .chocolate
var body: some View {
List {
Picker("Flavor", selection: $selectedFlavor) {
ForEach(Flavor.allCases) { flavor in
Text(flavor.rawValue.capitalized)
}
}
}
}
}
위의 두 코드는 같은 표현임
import SwiftUI
enum Flavor: String, CaseIterable, Identifiable {
case chocolate, vanila, strawberry
var id: Self { self }
}
enum Topping: String, CaseIterable, Identifiable {
case nuts, cookies, blueberries
var id: Self { self }
}
extension Flavor {
var suggestedTopping: Topping {
switch self {
case .chocolate: return .nuts
case .vanila: return .cookies
case .strawberry: return .blueberries
}
}
}
struct TimerPickerView: View {
@State private var selectedFlavor: Flavor = .chocolate
@State private var suggestedTopping: Topping = .nuts
var body: some View {
List {
Picker("Flavor", selection: $suggestedTopping) {
ForEach(Flavor.allCases) { flavor in
Text(flavor.rawValue.capitalized)
.tag(flavor.suggestedTopping)
}
}.pickerStyle(.wheel)
HStack {
Text("Suggested Topping")
Spacer()
Text(suggestedTopping.rawValue.capitalized)
.foregroundStyle(.secondary)
}
}
}
}
struct TimerPickerView: View {
@State var hourSelection = 0
@State var minuteSelection = 0
@State var secondSelection = 0
var hours = [Int](0..<24)
var minutes = [Int](0..<60)
var seconds = [Int](0..<60)
var body: some View {
GeometryReader { geometry in
HStack {
Picker(selection: $hourSelection, label: Text("")) {
ForEach(0..<24) { index in
Text("\(hours[index]) 시간").tag(index)
}
}.frame(width: geometry.size.width/3, height: 100, alignment: .center)
Picker(selection: $minuteSelection, label: Text("")) {
ForEach(0..<60) { index in
Text("\(minutes[index]) 분").tag(index)
}
}.frame(width: geometry.size.width/3, height: 100, alignment: .center)
Picker(selection: $secondSelection, label: Text("")) {
ForEach(0..<60) { index in
Text("\(seconds[index]) 초").tag(index)
}
}.frame(width: geometry.size.width/3, height: 100, alignment: .center)
}.pickerStyle(.wheel)
}
}
}
구글링하면서 찾은 방법 중에 하나인데
아직 원하는 느낌이 안나온다..
저 선택되는 부분이 하나의 단일 바로 구성되고,
시간 분 초 같은 경우엔 고정적으로 뙇 박혀있어야함
struct TimerPickerView: View {
@State private var hours = 0
@State private var minutes = 0
@State private var seconds = 0
var body: some View {
HStack(spacing: -30) {
Picker("Hours", selection: $hours) {
ForEach(0..<24) {
Text("\($0)").tag($0)
}
}
Text("시간")
.font(.body)
Picker("Minutes", selection: $minutes) {
ForEach(0..<60) {
Text("\($0)").tag($0)
}
}
Text("분")
.font(.body)
Picker("Seconds", selection: $seconds) {
ForEach(0..<60) {
Text("\($0)").tag($0)
}
}
Text("초")
.font(.body)
}
.padding()
.pickerStyle(.wheel)
}
}
찾은 또 하나의 방법. 여전히 원하는 느낌은 아니다
HStack으로 개별적인 Picker들을 만들고 사이에 텍스트를 넣었는데
시간이 선택되는 바가 한 묶음으로 묶여있어야함
혹시나 HStack의 spacing을 줄여서 만들어볼 수 있지 않을까 했는데
pickerStyle(.wheel)의 selection Bar가 투명도를 가지고 있는걸 알게됨
DatePicker도 가져와보고 custom이 가능한지 살펴봤는데
이것도 한정적이다
import SwiftUI
import UIKit
struct Time {
var hour: Int = 0
var minute: Int = 0
var second: Int = 0
}
struct TimerPickerView: UIViewRepresentable {
var time: Time
func makeCoordinator() -> TimerPickerView.Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIDatePicker {
let datePicker = UIDatePicker()
datePicker.datePickerMode = .countDownTimer
datePicker.addTarget(context.coordinator, action: #selector(Coordinator.onDateChanged), for: .valueChanged)
return datePicker
}
func updateUIView(_ datePicker: UIDatePicker, context: Context) {
let date = Calendar.current.date(bySettingHour: time.hour, minute: time.minute, second: time.second, of: datePicker.date)!
datePicker.setDate(date, animated: true)
}
class Coordinator: NSObject {
var durationPicker: TimerPickerView
init(_ durationPicker: TimerPickerView) {
self.durationPicker = durationPicker
}
@objc func onDateChanged(sender: UIDatePicker) {
print(sender.date)
let calendar = Calendar.current
let date = sender.date
durationPicker.time = Time(hour: calendar.component(.hour, from: date), minute: calendar.component(.minute, from: date), second: calendar.component(.second, from: date))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
TimerPickerView(time: Time(hour: 0, minute: 0, second: 0))
}
}
coordinate 패턴 사용하는 방법도 있음.
이 경우엔 UIKit을 스유에서 띄워주는 방법을 사용하는 거 같음
enum TimePeriod: CaseIterable {
case am
case pm
var displayText: String {
switch self {
case .am:
return "AM"
case .pm:
return "PM"
}
}
}
struct TimerPickerView: View {
@State private var selectedHour: Int = 0
@State private var selectedTimePeriod: TimePeriod = .am
var body: some View {
GeometryReader { geometry in
VStack {
HStack {
Spacer()
Picker("Selected Hour", selection: $selectedHour) {
ForEach(0..<24, id: \.self) { hour in
Text("\(hour)")
}
}
.pickerStyle(.wheel)
.frame(width: geometry.size.width/3, height: 100)
.clipped()
Picker("Select Period", selection: $selectedTimePeriod) {
ForEach(TimePeriod.allCases, id: \.self) { timePeriod in
Text(timePeriod.displayText)
}
}
.pickerStyle(.wheel)
.frame(width: geometry.size.width/3, height: 100)
.clipped()
Spacer()
}
}
}
}
}
geometry reader 사용해서 구현하는 법임
struct TimerPickerView: View {
@State private var selectedHour: Int = 0
@State private var selectedMinute: Int = 0
@State private var selectedSecond: Int = 0
var body: some View {
GeometryReader { geometry in
VStack {
HStack(spacing: 0) {
Spacer()
Picker("Selected Hour", selection: $selectedHour) {
ForEach(0..<24, id: \.self) { hour in
Text("\(hour)")
}
}
.pickerStyle(.wheel)
.frame(width: geometry.size.width/5, height: 200)
.clipped()
Text("시간")
Picker("Select Period", selection: $selectedMinute) {
ForEach(0..<60, id: \.self) { minute in
Text("\(minute)")
}
}
.pickerStyle(.wheel)
.frame(width: geometry.size.width/4, height: 200)
.clipped()
Text("분")
Picker("Select Period", selection: $selectedSecond) {
ForEach(0..<60, id: \.self) { minute in
Text("\(minute)")
}
}
.pickerStyle(.wheel)
.frame(width: geometry.size.width/4, height: 200)
.clipped()
Text("초")
Spacer()
}
}
}
}
}
지금까지 구현한 것들 중에는 제일 근접하게 왔다
GeometryReader를 사용해서 Picker마다에 screen의 사이즈를 이용해서 frame을 지정해줌
아직 문제가 되는건 selection Bar가 하나로 묶여있지 않은거 , column 별로 여러개가 돌아가는 중이면 제대로 작동하지 않는 현상이 있음
아마 Hashable 하지 않아서 그런 것 같다
피커뷰 디자인을 내려놓기로함...
왜 custom이 안될까!!!
ctaButton 만들어주고, 탭 추가해줌
그래도 얼추 비슷해진거 같아서 만족 중이다