Skip to content

Commit d94e00a

Browse files
committed
Implement onAppear and onDisappear view modifiers
1 parent 0f2d23d commit d94e00a

File tree

5 files changed

+167
-21
lines changed

5 files changed

+167
-21
lines changed

Sources/SwiftCrossUI/Modifiers/AlertModifier.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ struct AlertModifierView<Child: View>: TypeSafeView {
7070
)
7171
backend.showAlert(
7272
alert,
73-
window: environment.window! as! Backend.Window
73+
window: .some(environment.window! as! Backend.Window)
7474
) { responseId in
7575
children.alert = nil
7676
isPresented.wrappedValue = false
@@ -80,7 +80,7 @@ struct AlertModifierView<Child: View>: TypeSafeView {
8080
} else if isPresented.wrappedValue == false && children.alert != nil {
8181
backend.dismissAlert(
8282
children.alert as! Backend.Alert,
83-
window: environment.window! as! Backend.Window
83+
window: .some(environment.window! as! Backend.Window)
8484
)
8585
children.alert = nil
8686
}

Sources/SwiftCrossUI/Modifiers/EnvironmentModifier.swift

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
struct EnvironmentModifier<Child: View>: TypeSafeView {
2-
typealias Children = TupleView1<Child>.Children
3-
1+
struct EnvironmentModifier<Child: View>: LightweightView {
42
var body: TupleView1<Child>
53
var modification: (EnvironmentValues) -> EnvironmentValues
64

@@ -13,31 +11,17 @@ struct EnvironmentModifier<Child: View>: TypeSafeView {
1311
backend: Backend,
1412
snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
1513
environment: EnvironmentValues
16-
) -> Children {
14+
) -> any ViewGraphNodeChildren {
1715
body.children(
1816
backend: backend,
1917
snapshots: snapshots,
2018
environment: modification(environment)
2119
)
2220
}
2321

24-
func layoutableChildren<Backend: AppBackend>(
25-
backend: Backend,
26-
children: Children
27-
) -> [LayoutSystem.LayoutableChild] {
28-
[]
29-
}
30-
31-
func asWidget<Backend: AppBackend>(
32-
_ children: Children,
33-
backend: Backend
34-
) -> Backend.Widget {
35-
return body.asWidget(children, backend: backend)
36-
}
37-
3822
func update<Backend: AppBackend>(
3923
_ widget: Backend.Widget,
40-
children: Children,
24+
children: any ViewGraphNodeChildren,
4125
proposedSize: SIMD2<Int>,
4226
environment: EnvironmentValues,
4327
backend: Backend,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
extension View {
2+
/// Adds an action to be performed before this view appears.
3+
///
4+
/// The exact moment that the action gets called is an internal detail and
5+
/// may change at any time, but it is guaranteed to be after accessing the
6+
/// view's ``View/body`` and before the view appears on screen. Currently,
7+
/// if these docs have been kept up to date, the action gets called just
8+
/// before creating the view's widget.
9+
public func onAppear(perform action: @escaping () -> Void) -> some View {
10+
OnAppearModifier(body: self, action: action)
11+
}
12+
}
13+
14+
struct OnAppearModifier<Content: View>: LightweightView {
15+
var body: Content
16+
var action: () -> Void
17+
18+
func asWidget<Backend: AppBackend>(
19+
_ children: any ViewGraphNodeChildren,
20+
backend: Backend
21+
) -> Backend.Widget {
22+
action()
23+
return body.asWidget(children, backend: backend)
24+
}
25+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
extension View {
2+
/// Adds an action to be performed after this view disappears.
3+
///
4+
/// `onDisappear` actions on outermost views are called first and propagate
5+
/// down to the leaf views due to essentially relying on the `deinit` of the
6+
/// modifier view's ``ViewGraphNode``.
7+
public func onDisappear(perform action: @escaping () -> Void) -> some View {
8+
OnDisappearModifier(body: self, action: action)
9+
}
10+
}
11+
12+
struct OnDisappearModifier<Content: View>: TypeSafeView {
13+
var body: Content
14+
var action: () -> Void
15+
16+
func children<Backend: AppBackend>(
17+
backend: Backend,
18+
snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
19+
environment: EnvironmentValues
20+
) -> OnDisappearModifierChildren {
21+
OnDisappearModifierChildren(
22+
wrappedChildren: body.children(
23+
backend: backend,
24+
snapshots: snapshots,
25+
environment: environment
26+
),
27+
action: action
28+
)
29+
}
30+
31+
func layoutableChildren<Backend: AppBackend>(
32+
backend: Backend,
33+
children: OnDisappearModifierChildren
34+
) -> [LayoutSystem.LayoutableChild] {
35+
body.layoutableChildren(
36+
backend: backend,
37+
children: children.wrappedChildren
38+
)
39+
}
40+
41+
func asWidget<Backend: AppBackend>(
42+
_ children: OnDisappearModifierChildren,
43+
backend: Backend
44+
) -> Backend.Widget {
45+
body.asWidget(children.wrappedChildren, backend: backend)
46+
}
47+
48+
func update<Backend: AppBackend>(
49+
_ widget: Backend.Widget,
50+
children: OnDisappearModifierChildren,
51+
proposedSize: SIMD2<Int>,
52+
environment: EnvironmentValues,
53+
backend: Backend,
54+
dryRun: Bool
55+
) -> ViewSize {
56+
body.update(
57+
widget,
58+
children: children.wrappedChildren,
59+
proposedSize: proposedSize,
60+
environment: environment,
61+
backend: backend,
62+
dryRun: dryRun
63+
)
64+
}
65+
}
66+
67+
class OnDisappearModifierChildren: ViewGraphNodeChildren {
68+
var wrappedChildren: any ViewGraphNodeChildren
69+
var action: () -> Void
70+
71+
var widgets: [AnyWidget] {
72+
wrappedChildren.widgets
73+
}
74+
75+
var erasedNodes: [ErasedViewGraphNode] {
76+
wrappedChildren.erasedNodes
77+
}
78+
79+
init(
80+
wrappedChildren: any ViewGraphNodeChildren,
81+
action: @escaping () -> Void
82+
) {
83+
self.wrappedChildren = wrappedChildren
84+
self.action = action
85+
}
86+
87+
deinit {
88+
action()
89+
}
90+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/// A view that wraps its content without introducing any extra layers of
2+
/// widgets. Defer's to the body's `View` implementation directly by default,
3+
/// allowing `LightweightView`s to just tweak/wrap a few of the view without
4+
/// introducing extra layout system overhead or view graph nodes etc.
5+
protocol LightweightView: View {}
6+
7+
extension LightweightView {
8+
func children<Backend: AppBackend>(
9+
backend: Backend,
10+
snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
11+
environment: EnvironmentValues
12+
) -> any ViewGraphNodeChildren {
13+
body.children(backend: backend, snapshots: snapshots, environment: environment)
14+
}
15+
16+
func layoutableChildren<Backend: AppBackend>(
17+
backend: Backend,
18+
children: any ViewGraphNodeChildren
19+
) -> [LayoutSystem.LayoutableChild] {
20+
body.layoutableChildren(backend: backend, children: children)
21+
}
22+
23+
func asWidget<Backend: AppBackend>(
24+
_ children: any ViewGraphNodeChildren,
25+
backend: Backend
26+
) -> Backend.Widget {
27+
body.asWidget(children, backend: backend)
28+
}
29+
30+
func update<Backend: AppBackend>(
31+
_ widget: Backend.Widget,
32+
children: any ViewGraphNodeChildren,
33+
proposedSize: SIMD2<Int>,
34+
environment: EnvironmentValues,
35+
backend: Backend,
36+
dryRun: Bool
37+
) -> ViewSize {
38+
body.update(
39+
widget,
40+
children: children,
41+
proposedSize: proposedSize,
42+
environment: environment,
43+
backend: backend,
44+
dryRun: dryRun
45+
)
46+
}
47+
}

0 commit comments

Comments
 (0)