From 3b7e4be1361e76d4566449114482a4fa575839ab Mon Sep 17 00:00:00 2001 From: Prashanth Govindaraj Date: Fri, 1 May 2026 21:29:08 -0700 Subject: [PATCH] Add popAndPush convenience method (fixes #110) Adds a method to (where Element: RouteProtocol), , and that atomically pops the top pushed screen and pushes a new screen in a single routes mutation. --- .../Array+convenienceMethods.swift | 12 ++++++++++++ .../FlowNavigator+convenienceMethods.swift | 11 +++++++++++ .../FlowPath+convenienceMethods.swift | 11 +++++++++++ .../ConvenienceMethodsTests.swift | 18 ++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/Sources/FlowStacks/Convenience methods/Array+convenienceMethods.swift b/Sources/FlowStacks/Convenience methods/Array+convenienceMethods.swift index 806188a..c4484c5 100644 --- a/Sources/FlowStacks/Convenience methods/Array+convenienceMethods.swift +++ b/Sources/FlowStacks/Convenience methods/Array+convenienceMethods.swift @@ -278,6 +278,18 @@ public extension Array where Element: RouteProtocol, Element.Screen: Identifiabl } } +// MARK: - Pop and push + +public extension Array where Element: RouteProtocol { + /// Pops the top pushed screen and pushes a new screen, resulting in a push transition to the new screen. + /// Only the top screen will be popped, and it must have been pushed (not presented). + /// - Parameter screen: The new screen to push. + mutating func popAndPush(_ screen: Element.Screen) { + pop() + push(screen) + } +} + // MARK: - Dismiss public extension Array where Element: RouteProtocol { diff --git a/Sources/FlowStacks/Convenience methods/FlowNavigator+convenienceMethods.swift b/Sources/FlowStacks/Convenience methods/FlowNavigator+convenienceMethods.swift index 29ea274..43c26c8 100644 --- a/Sources/FlowStacks/Convenience methods/FlowNavigator+convenienceMethods.swift +++ b/Sources/FlowStacks/Convenience methods/FlowNavigator+convenienceMethods.swift @@ -256,6 +256,17 @@ public extension FlowNavigator where Screen == AnyHashable { } } +// MARK: - Pop and push + +public extension FlowNavigator { + /// Pops the top pushed screen and pushes a new screen, resulting in a push transition to the new screen. + /// Only the top screen will be popped, and it must have been pushed (not presented). + /// - Parameter screen: The new screen to push. + func popAndPush(_ screen: Screen) { + routes.popAndPush(screen) + } +} + // MARK: - Dismiss public extension FlowNavigator { diff --git a/Sources/FlowStacks/Convenience methods/FlowPath+convenienceMethods.swift b/Sources/FlowStacks/Convenience methods/FlowPath+convenienceMethods.swift index 0b63acd..8eb14a7 100644 --- a/Sources/FlowStacks/Convenience methods/FlowPath+convenienceMethods.swift +++ b/Sources/FlowStacks/Convenience methods/FlowPath+convenienceMethods.swift @@ -180,6 +180,17 @@ public extension FlowPath { } } +// MARK: - Pop and push + +public extension FlowPath { + /// Pops the top pushed screen and pushes a new screen, resulting in a push transition to the new screen. + /// Only the top screen will be popped, and it must have been pushed (not presented). + /// - Parameter screen: The new screen to push. + mutating func popAndPush(_ screen: AnyHashable) { + routes.popAndPush(screen) + } +} + // MARK: - Dismiss public extension FlowPath { diff --git a/Tests/FlowStacksTests/ConvenienceMethodsTests.swift b/Tests/FlowStacksTests/ConvenienceMethodsTests.swift index e83f9a1..d7306c4 100644 --- a/Tests/FlowStacksTests/ConvenienceMethodsTests.swift +++ b/Tests/FlowStacksTests/ConvenienceMethodsTests.swift @@ -86,6 +86,24 @@ final class ConvenienceMethodsTests: XCTestCase { XCTAssertEqual(path.count, 2) } + func testPopAndPush() { + var path = FlowPath([.push(1), .push("two"), .push(true)]) + path.popAndPush("three") + XCTAssertEqual(path.count, 3) + // Last route should be a push of the new screen + XCTAssertEqual(path.routes.last?.style, .push) + XCTAssertEqual(path.routes.last?.screen as? String, "three") + // Previous screen should be unchanged + XCTAssertEqual(path.routes[1].screen as? String, "two") + } + + func testPopAndPushFromSingleScreen() { + var path = FlowPath([.push(1)]) + path.popAndPush("replacement") + XCTAssertEqual(path.count, 1) + XCTAssertEqual(path.routes.last?.screen as? String, "replacement") + } + func testDismissAllWhereFirstIsPushed() { var path = FlowPath([.push(1), .sheet("two"), .push(3), .cover("four"), .push(5), .cover("six"), .push(7)]) path.dismiss()