Skip to content

Instantly share code, notes, and snippets.

@pteasima
Created June 22, 2022 20:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pteasima/12d87cf862caa0faedd709ae8f553142 to your computer and use it in GitHub Desktop.
Save pteasima/12d87cf862caa0faedd709ae8f553142 to your computer and use it in GitHub Desktop.
LifetimeTask and ThrowingLifetimeTask modifiers
import SwiftUI
private struct LifetimeTask: ViewModifier {
@State @Reference private var hasAppeared = false
@State @Reference private var task: Task<Void, Never>?
let perform: () async -> Void
func body(content: Content) -> some View {
content
.onFirstAppear {
task = Task {
await perform()
}
}
.onDestroy {
task?.cancel()
}
}
}
public extension View {
func lifetimeTask(perform: @escaping () async -> Void) -> some View {
self.modifier(LifetimeTask(perform: perform))
}
}
private struct ThrowingLifetimeTask: ViewModifier {
@State @Reference private var hasAppeared = false
@State @Reference private var task: Task<Void, Never>?
@Environment(\.[service: \Throw.self]) private var `throw`
let perform: () async throws -> Void
func body(content: Content) -> some View {
content
.onFirstAppear {
task = `throw`.try {
try await perform()
}
}
.onDestroy {
task?.cancel()
}
}
}
public extension View {
func throwingLifetimeTask(perform: @escaping () async throws -> Void) -> some View {
self.modifier(ThrowingLifetimeTask(perform: perform))
}
}
// MARK: OnFirstAppear and OnDestroy helpers
// We shouldn't need to use these in client code, so they're fileprivate.
private struct OnFirstAppear: ViewModifier {
@State @Reference private var hasAppeared = false
var perform: () -> Void
func body(content: Content) -> some View {
content
.onAppear {
guard !hasAppeared else { return }
hasAppeared = true
perform()
}
}
}
fileprivate extension View {
func onFirstAppear(perform: @escaping () -> Void) -> some View {
self.modifier(OnFirstAppear(perform: perform))
}
}
private struct OnDestroy: ViewModifier {
let onDestroy: () -> Void
final class Lifetime {
var onDestroy: () -> Void = { }
deinit { onDestroy() }
}
@State var lifetime: Lifetime = .init()
func body(content: Content) -> some View {
lifetime.onDestroy = onDestroy
return content
.background(Color.clear) //this needs to be actually different than content itself, else `body(content:)` wont even run (thanks to SwiftUI magic)
}
}
fileprivate extension View {
func onDestroy(_ onDestroy: @escaping () -> Void) -> some View {
self.modifier(OnDestroy(onDestroy: onDestroy))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment