Как обновить SwiftData (ручное) принесение всякий раз, когда изменяется база данных?IOS

Программируем под IOS
Ответить
Anonymous
 Как обновить SwiftData (ручное) принесение всякий раз, когда изменяется база данных?

Сообщение Anonymous »

Сводка < /h2>
У меня есть класс модели представления, в сочетании с представлением Swiftui, которые извлекают фильтрованные записи из SwiftData с использованием моделитора. Я использовал Swiftui .task (id :) , чтобы запустить повторные загрузки в модели представления всякий раз, когда изменяется мой предикат фильтра, но я хочу также запустить повторные загрузки всякий раз, когда сама база данных обновляется на протяжении всего моего пользовательского интерфейса. Как я могу сообщить модели представления о повторной загрузке всякий раз, когда SwiftData Commits My insert () и delete () вызовы? Традиционные @Query Настройки не предоставили той гибкости, которую я хотел (в частности, для рендеринга состояний загрузки), поэтому я создал фонового актера для обработки данных:

Код: Выделить всё

import Foundation
import SwiftData

@Model
final class Item {
var timestamp: Date

init(timestamp: Date) {
self.timestamp = timestamp
}
}

struct ItemView: Identifiable, Sendable {
var id: PersistentIdentifier
var timestamp: Date

init(_ model: Item) {
id = model.id
timestamp = model.timestamp
}
}

@ModelActor
actor ThreadsafeBackgroundActor: Sendable {
private var context: ModelContext { modelExecutor.modelContext }

func fetchData(_ predicate: Predicate? = nil) throws -> [ItemView] {
let descriptor = if let p = predicate {
FetchDescriptor(predicate: p)
} else {
FetchDescriptor()
}
let items = try context.fetch(descriptor)
return items.map(ItemView.init)
}
}
< /code>
У меня также есть модель представления, называемая актером: < /p>
import SwiftUI
import SwiftData

@Observable
class ItemListViewModel {
enum State {
case idle
case loading
case failed(Error)
case loaded([ItemView])
}

private(set) var state = State.idle

func fetchData(container: ModelContainer, predicate: Predicate) async throws ->  [ItemView] {
let service = ThreadsafeBackgroundActor(modelContainer: container)
return try await service.fetchData(predicate)
}

@MainActor func load(container: ModelContainer, predicate: Predicate) async {
state = .loading

do {
// Artificial delay to visualize loading state
try await Task.sleep(for: .seconds(1))
let items = try await fetchData(container: container, predicate: predicate)
state = .loaded(items)
} catch is CancellationError {
state = .idle
} catch {
state = .failed(error)
}
}
}
И у меня есть задача в моем представлении Swiftui, чтобы начать начальную нагрузку:

Код: Выделить всё

import SwiftUI
import SwiftData

struct ContentView: View {
@State private var viewModel = ItemListViewModel()
@Environment(\.modelContext) private var modelContext
@State private var refreshCount = 0

var body: some View {
NavigationSplitView {
Group {
switch viewModel.state {
case .idle:
EmptyView()
case .loading:
ProgressView()
case .failed(let error):
Text("Error: \(error)")
case .loaded(let items):
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
} label: {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
}
}
}
#if os(macOS)
.navigationSplitViewColumnWidth(min: 180, ideal: 200)
#endif
.toolbar {
#if os(iOS)
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
#endif
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
ToolbarItem {
Button {
do {
try modelContext.save()
} catch {
fatalError(error.localizedDescription)
}
} label: {
Label("Save", systemImage: "square.and.arrow.down")
}
}
ToolbarItem {
Button {
refreshCount += 1
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
}
}
}
} detail: {
Text("Select an item")
}
.task(id: refreshCount) {
// Typically the task ID would be tied to a dynamic predicate.
// I have it tied to a UI button (and made the predicate static) for a minimal example.
await viewModel.load(container: modelContext.container, predicate: #Predicate { _ in true })
}
}

private func addItem() {
withAnimation {
let newItem = Item(timestamp: Date())
modelContext.insert(newItem)
}
}
}
Эта настройка работает превосходно до тех пор, пока действия в представлении не обновит что -либо в базе данных . Действия ModelContext, например, context.insert () в additem , не запускайте мою функцию загрузки . Вызов context.save () (в действии кнопки «Сохранить») также не заставляет обновление. Позволить SwiftData работать с минимальным вмешательством от меня-цель. Хотя это полезно для пользователей, которые знают, что им нужны обновленные данные, я не хочу, чтобы достоверность представленных данных зависела от вмешательства пользователя. Области кода, которые обновляют индустрии базы данных, обновляется , и отдельная задача в моем списке просмотр просмотра.

Код: Выделить всё

.task(id: predicate) { /* Same call */ }
.task(id: refreshCount) { // Eww...
viewModel.load(container: modelContext.container, predicate: predicate)
}
Не только эта стратегия утомительна и хрупкая, но также не работает риск вызовой нагрузки несколько раз без необходимости (особенно во время первоначального рендеринга).

[*] Я рассмотрел систему потокового уведомления Swift. Я совершенно уверен, что nspersistentstoreremotechange - это то, что я хочу посмотреть. Я просто не могу понять, как/где инициализировать этого наблюдателя. AddObserver запрашивает аннотации Objective-C. Я не думаю, что .publisher (). Snin {} также является решением, потому что я хочу начать мутирующее вызов viewmodel.load () в (сбегающем) закрытии.



Подробнее здесь: https://stackoverflow.com/questions/797 ... is-changed
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»