Changes duration selection with a picker

main
Laurent 3 years ago
parent e4758a3fea
commit c3e4d40116
  1. 22
      LaunchWidget/LaunchWidgetLiveActivity.swift
  2. 4
      LeCountdown.xcodeproj/project.pbxproj
  3. 130
      LeCountdown/Views/Countdown/CountdownFormView.swift
  4. 144
      LeCountdown/Views/Countdown/NewCountdownView.swift
  5. 9
      LeCountdown/Views/Reusable/MailView.swift
  6. 127
      LeCountdown/Views/Reusable/TimePickerView.swift

@ -47,10 +47,10 @@ struct LaunchWidgetLiveActivity: Widget {
Text(context.attributes.name.uppercased()) Text(context.attributes.name.uppercased())
.font(.callout) .font(.callout)
}.padding() }
.padding()
.monospaced() .monospaced()
.foregroundColor(.white) .foregroundColor(.white)
.activityBackgroundTint(Color(red: 1.0, green: 0.3, blue: 0.4))
.activitySystemActionForegroundColor(.white) .activitySystemActionForegroundColor(.white)
} dynamicIsland: { context in } dynamicIsland: { context in
DynamicIsland { DynamicIsland {
@ -72,11 +72,23 @@ struct LaunchWidgetLiveActivity: Widget {
} }
} }
} compactLeading: { } compactLeading: {
Text("L") Text(context.attributes.name.uppercased())
} compactTrailing: { } compactTrailing: {
Text("T") if context.attributes.isTimer {
let range = Date()...context.attributes.date
Text(timerInterval: range,
pauseTime: range.lowerBound)
} else {
Text(context.attributes.date, style: .timer)
}
} minimal: { } minimal: {
Text("Min") if context.attributes.isTimer {
let range = Date()...context.attributes.date
Text(timerInterval: range,
pauseTime: range.lowerBound)
} else {
Text(context.attributes.date, style: .timer)
}
} }
.widgetURL(URL(string: context.attributes.id)) .widgetURL(URL(string: context.attributes.id))
.keylineTint(Color.red) .keylineTint(Color.red)

@ -41,6 +41,7 @@
C4286EA12A1502FD0070D075 /* Stopwatch+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4286E9F2A1502FD0070D075 /* Stopwatch+CoreDataClass.swift */; }; C4286EA12A1502FD0070D075 /* Stopwatch+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4286E9F2A1502FD0070D075 /* Stopwatch+CoreDataClass.swift */; };
C4286EA32A1503320070D075 /* Stopwatch+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4286E9F2A1502FD0070D075 /* Stopwatch+CoreDataClass.swift */; }; C4286EA32A1503320070D075 /* Stopwatch+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4286E9F2A1502FD0070D075 /* Stopwatch+CoreDataClass.swift */; };
C4286EA42A1503330070D075 /* Stopwatch+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4286E9F2A1502FD0070D075 /* Stopwatch+CoreDataClass.swift */; }; C4286EA42A1503330070D075 /* Stopwatch+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4286E9F2A1502FD0070D075 /* Stopwatch+CoreDataClass.swift */; };
C4286EA62A150A7E0070D075 /* TimePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4286EA52A150A7E0070D075 /* TimePickerView.swift */; };
C42E96FB29E59E72005B1B8C /* BackgroundBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E96FA29E59E72005B1B8C /* BackgroundBlurView.swift */; }; C42E96FB29E59E72005B1B8C /* BackgroundBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E96FA29E59E72005B1B8C /* BackgroundBlurView.swift */; };
C42E96FD29E5B06D005B1B8C /* ActivityCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E96FC29E5B06D005B1B8C /* ActivityCalendarView.swift */; }; C42E96FD29E5B06D005B1B8C /* ActivityCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E96FC29E5B06D005B1B8C /* ActivityCalendarView.swift */; };
C42E96FE29E5B5CD005B1B8C /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B6429A3C37D00CB4FBA /* Filter.swift */; }; C42E96FE29E5B5CD005B1B8C /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B6429A3C37D00CB4FBA /* Filter.swift */; };
@ -373,6 +374,7 @@
C418A14F298428CB00C22230 /* LeCountdown.0.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.1.xcdatamodel; sourceTree = "<group>"; }; C418A14F298428CB00C22230 /* LeCountdown.0.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.1.xcdatamodel; sourceTree = "<group>"; };
C4286E952A14EC4E0070D075 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; }; C4286E952A14EC4E0070D075 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
C4286E9F2A1502FD0070D075 /* Stopwatch+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stopwatch+CoreDataClass.swift"; sourceTree = "<group>"; }; C4286E9F2A1502FD0070D075 /* Stopwatch+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stopwatch+CoreDataClass.swift"; sourceTree = "<group>"; };
C4286EA52A150A7E0070D075 /* TimePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePickerView.swift; sourceTree = "<group>"; };
C42E96FA29E59E72005B1B8C /* BackgroundBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundBlurView.swift; sourceTree = "<group>"; }; C42E96FA29E59E72005B1B8C /* BackgroundBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundBlurView.swift; sourceTree = "<group>"; };
C42E96FC29E5B06D005B1B8C /* ActivityCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityCalendarView.swift; sourceTree = "<group>"; }; C42E96FC29E5B06D005B1B8C /* ActivityCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityCalendarView.swift; sourceTree = "<group>"; };
C42E970129E6B32B005B1B8C /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; }; C42E970129E6B32B005B1B8C /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
@ -951,6 +953,7 @@
C4E5D68729BB3FE1008E7465 /* SiriTimerView.swift */, C4E5D68729BB3FE1008E7465 /* SiriTimerView.swift */,
C4F8B165298A9ABB005C86A5 /* SoundFormView.swift */, C4F8B165298A9ABB005C86A5 /* SoundFormView.swift */,
C4BA2AD52993F62700CB4FBA /* SoundSelectionView.swift */, C4BA2AD52993F62700CB4FBA /* SoundSelectionView.swift */,
C4286EA52A150A7E0070D075 /* TimePickerView.swift */,
C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */, C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */,
C473C33829ACDBD70056B38A /* TipView.swift */, C473C33829ACDBD70056B38A /* TipView.swift */,
C4BA2B2E299E69A000CB4FBA /* View+Extension.swift */, C4BA2B2E299E69A000CB4FBA /* View+Extension.swift */,
@ -1247,6 +1250,7 @@
C438C7FF2981300500BF3EF9 /* IntentDataProvider.swift in Sources */, C438C7FF2981300500BF3EF9 /* IntentDataProvider.swift in Sources */,
C4E5D68829BB3FE1008E7465 /* SiriTimerView.swift in Sources */, C4E5D68829BB3FE1008E7465 /* SiriTimerView.swift in Sources */,
C4E5D66629B73AED008E7465 /* StartTimerIntent.swift in Sources */, C4E5D66629B73AED008E7465 /* StartTimerIntent.swift in Sources */,
C4286EA62A150A7E0070D075 /* TimePickerView.swift in Sources */,
C4BA2AD62993F62700CB4FBA /* SoundSelectionView.swift in Sources */, C4BA2AD62993F62700CB4FBA /* SoundSelectionView.swift in Sources */,
C498E5A5299152B400E90DE0 /* GreenCheckmarkView.swift in Sources */, C498E5A5299152B400E90DE0 /* GreenCheckmarkView.swift in Sources */,
C4BA2B3E299FC86800CB4FBA /* StatisticsView.swift in Sources */, C4BA2B3E299FC86800CB4FBA /* StatisticsView.swift in Sources */,

@ -9,9 +9,9 @@ import SwiftUI
enum CountdownField: Int, Hashable { enum CountdownField: Int, Hashable {
case name case name
case hours // case hours
case minutes // case minutes
case seconds // case seconds
} }
struct CountdownFormView : View { struct CountdownFormView : View {
@ -22,10 +22,12 @@ struct CountdownFormView : View {
var nameBinding: Binding<String> var nameBinding: Binding<String>
var secondsBinding: Binding<String> // var secondsBinding: Binding<String>
var minutesBinding: Binding<String> // var minutesBinding: Binding<String>
var hoursBinding: Binding<String> // var hoursBinding: Binding<String>
var durationBinding: Binding<TimeInterval>
var imageBinding: Binding<CoolPic> var imageBinding: Binding<CoolPic>
var repeatCountBinding: Binding<Int16> var repeatCountBinding: Binding<Int16>
@ -34,67 +36,88 @@ struct CountdownFormView : View {
var body: some View { var body: some View {
Group { Form {
Section { Section(header: Text("Name for tracking the activity")) {
TextField("Name", text: nameBinding) TextField("name", text: nameBinding)
.focused($focusedField, equals: .name) .focused($focusedField, equals: .name)
.onSubmit { .onSubmit {
self.focusNextField($focusedField) self.focusedField = nil
} }
} header: {
Text("Name")
} footer: {
if !self.nameBinding.wrappedValue.isEmpty {
Text("Ask Siri: \(Bundle.main.applicationName) \(self.nameBinding.wrappedValue)!")
}
} }
// Section {
//// self.focusedField = nil
//
// TextField("Name", text: nameBinding)
// .focused($focusedField, equals: .name)
// .onSubmit {
// self.focusedField = nil
//
//// self.focusNextField($focusedField)
// }
// } header: {
// Text("Name")
// } footer: {
// if !self.nameBinding.wrappedValue.isEmpty {
// Text("Ask Siri: \(Bundle.main.applicationName) \(self.nameBinding.wrappedValue)!")
// }
// }
Section { Section {
TextField("Hours", text: hoursBinding)
.keyboardType(.numberPad) TimePickerView(duration: self.durationBinding)
.focused($focusedField, equals: .hours)
.onSubmit { // TextField("Hours", text: hoursBinding)
self.focusNextField($focusedField) // .keyboardType(.numberPad)
} // .focused($focusedField, equals: .hours)
TextField("Minutes", text: minutesBinding) // .onSubmit {
.keyboardType(.numberPad) // self.focusNextField($focusedField)
.focused($focusedField, equals: .minutes) // }
.onSubmit { // TextField("Minutes", text: minutesBinding)
self.focusNextField($focusedField) // .keyboardType(.numberPad)
} // .focused($focusedField, equals: .minutes)
TextField("Seconds", text: secondsBinding) // .onSubmit {
.keyboardType(.numberPad) // self.focusNextField($focusedField)
.focused($focusedField, equals: .seconds) // }
.onSubmit { // TextField("Seconds", text: secondsBinding)
self.focusedField = nil // .keyboardType(.numberPad)
} // .focused($focusedField, equals: .seconds)
// .onSubmit {
// self.focusedField = nil
// }
} header: { } header: {
LabeledContent("Duration", value: self.duration().hourMinuteSecond).font(.footnote) LabeledContent("Duration", value: self.duration().hourMinuteSecond).font(.footnote)
} }
SoundFormView( SoundFormView(
model: self.model, model: self.model,
imageBinding: imageBinding, imageBinding: self.imageBinding,
repeatCountBinding: repeatCountBinding) repeatCountBinding: self.repeatCountBinding)
} }
} }
func duration() -> TimeInterval { func duration() -> TimeInterval {
let formatter = NumberFormatter() return self.durationBinding.wrappedValue
var hours: Int = 0
var minutes: Int = 0
var seconds: Int = 0 // let formatter = NumberFormatter()
if let h = formatter.number(from: hoursBinding.wrappedValue) { // var hours: Int = 0
hours = h.intValue // var minutes: Int = 0
} // var seconds: Int = 0
if let m = formatter.number(from: minutesBinding.wrappedValue) { // if let h = formatter.number(from: hoursBinding.wrappedValue) {
minutes = m.intValue // hours = h.intValue
} // }
if let s = formatter.number(from: secondsBinding.wrappedValue) { // if let m = formatter.number(from: minutesBinding.wrappedValue) {
seconds = s.intValue // minutes = m.intValue
} // }
return Double(seconds) + 60 * Double(minutes) + 60 * 60 * Double(hours) // if let s = formatter.number(from: secondsBinding.wrappedValue) {
// seconds = s.intValue
// }
// return Double(seconds) + 60 * Double(minutes) + 60 * 60 * Double(hours)
} }
} }
@ -108,9 +131,10 @@ struct CountdownFormView_Previews: PreviewProvider {
Form { Form {
CountdownFormView( CountdownFormView(
nameBinding: .constant(""), nameBinding: .constant(""),
secondsBinding: .constant(""), // secondsBinding: .constant(""),
minutesBinding: .constant(""), // minutesBinding: .constant(""),
hoursBinding: .constant(""), // hoursBinding: .constant(""),
durationBinding: .constant(0.0),
imageBinding: .constant(.pic3), imageBinding: .constant(.pic3),
repeatCountBinding: .constant(2), repeatCountBinding: .constant(2),
intervalRepeatBinding: .constant(2)) intervalRepeatBinding: .constant(2))

@ -55,9 +55,11 @@ struct CountdownEditView : View {
@State var nameString: String = "" @State var nameString: String = ""
@State var secondsString: String = "" // @State var secondsString: String = ""
@State var minutesString: String = "" // @State var minutesString: String = ""
@State var hoursString: String = "" // @State var hoursString: String = ""
@State var duration: TimeInterval = 0.0
@State var soundRepeatCount: Int16 = 0 @State var soundRepeatCount: Int16 = 0
@State var image: CoolPic = .pic1 @State var image: CoolPic = .pic1
@ -70,12 +72,7 @@ struct CountdownEditView : View {
@State var error: Error? = nil @State var error: Error? = nil
var tabSelection: Binding<Int>? = nil var tabSelection: Binding<Int>? = nil
// @FocusState private var textFieldIsFocused: Bool
// @FetchRequest(sortDescriptors: [])
// private var timers: FetchedResults<AbstractTimer>
@State var _isNewCountdown: Bool = false // false if editing an existing countdown @State var _isNewCountdown: Bool = false // false if editing an existing countdown
@State var _hasLoaded = false @State var _hasLoaded = false
@ -112,42 +109,43 @@ struct CountdownEditView : View {
} }
} }
Form { // Form {
EmptyView().id("anchor") // EmptyView().id("anchor")
CountdownFormView( CountdownFormView(
focusedField: _focusedField, focusedField: _focusedField,
nameBinding: $nameString, nameBinding: $nameString,
secondsBinding: $secondsString, // secondsBinding: $secondsString,
minutesBinding: $minutesString, // minutesBinding: $minutesString,
hoursBinding: $hoursString, // hoursBinding: $hoursString,
durationBinding: $duration,
imageBinding: $image, imageBinding: $image,
repeatCountBinding: $soundRepeatCount) repeatCountBinding: $soundRepeatCount)
.environmentObject(self.model) .environmentObject(self.model)
BasePresetsView { preset in // BasePresetsView { preset in
self._loadPreset(preset) // self._loadPreset(preset)
} // }
}.toolbar { // }
.toolbar {
ToolbarItemGroup(placement: .keyboard) { ToolbarItemGroup(placement: .keyboard) {
Button { Button {
Logger.log("NIL!!!!")
self.focusedField = nil self.focusedField = nil
} label: { } label: {
Image(systemName: "keyboard.chevron.compact.down") Image(systemName: "keyboard.chevron.compact.down")
} }
Spacer() // Spacer()
Button { // Button {
self.focusPreviousField($focusedField) // self.focusPreviousField($focusedField)
} label: { // } label: {
Image(systemName: "chevron.up") // Image(systemName: "chevron.up")
} // }
Button { // Button {
self.focusNextField($focusedField) // self.focusNextField($focusedField)
} label: { // } label: {
Image(systemName: "chevron.down") // Image(systemName: "chevron.down")
} // }
} }
} }
.onChange(of: self.shouldScrollToTop) { newValue in .onChange(of: self.shouldScrollToTop) { newValue in
@ -232,20 +230,22 @@ struct CountdownEditView : View {
fileprivate func _loadPreset(_ preset: Preset) { fileprivate func _loadPreset(_ preset: Preset) {
self.nameString = preset.localizedName self.nameString = preset.localizedName
let nf = NumberFormatter() self.duration = preset.duration
let minutes = Int(preset.duration / 60.0)
if minutes > 0 {
self.minutesString = nf.string(from: NSNumber(value: minutes)) ?? ""
} else {
self.minutesString = ""
}
let seconds = Int(preset.duration) - minutes * 60 // let nf = NumberFormatter()
if seconds > 0 { // let minutes = Int(preset.duration / 60.0)
self.secondsString = nf.string(from: NSNumber(value: seconds)) ?? "" // if minutes > 0 {
} else { // self.minutesString = nf.string(from: NSNumber(value: minutes)) ?? ""
self.secondsString = "" // } else {
} // self.minutesString = ""
// }
//
// let seconds = Int(preset.duration) - minutes * 60
// if seconds > 0 {
// self.secondsString = nf.string(from: NSNumber(value: seconds)) ?? ""
// } else {
// self.secondsString = ""
// }
self.model.group = preset.intervalGroup self.model.group = preset.intervalGroup
self.model.soundModel.loadPreset(preset) self.model.soundModel.loadPreset(preset)
@ -254,19 +254,21 @@ struct CountdownEditView : View {
fileprivate func _loadCountdown(_ countdown: Countdown) { fileprivate func _loadCountdown(_ countdown: Countdown) {
let hours = Int(countdown.duration / 3600.0) self.duration = countdown.duration
let minutes = Int(countdown.duration - Double(hours * 3600)) / 60
let seconds = countdown.duration - Double(hours * 3600) - Double(minutes * 60)
if hours > 0 { // let hours = Int(countdown.duration / 3600.0)
self.hoursString = self._numberFormatter.string(from: NSNumber(value: hours)) ?? "" // let minutes = Int(countdown.duration - Double(hours * 3600)) / 60
} // let seconds = countdown.duration - Double(hours * 3600) - Double(minutes * 60)
if minutes > 0 { //
self.minutesString = self._numberFormatter.string(from: NSNumber(value: minutes)) ?? "" // if hours > 0 {
} // self.hoursString = self._numberFormatter.string(from: NSNumber(value: hours)) ?? ""
if seconds > 0 { // }
self.secondsString = self._numberFormatter.string(from: NSNumber(value: seconds)) ?? "" // if minutes > 0 {
} // self.minutesString = self._numberFormatter.string(from: NSNumber(value: minutes)) ?? ""
// }
// if seconds > 0 {
// self.secondsString = self._numberFormatter.string(from: NSNumber(value: seconds)) ?? ""
// }
if let name = countdown.activity?.name, !name.isEmpty { if let name = countdown.activity?.name, !name.isEmpty {
self.nameString = name self.nameString = name
@ -283,20 +285,20 @@ struct CountdownEditView : View {
} }
} }
fileprivate let _numberFormatter = NumberFormatter() // fileprivate let _numberFormatter = NumberFormatter()
//
fileprivate var _seconds: Double { // fileprivate var _seconds: Double {
return self._numberFormatter.number(from: self.secondsString)?.doubleValue ?? 0.0 // return self._numberFormatter.number(from: self.secondsString)?.doubleValue ?? 0.0
} // }
//
fileprivate var _minutes: Double { // fileprivate var _minutes: Double {
return self._numberFormatter.number(from: self.minutesString)?.doubleValue ?? 0.0 // return self._numberFormatter.number(from: self.minutesString)?.doubleValue ?? 0.0
} // }
//
fileprivate var _hours: Double { // fileprivate var _hours: Double {
return self._numberFormatter.number(from: self.hoursString)?.doubleValue ?? 0.0 // return self._numberFormatter.number(from: self.hoursString)?.doubleValue ?? 0.0
} // }
fileprivate func _cancel() { fileprivate func _cancel() {
self.viewContext.rollback() self.viewContext.rollback()
self.isPresented = false self.isPresented = false
@ -311,7 +313,9 @@ struct CountdownEditView : View {
cd = Countdown(context: viewContext) cd = Countdown(context: viewContext)
} }
cd.duration = self._hours * 3600.0 + self._minutes * 60.0 + self._seconds // cd.duration = self._hours * 3600.0 + self._minutes * 60.0 + self._seconds
cd.duration = self.duration
if self._isNewCountdown { if self._isNewCountdown {
let max: Int16 let max: Int16
do { do {

@ -12,28 +12,19 @@ import SwiftUI
struct MailView: UIViewControllerRepresentable { struct MailView: UIViewControllerRepresentable {
@Binding var isShowing: Bool @Binding var isShowing: Bool
// @Binding var result: Result<MFMailComposeResult, Error>?
class Coordinator: NSObject, MFMailComposeViewControllerDelegate { class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
@Binding var isShowing: Bool @Binding var isShowing: Bool
// @Binding var result: Result<MFMailComposeResult, Error>?
init(isShowing: Binding<Bool>) { init(isShowing: Binding<Bool>) {
_isShowing = isShowing _isShowing = isShowing
// _result = result
} }
func mailComposeController(_ controller: MFMailComposeViewController, func mailComposeController(_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult, didFinishWith result: MFMailComposeResult,
error: Error?) { error: Error?) {
isShowing = false isShowing = false
// guard error == nil else {
// self.result = .failure(error!)
// return
// }
// self.result = .success(result)
} }
} }

@ -0,0 +1,127 @@
//
// TimePickerView.swift
// LeCountdown
//
// Created by Laurent Morvillier on 17/05/2023.
//
import SwiftUI
struct MultiComponentPicker<Tag: Hashable>: View {
let columns: [Column]
var selections: [Binding<Tag>]
init?(columns: [Column], selections: [Binding<Tag>]) {
guard !columns.isEmpty && columns.count == selections.count else {
return nil
}
self.columns = columns
self.selections = selections
}
var body: some View {
GeometryReader { geometry in
HStack(spacing: 0) {
ForEach(0..<columns.count, id: \.self) { index in
let column = columns[index]
ZStack(alignment: Alignment.init(horizontal: .customCenter, vertical: .center)) {
if (!column.label.isEmpty && !column.options.isEmpty) {
HStack {
Text(verbatim: String("ld"))
.foregroundColor(.clear)
.alignmentGuide(.customCenter) { $0[HorizontalAlignment.center] }
Text(column.label).font(Font.footnote)
}
}
Picker(column.label, selection: selections[index]) {
ForEach(column.options, id: \.tag) { option in
Text(verbatim: option.text).tag(option.tag)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: geometry.size.width / CGFloat(columns.count), height: geometry.size.height)
.clipped()
}
}
}
}
}
}
extension MultiComponentPicker {
struct Column {
struct Option {
var text: String
var tag: Tag
}
var label: String
var options: [Option]
}
}
private extension HorizontalAlignment {
enum CustomCenter: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat { context[HorizontalAlignment.center] }
}
static let customCenter = Self(CustomCenter.self)
}
struct TimePickerView: View {
@Binding var duration: TimeInterval
@State var hours = 0
@State var minutes = 0
@State var seconds = 0
var hoursBinding: Binding<Int> { Binding(
get: { self.hours },
set: {
self.hours = $0
self._computeDuration()
}
) }
var minutesBinding: Binding<Int> { Binding(
get: { self.minutes },
set: {
self.minutes = $0
self._computeDuration()
}
) }
var secondsBinding: Binding<Int> { Binding(
get: { self.seconds },
set: {
self.seconds = $0
self._computeDuration()
}
) }
var columns = [
MultiComponentPicker.Column(label: "h", options: Array(0..<24).map { MultiComponentPicker.Column.Option(text: "\($0)", tag: $0) }),
MultiComponentPicker.Column(label: "min", options: Array(0..<60).map { MultiComponentPicker.Column.Option(text: "\($0)", tag: $0) }),
MultiComponentPicker.Column(label: "sec", options: Array(0..<60).map { MultiComponentPicker.Column.Option(text: "\($0)", tag: $0) }),
]
fileprivate func _computeDuration() {
self.duration = Double(hours) * 3600.0 + Double(minutes) * 60.0 + Double(seconds)
Logger.log("duration = \(self.duration)")
}
var body: some View {
MultiComponentPicker(columns: columns, selections: [hoursBinding, minutesBinding, secondsBinding])
.frame(height: 200)
.previewLayout(.sizeThatFits)
}
}
struct MultiComponentPicker_Previews: PreviewProvider {
static var previews: some View {
TimePickerView(duration: .constant(0.0)).padding(48.0)
}
}
Loading…
Cancel
Save