Created
June 22, 2022 20:04
-
-
Save pteasima/12d87cf862caa0faedd709ae8f553142 to your computer and use it in GitHub Desktop.
LifetimeTask and ThrowingLifetimeTask modifiers
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 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