我有一个出售mysql -e ""
的类型,您可以在下面看到:
Timer.TimerPublisher
我有一个import Combine
import Foundation
struct TimerClient {
// MARK: Properties
var timerValueChange: () -> AnyPublisher<Date, Never>
// MARK: Initialization
init(
timerValueChange: @escaping () -> AnyPublisher<Date, Never>
) {
self.timerValueChange = timerValueChange
}
}
extension TimerClient {
// MARK: Properties
static var live: Self {
Self {
return Timer
.TimerPublisher(
interval: 1,
runLoop: .main,
mode: .common
)
.autoconnect()
.share()
.eraseToAnyPublisher()
}
}
}
,用于保存新事件。它注入了使用View
的视图模型,因此,如果用户尝试保存过去的日期,我可以禁用TimerClient
按钮:
Save
视图模型运行良好,并且如果尝试选择过去的日期,它将更新import Combine
final class CountdownEventEntryViewModel: ObservableObject {
// MARK: Properties
private let nowSubject = CurrentValueSubject<Date, Never>(Date())
private let calendar: Calendar
private let timerClient: TimerClient
private var cancellables = Set<AnyCancellable>()
@Published var eventTitle = ""
@Published var isAllDay = false
@Published var eventDate = Date()
@Published var eventTime = Date()
@Published var shouldDisableSaveButton = true
// MARK: Initialization
init(
calendar: Calendar = .autoupdatingCurrent,
timerClient: TimerClient
) {
self.calendar = calendar
self.timerClient = timerClient
observeTimer()
observeCurrentDateChanges()
}
// MARK: UI Configuration
private func disableSaveButton() -> Bool {
if eventTitle.trimmingCharacters(in: .whitespaces).isEmpty {
return true
}
if isAllDay {
let startOfToday = calendar.startOfDay(for: nowSubject.value)
let startOfSelectedDate = calendar.startOfDay(for: eventDate)
return startOfSelectedDate <= startOfToday
} else {
return normalizedSelectedDate() <= nowSubject.value
}
}
private func observeTimer() {
timerClient.timerValueChange().sink(receiveValue: { [weak self] newDate in
self?.nowSubject.send(newDate)
})
.store(in: &cancellables)
}
private func observeCurrentDateChanges() {
nowSubject.sink(receiveValue: { [weak self] _ in
self?.shouldDisableSaveButton = self?.disableSaveButton() ?? false
})
.store(in: &cancellables)
}
}
按钮:
Save
关闭输入视图后,根据以下视图模型更新显示已保存事件的import SwiftUI
struct CountdownEventEntryView: View {
// MARK: Properties
@ObservedObject var viewModel: CountdownEventEntryViewModel
var body: some View {
Form {
Section {
TextField(
viewModel.eventTitlePlaceholderKey,
text: $viewModel.eventTitle
)
}
Section {
Toggle(
isOn: $viewModel.isAllDay.animation(),
label: {
Text(viewModel.isAllDayLabelTextKey)
}
)
DatePicker(
viewModel.eventDatePickerLabelTextKey,
selection: $viewModel.eventDate,
displayedComponents: [.date]
)
if !viewModel.isAllDay {
DatePicker(
viewModel.eventTimePickerLabelTextKey,
selection: $viewModel.eventTime,
displayedComponents: [.hourAndMinute]
)
}
}
}
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text(viewModel.saveButtonTitleKey)
}
.disabled(viewModel.shouldDisableSaveButton)
}
}
}
}
:
List
}
视图模型用在import Combine
import CoreData
final class CountdownEventsViewModel: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
// MARK: Properties
private let calendar: Calendar
private let timerClient: TimerClient
private var cancellables = Set<AnyCancellable>()
@Published var now = Date()
@Published var countdownEvents = [CountdownEvent]()
// MARK: Initialization
init(
calendar: Calendar = .autoupdatingCurrent,
timerClient: TimerClient
) {
self.calendar = calendar
self.timerClient = timerClient
super.init()
observeTimer()
fetchCountdownEvents()
}
// MARK: Timer Observation
private func observeTimer() {
timerClient.timerValueChange().sink(receiveValue: { [weak self] newDate in
self?.now = newDate
})
.store(in: &cancellables)
}
// MARK: Event Display
func formattedTitle(for event: CountdownEvent) -> String {
event.title ?? untitledLabelKey
}
func formattedDate(for event: CountdownEvent) -> String {
guard let date = event.date else {
return dateUnknownLabelKey
}
if event.isAllDay {
return DateFormatter.dateOnlyFormatter.string(from: date)
}
return DateFormatter.dateAndTimeFormatter.string(from: date)
}
func formattedTimeRemaining(from date: Date, to event: CountdownEvent) -> String {
guard let eventDate = event.date else {
return dateUnknownLabelKey
}
let allowedComponents: Set<Calendar.Component> = [.year, .month, .day, .hour, .minute, .second]
let dateComponents = calendar.dateComponents(allowedComponents, from: date, to: eventDate)
guard let formatted = DateComponentsFormatter.eventTimeRemainingFormatter.string(from: dateComponents) else {
return dateUnknownLabelKey
}
return formatted
}
中,该视图显示列表中所有已保存的项目:
View
}
这可以按预期方式工作,并且import SwiftUI
struct CountdownEventsView: View {
// MARK: Properties
@ObservedObject private var viewModel: CountdownEventsViewModel
@State private var showEventEntry = false
@State private var now = Date()
var body: some View {
List {
Section {
ForEach(viewModel.countdownEvents) { event in
VStack(alignment: .leading) {
Text(viewModel.formattedTitle(for: event))
Text(viewModel.formattedDate(for: event))
Text(viewModel.formattedTimeRemaining(from: now, to: event))
}
}
}
}
.toolbar {
ToolbarItem(placement: .navigation) {
HStack(spacing: 30) {
Button(action: showSettings) {
Label(viewModel.settingsButtonTitleKey, systemImage: viewModel.settingsButtonImageName)
}
Button(action: {
showEventEntry = true
}, label: {
Label(
title: { Text(viewModel.addEventButtonTitleKey) },
icon: { Image(systemName: viewModel.addEventButtonImageName) }
)
})
}
}
.sheet(
isPresented: $showEventEntry,
content: {
NavigationView {
CountdownEventEntryView(
viewModel:
CountdownEventEntryViewModel(
timerClient: .live
)
)
}
}
)
.onReceive(viewModel.$now, perform: { now in
self.now = now
})
}
// MARK: Initialization
init(viewModel: CountdownEventsViewModel) {
self.viewModel = viewModel
}
元素已更新为预期值。另外,由于向Text
运行循环和List
模式添加了Text
,因此我能够滚动Timer
并看到main
值的更新。但是,当我导航到事件条目视图时,计时器触发时,视图模型似乎会重置。
下面,您会看到每次触发计时器都会重置输入的文本:
似乎我的视图模型以某种方式被重新创建,但是两个视图模型都是common
,所以我不确定为什么在计时器触发后看到值重置。计时器启动时,@ObservableObject
,DatePicker
,Toggle
和TextField
的值都重置为默认值。
我缺少什么导致计时器触发时清除文本?
如果有帮助,项目位于here。确保使用Button
分支。此外,如果可以帮助提供更多上下文,那么我将在Point-Free的teachings about dependency injection(code)之后对客户端类型进行建模。