diff --git a/README.md b/README.md index 0b2be3a..dc68678 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,10 @@ The methods currently provided by the Container Manager are. * dismissAllView(in containers: [String], onlyShowing: Bool, animated flag: Bool) Dismiss all views in the specified containers + +* dismissTopmostView(in containers: [String], animated flag: Bool) + + Dismiss the top view in specified containers ### Blockable animation diff --git a/READMECN.md b/READMECN.md index a68b6eb..51fcfde 100644 --- a/READMECN.md +++ b/READMECN.md @@ -340,6 +340,10 @@ struct ContentView1: View { 撤销指定容器内的所有视图 +* dismissTopmostView(in containers: [String], animated flag: Bool) + + 撤销指定容器的顶视图 + ### 可屏蔽动画 无论是直接调用容器管理器还是使用 View modifier,当将 animated 设为 false 时,均可强制取消转场动画。 diff --git a/Sources/SwiftUIOverlayContainer/Container/QueueHandler.swift b/Sources/SwiftUIOverlayContainer/Container/QueueHandler.swift index 7de88ab..ebf3f21 100644 --- a/Sources/SwiftUIOverlayContainer/Container/QueueHandler.swift +++ b/Sources/SwiftUIOverlayContainer/Container/QueueHandler.swift @@ -155,6 +155,12 @@ extension ContainerQueueHandler { } } + func dismissTopmostView(animated flag: Bool) { + if let theTopView = mainQueue.first { + dismiss(id: theTopView.id, animated: flag) + } + } + /// Push view into specific queue func pushViewIntoQueue(_ identifiableView: IdentifiableContainerView, queue: QueueType, animated flag: Bool = true) { switch queue { @@ -239,6 +245,8 @@ extension ContainerQueueHandler { dismissAll(animated: animated) case .dismissShowing(let animated): dismissMainQueue(animated: animated) + case .dismissTopmostView(let animated): + dismissTopmostView(animated: animated) } } @@ -256,6 +264,8 @@ extension ContainerQueueHandler { dismissAll(animated: animated) case .dismissShowing(let animated): dismissMainQueue(animated: animated) + case .dismissTopmostView(let animated): + dismissTopmostView(animated: animated) } } @@ -277,6 +287,8 @@ extension ContainerQueueHandler { dismissAll(animated: animated) case .dismissShowing(let animated): dismissMainQueue(animated: animated) + case .dismissTopmostView(let animated): + dismissTopmostView(animated: animated) } } diff --git a/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerAction.swift b/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerAction.swift index 2c30d57..c3e081f 100644 --- a/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerAction.swift +++ b/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerAction.swift @@ -30,4 +30,8 @@ enum OverlayContainerAction { /// /// Pass false to disable animation of transition case dismissShowing(Bool) + /// Dismiss the top view in the container + /// + /// Pass false to disable animation of transition + case dismissTopmostView(Bool) } diff --git a/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerManager.swift b/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerManager.swift index f0eb0d2..a39570b 100644 --- a/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerManager.swift +++ b/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerManager.swift @@ -216,6 +216,18 @@ extension ContainerManager: ContainerViewManagementForEnvironment { } } } + + /// Dismiss the top view in the containers + /// - Parameters: + /// - containers: container names + /// - flag: Pass false, disable animation when dismiss the view + public func dismissTopmostView(in containers: [String], animated flag: Bool) { + for container in containers { + if let publisher = getPublisher(for: container) { + publisher.upstream.send(.dismissTopmostView(flag)) + } + } + } } public extension ContainerManager { diff --git a/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerManagerProtocols.swift b/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerManagerProtocols.swift index 719d02f..92d51cc 100644 --- a/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerManagerProtocols.swift +++ b/Sources/SwiftUIOverlayContainer/ContainerManager/ContainerManagerProtocols.swift @@ -96,6 +96,12 @@ public protocol ContainerViewManagementForEnvironment { /// - containers: container names /// - flag: pass true to animate the transition func dismissAllView(in containers: [String], onlyShowing: Bool, animated flag: Bool) + + /// Dismiss the top view in containers + /// - Parameters: + /// - container: container names + /// - flag: pass true to animate the transition + func dismissTopmostView(in containers: [String], animated flag: Bool) } /// A type defines logging behavior diff --git a/Tests/SwiftUIOverlayContainerTests/QueueHandlerForMultipleTests.swift b/Tests/SwiftUIOverlayContainerTests/QueueHandlerForMultipleTests.swift index e1d9c04..0e3e6be 100644 --- a/Tests/SwiftUIOverlayContainerTests/QueueHandlerForMultipleTests.swift +++ b/Tests/SwiftUIOverlayContainerTests/QueueHandlerForMultipleTests.swift @@ -171,6 +171,29 @@ class QueueHandlerForMultipleUnitTests: XCTestCase { XCTAssertEqual(handler.mainQueue.count, 0) } + func testDismissTopmostView() throws { + // given + let view1 = IdentifiableContainerView( + id: UUID(), view: MessageView(), viewConfiguration: MessageView(), isPresented: nil + ) + let view2 = IdentifiableContainerView( + id: UUID(), view: MessageView(), viewConfiguration: MessageView(), isPresented: nil + ) + + let perform = handler.getStrategyHandler(for: .multiple) + + // when + perform(.show(view1, false)) + perform(.show(view2, false)) + + // dismiss view + perform(.dismissTopmostView(false)) + + // then + XCTAssertEqual(handler.mainQueue.count, 1) + XCTAssertEqual(handler.mainQueue.first?.id, view2.id) + } + func testShowViewAfterConnect() async throws { // given let view1 = MessageView() @@ -298,7 +321,6 @@ class QueueHandlerForMultipleUnitTests: XCTestCase { XCTAssertEqual(handler.mainQueue.count, 3) XCTAssertEqual(handler.tempQueue.count, 1) } - } struct ContainerConfiguration: ContainerConfigurationProtocol { diff --git a/Tests/SwiftUIOverlayContainerTests/QueueHandlerForOneByOneTests.swift b/Tests/SwiftUIOverlayContainerTests/QueueHandlerForOneByOneTests.swift index 81ab122..97bc4a8 100644 --- a/Tests/SwiftUIOverlayContainerTests/QueueHandlerForOneByOneTests.swift +++ b/Tests/SwiftUIOverlayContainerTests/QueueHandlerForOneByOneTests.swift @@ -98,6 +98,22 @@ class QueueHandlerForOneByOneTests: XCTestCase { XCTAssertEqual(handler.mainQueue.count, 0) } + func testDismissTopmostView() throws { + // given + let view = MessageView() + let identifiableView = IdentifiableContainerView( + id: UUID(), view: view, viewConfiguration: view, isPresented: nil + ) + let perform = handler.getStrategyHandler(for: .oneByOne) + + // when + perform(.show(identifiableView, true)) + perform(.dismissTopmostView(false)) + + // then + XCTAssertEqual(handler.mainQueue.count, 0) + } + func testDismissAllView() throws { // given let view = MessageView() diff --git a/Tests/SwiftUIOverlayContainerTests/QueueHandlerForOneByeOneWaitFinishTests.swift b/Tests/SwiftUIOverlayContainerTests/QueueHandlerForOneByeOneWaitFinishTests.swift index 53b5e94..436880c 100644 --- a/Tests/SwiftUIOverlayContainerTests/QueueHandlerForOneByeOneWaitFinishTests.swift +++ b/Tests/SwiftUIOverlayContainerTests/QueueHandlerForOneByeOneWaitFinishTests.swift @@ -146,6 +146,29 @@ class QueueHandlerForOneByeOneWaitFinishTests: XCTestCase { XCTAssertEqual(handler.tempQueue.count, 0) } + func testDismissTopmostView() async throws { + // given + let view = MessageView() + let identifiableView1 = IdentifiableContainerView( + id: UUID(), view: view, viewConfiguration: view, isPresented: nil + ) + let identifiableView2 = IdentifiableContainerView( + id: UUID(), view: view, viewConfiguration: view, isPresented: nil + ) + + let perform = handler.getStrategyHandler(for: .oneByOneWaitFinish) + + // when + perform(.show(identifiableView1, false)) + perform(.show(identifiableView2, false)) + perform(.dismissTopmostView(false)) + + // then + XCTAssertEqual(handler.mainQueue.count, 1) + XCTAssertEqual(handler.tempQueue.count, 0) + XCTAssertEqual(handler.mainQueue.first?.id, identifiableView2.id) + } + func testDismissShowingView() throws { // given let view = MessageView()