Skip to content

Commit

Permalink
Add onFirstAppear modifier to fix circular navigation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeehut committed Nov 23, 2024
1 parent 8a58918 commit 47dc334
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ The example shows how `.confirmDeleteDialog` handles the entire deletion flow
- ``SwiftUICore/View/macOSOnlyPadding(_:_:)``
- ``SwiftUICore/View/macOSOnlyPadding(insets:)``
- ``SwiftUICore/View/macOSOnlyFrame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:)``
- ``SwiftUICore/View/onFirstAppear(perform:)``
- ``SwiftUICore/View/progressOverlay(type:)``
- ``SwiftUICore/View/roundedRectangleBorder(_:cornerRadius:lineWidth:)``
- ``SwiftUICore/View/throwingTask(asyncAction:catchError:)``
Expand Down
56 changes: 56 additions & 0 deletions Sources/HandySwiftUI/Modifiers/FirstAppearModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import SwiftUI

struct FirstAppearModifier: ViewModifier {
@State private var hasAppeared = false
let perform: () -> Void

func body(content: Content) -> some View {
content.onAppear {
guard !hasAppeared else { return }
self.hasAppeared = true
self.perform()
}
}
}

extension View {
/// Adds a modifier that runs a closure only on the first appearance of a view,
/// even across navigation stack transitions or view reconstructions.
///
/// This is particularly useful in SwiftUI navigation scenarios where you want to perform
/// an action only once when a view is initially displayed, but not when navigating back
/// to it in a NavigationStack.
///
/// Example usage:
/// ```swift
/// struct ProductListView: View {
/// @State private var selectedProduct: Product?
/// let products: [Product]
///
/// var body: some View {
/// List(products) { product in
/// ProductRow(product: product)
/// }
/// .onFirstAppear {
/// // Auto-select first product only once,
/// // not when navigating back
/// if let firstProduct = products.first {
/// selectedProduct = firstProduct
/// }
/// }
/// }
/// }
/// ```
///
/// Common use cases include:
/// - Initial data loading or setup
/// - One-time animations or tutorials
/// - Analytics events that should only fire once
/// - Auto-selection of initial values
///
/// - Parameter perform: The closure to execute on first appearance.
/// - Returns: A view with the first-appear modifier applied.
public func onFirstAppear(perform: @escaping () -> Void) -> some View {
self.modifier(FirstAppearModifier(perform: perform))
}
}

0 comments on commit 47dc334

Please sign in to comment.