Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 19 additions & 4 deletions Sources/OpenSwiftUI/Core/Update/Update.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,24 @@ enum Update {

@inline(__always)
static func dispatchActions() {
// FIXME
for action in actions {
action()
guard !actions.isEmpty else {
return
}

let actions = Update.actions
Update.actions = []
performOnMainThread {
// TODO: Signpost.postUpdateActions
begin()
for action in actions {
let oldDepth = depth
action()
let newDepth = depth
if newDepth != oldDepth {
fatalError("Action caused unbalanced updates.")
}
}
end()
}
}

Expand Down Expand Up @@ -107,5 +122,5 @@ extension Update {
// FIXME: migrate to use @_extern(c, "xx") in Swift 6
extension MovableLock {
@_silgen_name("_MovableLockSyncMain")
static func syncMain(lock: MovableLock ,body: @escaping () -> Void)
static func syncMain(lock: MovableLock, body: @escaping () -> Void)
}
6 changes: 6 additions & 0 deletions Sources/OpenSwiftUI/Core/View/View/ViewInputs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ public struct _ViewInputs {
return newInputs
}

// MARK: - base.phase
@inline(__always)
var phase: Attribute<_GraphInputs.Phase> {
base.phase
}

// MARK: - base.changedDebugProperties

@inline(__always)
Expand Down
159 changes: 159 additions & 0 deletions Sources/OpenSwiftUI/View/Modifier/AppearanceActionModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//
// AppearanceActionModifier.swift
// OpenSwiftUI
//
// Audited for RELEASE_2021
// Status: Blocked by _makeViewList
// ID: 8817D3B1C81ADA2B53E3500D727F785A

// MARK: - AppearanceActionModifier

internal import OpenGraphShims

/// A modifier that triggers actions when its view appears and disappears.
@frozen
public struct _AppearanceActionModifier: PrimitiveViewModifier {
public var appear: (() -> Void)?
public var disappear: (() -> Void)?

@inlinable
public init(appear: (() -> Void)? = nil, disappear: (() -> Void)? = nil) {
self.appear = appear
self.disappear = disappear
}

public static func _makeView(
modifier: _GraphValue<Self>,
inputs: _ViewInputs,
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
) -> _ViewOutputs {
let effect = AppearanceEffect(modifier: modifier.value, phase: inputs.phase)
let attribute = Attribute(effect)
attribute.flags = [.active, .removable]
return body(_Graph(), inputs)
}

public static func _makeViewList(
modifier: _GraphValue<Self>,
inputs: _ViewListInputs,
body: @escaping (_Graph, _ViewListInputs) -> _ViewListOutputs
) -> _ViewListOutputs {
fatalError("TODO")
}
}

// MARK: - AppearanceEffect

private struct AppearanceEffect {
@Attribute
var modifier: _AppearanceActionModifier
@Attribute
var phase: _GraphInputs.Phase
var lastValue: _AppearanceActionModifier?
var isVisible: Bool = false
var resetSeed: UInt32 = 0
var node: AnyOptionalAttribute = AnyOptionalAttribute()

mutating func appeared() {
guard !isVisible else { return }
defer { isVisible = true }
guard let lastValue,
let appear = lastValue.appear
else { return }
Update.enqueueAction(appear)
}

mutating func disappeared() {
guard isVisible else { return }
defer { isVisible = false }
guard let lastValue,
let disappear = lastValue.disappear
else { return }
Update.enqueueAction(disappear)
}
}

// MARK: AppearanceEffect + StatefulRule

extension AppearanceEffect: StatefulRule {
typealias Value = Void

mutating func updateValue() {
#if canImport(Darwin)
if node.attribute == nil {
node.attribute = .current
}

if phase.seed != resetSeed {
resetSeed = phase.seed
disappeared()
}
lastValue = modifier
appeared()
#else
fatalError("See #39")
#endif
}
}

#if canImport(Darwin) // See #39

// MARK: AppearanceEffect + RemovableAttribute

extension AppearanceEffect: RemovableAttribute {
static func willRemove(attribute: OGAttribute) {
let appearancePointer = UnsafeMutableRawPointer(mutating: attribute.info.body)
.assumingMemoryBound(to: AppearanceEffect.self)
guard appearancePointer.pointee.lastValue != nil else {
return
}
appearancePointer.pointee.disappeared()
}

static func didReinsert(attribute: OGAttribute) {
let appearancePointer = UnsafeMutableRawPointer(mutating: attribute.info.body)
.assumingMemoryBound(to: AppearanceEffect.self)
guard let nodeAttribute = appearancePointer.pointee.node.attribute else {
return
}
nodeAttribute.invalidateValue()
nodeAttribute.graph.graphHost().graphInvalidation(from: nil)
}
}
#endif

// MARK: - View Extension

extension View {
/// Adds an action to perform before this view appears.
///
/// The exact moment that OpenSwiftUI calls this method
/// depends on the specific view type that you apply it to, but
/// the `action` closure completes before the first
/// rendered frame appears.
///
/// - Parameter action: The action to perform. If `action` is `nil`, the
/// call has no effect.
///
/// - Returns: A view that triggers `action` before it appears.
@inlinable
public func onAppear(perform action: (() -> Void)? = nil) -> some View {
modifier(_AppearanceActionModifier(appear: action, disappear: nil))
}

/// Adds an action to perform after this view disappears.
///
/// The exact moment that OpenSwiftUI calls this method
/// depends on the specific view type that you apply it to, but
/// the `action` closure doesn't execute until the view
/// disappears from the interface.
///
/// - Parameter action: The action to perform. If `action` is `nil`, the
/// call has no effect.
///
/// - Returns: A view that triggers `action` after it disappears.
@inlinable
public func onDisappear(perform action: (() -> Void)? = nil) -> some View {
modifier(_AppearanceActionModifier(appear: nil, disappear: action))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// AppearanceActionModifierTests.swift
// OpenSwiftUICompatibilityTests

import Testing

#if canImport(Darwin)
struct AppearanceActionModifierTests {
@Test
func appear() async throws {
struct ContentView: View {
var confirmation: Confirmation

var body: some View {
AnyView(EmptyView())
.onAppear {
confirmation()
}
}
}

#if os(iOS)
await confirmation { @MainActor confirmation in
let vc = UIHostingController(rootView: ContentView(confirmation: confirmation))
vc.triggerLayout()
workaroundIssue87(vc)
}
#endif
}

// TODO: Add disappear support and test case
}
#endif