Created
October 8, 2025 01:38
-
-
Save Segmentational/c88d4a74d04181da2acc3e62230f1a9c to your computer and use it in GitHub Desktop.
Example-Observations-Usage.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Combine | |
| import OSLog | |
| import Persistables | |
| import SwiftData | |
| import SwiftUI | |
| extension Tags { | |
| public enum Views {} | |
| } | |
| extension Tags.Views { | |
| @MainActor | |
| @Observable | |
| public final class Watcher { | |
| public private(set) var exception: (any Error)? = nil | |
| public var hovering: Bool = false | |
| public private(set) var seeding: Bool = false | |
| public private(set) var task: Task<Result<Void, Error>, Never>? = nil | |
| public private(set) var response: Models.Tags.API.Response? = nil | |
| @Sendable | |
| public nonisolated func observe() -> Task< | |
| Observations< | |
| ( | |
| hovering: Bool, | |
| seeding: Bool, | |
| task: Bool, | |
| response: Models.Tags.API.Response? | |
| ), Never | |
| >, Never | |
| > { | |
| return Task { @MainActor in | |
| let observations = Observations { | |
| ( | |
| hovering: self.hovering, | |
| seeding: self.seeding, | |
| task: self.task != nil, | |
| response: self.response | |
| ) | |
| } | |
| return observations | |
| } | |
| } | |
| private static let logger: Logger = Polyium.Logger(String(reflecting: Watcher.self)) | |
| func hydrate(_ context: ModelContext) async { | |
| guard task == nil else { return } | |
| seeding = true | |
| defer { seeding = false } | |
| defer { task = nil } | |
| task = Task { | |
| do { | |
| let tags = Models.Tags.API(modelContainer: context.container) | |
| do { | |
| let ctx = ModelContext(context.container) | |
| let external = try await tags.seed(ctx) | |
| // I worked pretty damn hard on the loading button; because the view | |
| // doesn't become readily available ANYWAYS due to... something with @Query, | |
| // might as well show off the button for a little while longer while persistence | |
| // catches up. | |
| try await Task.sleep(for: .seconds(5)) | |
| response = external | |
| } catch { | |
| Self.logger.error("Error while seeding: \(error)") | |
| throw error | |
| } | |
| } catch { | |
| return .failure(error) | |
| } | |
| return .success(()) | |
| } | |
| switch await task?.value { | |
| case .none, .success: | |
| Self.logger.debug("Task completed successfully without error.") | |
| case .failure(let error): | |
| self.exception = error | |
| Self.logger.error("Task failure. Exception: \(error).") | |
| } | |
| } | |
| nonisolated func cancel() async { | |
| await Self.logger.debug("Running nonisolated version of cancel.") | |
| // Grab handle and clear state on MainActor in one hop. | |
| let handle = await MainActor.run { () -> Task<Result<Void, Error>, Never>? in | |
| let h = self.task | |
| self.task = nil | |
| self.seeding = false | |
| return h | |
| } | |
| handle?.cancel() | |
| } | |
| func cancel() { | |
| Self.logger.debug("Running main actor isolated version of cancel.") | |
| let handle = task | |
| handle?.cancel() | |
| task = nil | |
| seeding = false | |
| } | |
| } | |
| } | |
| struct Hydration: View { | |
| public var ns: Namespace.ID | |
| @Binding var watcher: Tags.Views.Watcher | |
| @Environment(\.modelContext) private var context | |
| private static let logger: Logger = Polyium.Logger(String(reflecting: Self.self)) | |
| var body: some View { | |
| Button { | |
| Task { | |
| await watcher.hydrate(context) | |
| } | |
| } label: { | |
| HStack { | |
| if watcher.seeding { | |
| Icon(ns: ns, watcher: $watcher) | |
| .matchedTransitionSource(id: "icon-transition-source", in: ns) | |
| .matchedGeometryEffect(id: "icon-geometry", in: ns) | |
| } else { | |
| Text("Test") | |
| .matchedTransitionSource(id: "title-transition-source", in: ns) | |
| .matchedGeometryEffect(id: "title-geometry", in: ns) | |
| } | |
| } | |
| } | |
| .buttonStyle(.glass) | |
| .buttonBorderShape(.capsule) | |
| .onHover { hovering in | |
| withAnimation { | |
| self.watcher.hovering = hovering | |
| } | |
| } | |
| .contextMenu { | |
| Button(role: .cancel) { | |
| watcher.cancel() | |
| } label: { | |
| Label("Cancel", systemImage: "xmark") | |
| } | |
| } | |
| .swipeActions(edge: .trailing, allowsFullSwipe: false) { | |
| Button(role: .destructive) { | |
| Task { | |
| await watcher.cancel() | |
| } | |
| } label: { | |
| Image(systemName: "xmark") | |
| .help(Text("Cancel")) | |
| } | |
| .help("Cancel") | |
| .labelStyle(.iconOnly) | |
| } | |
| .matchedTransitionSource( | |
| id: "button-transition-source", in: ns, | |
| configuration: { configuration in | |
| configuration.background(.adaptive(Colors.purple, alpha: watcher.hovering ? 0.5 : 0.25)) | |
| .clipShape(.rect(cornerRadius: 100, style: .circular)) | |
| } | |
| ) | |
| .task { | |
| let observations = await watcher.observe().value | |
| for await observation in observations { | |
| Self.logger.debug("\(String(describing: observation))") | |
| } | |
| } | |
| .invalidatableContent(true) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment