diff --git a/Sources/TokamakCore/Modifiers/AppearanceActionModifier.swift b/Sources/TokamakCore/Modifiers/AppearanceActionModifier.swift new file mode 100644 index 000000000..a10399c22 --- /dev/null +++ b/Sources/TokamakCore/Modifiers/AppearanceActionModifier.swift @@ -0,0 +1,46 @@ +// Copyright 2020 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +protocol AppearanceActionProtocol { + var appear: (() -> ())? { get } + var disappear: (() -> ())? { get } +} + +/// Underscore is present in the name for SwiftUI compatibility. +struct _AppearanceActionModifier: ViewModifier { + var appear: (() -> ())? + var disappear: (() -> ())? + init(appear: (() -> ())? = nil, disappear: (() -> ())? = nil) { + self.appear = appear + self.disappear = disappear + } + + typealias Body = Never +} + +extension ModifiedContent: AppearanceActionProtocol + where Content: View, Modifier == _AppearanceActionModifier { + var appear: (() -> ())? { modifier.appear } + var disappear: (() -> ())? { modifier.disappear } +} + +extension View { + public func onAppear(perform action: (() -> ())? = nil) -> some View { + modifier(_AppearanceActionModifier(appear: action)) + } + + public func onDisappear(perform action: (() -> ())? = nil) -> some View { + modifier(_AppearanceActionModifier(disappear: action)) + } +} diff --git a/Sources/TokamakCore/Modifiers/ViewModifier.swift b/Sources/TokamakCore/Modifiers/ViewModifier.swift index 68cf2c83b..422b07405 100644 --- a/Sources/TokamakCore/Modifiers/ViewModifier.swift +++ b/Sources/TokamakCore/Modifiers/ViewModifier.swift @@ -28,8 +28,8 @@ public struct _ViewModifier_Content: View where Modifier: ViewModifier } public extension View { - func modifier(_ modifier: Modifier) -> Modifier.Body where Modifier: ViewModifier { - modifier.body(content: .init(modifier: modifier, view: AnyView(self))) + func modifier(_ modifier: Modifier) -> ModifiedContent { + .init(content: self, modifier: modifier) } } diff --git a/Sources/TokamakCore/MountedViews/MountedCompositeView.swift b/Sources/TokamakCore/MountedViews/MountedCompositeView.swift index c3ead1f73..08c88085d 100644 --- a/Sources/TokamakCore/MountedViews/MountedCompositeView.swift +++ b/Sources/TokamakCore/MountedViews/MountedCompositeView.swift @@ -44,6 +44,10 @@ final class MountedCompositeView: MountedView, Hashable { override func mount(with reconciler: StackReconciler) { let childBody = reconciler.render(compositeView: self) + if let appearanceAction = view.view as? AppearanceActionProtocol { + appearanceAction.appear?() + } + let child: MountedView = childBody.makeMountedView(parentTarget, environmentValues) mountedChildren = [child] @@ -52,6 +56,10 @@ final class MountedCompositeView: MountedView, Hashable { override func unmount(with reconciler: StackReconciler) { mountedChildren.forEach { $0.unmount(with: reconciler) } + + if let appearanceAction = view.view as? AppearanceActionProtocol { + appearanceAction.disappear?() + } } override func update(with reconciler: StackReconciler) { diff --git a/Sources/TokamakDOM/Modifiers/_ViewModifier_Content.swift b/Sources/TokamakDOM/Modifiers/_ViewModifier_Content.swift index 34f8f160e..9dc6a986f 100644 --- a/Sources/TokamakDOM/Modifiers/_ViewModifier_Content.swift +++ b/Sources/TokamakDOM/Modifiers/_ViewModifier_Content.swift @@ -15,17 +15,15 @@ import Runtime import TokamakCore -public typealias _ViewModifier_Content = TokamakCore._ViewModifier_Content - -extension _ViewModifier_Content: ViewDeferredToRenderer { +extension ModifiedContent: ViewDeferredToRenderer where Content: View { public var deferredBody: AnyView { if let domModifier = modifier as? DOMViewModifier { return AnyView(HTML("div", domModifier.attributes) { - view + content }) } else { return AnyView(HTML("div") { - view + content }) } } diff --git a/Sources/TokamakDemo/Counter.swift b/Sources/TokamakDemo/Counter.swift index 519279974..66a023d4b 100644 --- a/Sources/TokamakDemo/Counter.swift +++ b/Sources/TokamakDemo/Counter.swift @@ -29,6 +29,8 @@ public struct Counter: View { Button("Increment") { count += 1 } Text("\(count)") } + .onAppear { print("Counter.VStack onAppear") } + .onDisappear { print("Counter.VStack onDisappear") } ) : AnyView( VStack { Text("Limit exceeded") } )