Skip to content

Commit

Permalink
Dismiss presented controller when parent controller goes away (#192)
Browse files Browse the repository at this point in the history
* Dismiss presented controller when parent controller goes away

This brings parity with SwiftUI, where if a view disappears, an attached
sheet disappears shortly thereafter.

* wip

* wip
  • Loading branch information
stephencelis authored Aug 6, 2024
1 parent 6665ebd commit 4d275f0
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 25 deletions.
67 changes: 42 additions & 25 deletions Examples/CaseStudiesTests/PresentationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ final class PresentationTests: XCTestCase {

@MainActor
func testPushFireAndForget_PushStateDriven() async throws {
let nav = UINavigationController(rootViewController: UIViewController())
let nav = UINavigationController(rootViewController: ViewController())
try await setUp(controller: nav)
await assertEventuallyEqual(nav.viewControllers.count, 1)

Expand All @@ -321,7 +321,7 @@ final class PresentationTests: XCTestCase {

@MainActor
func testDismissMultiplePresentations() async throws {
class VC: UIViewController {
class VC: ViewController {
@UIBinding var isPresented = false
override func viewDidLoad() {
super.viewDidLoad()
Expand All @@ -344,17 +344,8 @@ final class PresentationTests: XCTestCase {

@MainActor
func testDismissLeafPresentationDirectly() async throws {
class VC: UIViewController {
class VC: ViewController {
@UIBinding var isPresented = false
override func loadView() {
super.loadView()
view.backgroundColor = .init(
red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1),
alpha: 1
)
}
override func viewDidLoad() {
super.viewDidLoad()
present(isPresented: $isPresented) { VC() }
Expand All @@ -376,17 +367,8 @@ final class PresentationTests: XCTestCase {
}

@MainActor func testDismissMiddlePresentation() async throws {
class VC: UIViewController {
class VC: ViewController {
@UIBinding var isPresented = false
override func loadView() {
super.loadView()
view.backgroundColor = .init(
red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1),
alpha: 1
)
}
override func viewDidLoad() {
super.viewDidLoad()
present(isPresented: $isPresented) { VC() }
Expand Down Expand Up @@ -414,8 +396,31 @@ final class PresentationTests: XCTestCase {
await assertEventuallyNil(vc.presentedViewController?.presentedViewController, timeout: 2)
}

@MainActor func testDoublePresentation_ChangeRootBinding() async throws {
@MainActor func testNestedPresentationParentDismissalDismissesChild() async throws {
let nav = UINavigationController(rootViewController: ViewController())
try await setUp(controller: nav)
await assertEventuallyEqual(nav.viewControllers.count, 1)

var child: BasicViewController? = BasicViewController(model: Model())
nav.pushViewController(child!, animated: false)
await assertEventuallyEqual(nav.viewControllers.count, 2)

try await Task.sleep(for: .seconds(0.5))
withUITransaction(\.uiKit.disablesAnimations, true) {
child!.model.isPresented = true
}
try await Task.sleep(for: .seconds(0.5))

await assertEventuallyNotNil(child!.presentedViewController)
XCTAssertEqual(child!.presentedViewController, nav.presentedViewController)
nav.popToRootViewController(animated: false)
try await Task.sleep(for: .seconds(0.5))

child = nil
try await Task.sleep(for: .seconds(0.5))

await assertEventuallyEqual(nav.viewControllers.count, 1)
await assertEventuallyNil(nav.presentedViewController)
}
}

Expand All @@ -441,6 +446,18 @@ private final class Model: Identifiable {
}
}

private class ViewController: UIViewController {
override func viewDidLoad() {
view.backgroundColor = .init(
red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1),
alpha: 1
)
super.viewDidLoad()
}
}

private class BasicViewController: UIViewController {
@UIBindable var model: Model
var isPresenting = false
Expand All @@ -463,7 +480,7 @@ private class BasicViewController: UIViewController {
self?.isPresenting = false
} content: { [weak self] in
self?.isPresenting = true
return UIViewController()
return ViewController()
}
present(item: $model.presentedChild) { [weak self] in
self?.isPresenting = false
Expand All @@ -472,7 +489,7 @@ private class BasicViewController: UIViewController {
return BasicViewController(model: model)
}
navigationDestination(isPresented: $model.isPushed) {
UIViewController()
ViewController()
}
navigationDestination(item: $model.pushedChild) { model in
BasicViewController(model: model)
Expand Down
9 changes: 9 additions & 0 deletions Sources/UIKitNavigation/Navigation/Presentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,18 @@
}
}

@MainActor
private class Presented {
weak var controller: UIViewController?
let presentationID: AnyHashable?
deinit {
// NB: This can only be assumed because it is held in a UIViewController and is guaranteed to
// deinit alongside it on the main thread. If we use this other places we should force it
// to be a UIViewController as well, to ensure this functionality.
MainActor.assumeIsolated {
self.controller?.dismiss(animated: false)
}
}
init(_ controller: UIViewController, id presentationID: AnyHashable? = nil) {
self.controller = controller
self.presentationID = presentationID
Expand Down

0 comments on commit 4d275f0

Please sign in to comment.