Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

onDisappear called immediately after hosting the view #214

Open
Karllas opened this issue Nov 29, 2022 · 2 comments
Open

onDisappear called immediately after hosting the view #214

Karllas opened this issue Nov 29, 2022 · 2 comments
Labels
more info needed The progress is halted due to insufficient information

Comments

@Karllas
Copy link
Contributor

Karllas commented Nov 29, 2022

Is it a bug or expected behaviour that onDisappear closure get's called straight after the view is hosted?

@nalexn nalexn added bug Something isn't working help wanted Extra attention is needed labels Dec 4, 2022
@nalexn
Copy link
Owner

nalexn commented Jan 6, 2023

It depends on the structure of your test. Please provide the sample code that illustrates the issue.

@nalexn nalexn added more info needed The progress is halted due to insufficient information and removed bug Something isn't working help wanted Extra attention is needed labels Jul 13, 2023
@bdbergeron
Copy link

bdbergeron commented Sep 30, 2023

I'm encountering this issue when running tests for my custom view modifier on macOS, but running them on iOS does not exhibit this behavior and my test cases run successfully.

Here's my custom view modifier:

extension View {
  public func autoUpdating(updater: AutoUpdater, updateInterval: TimeInterval) -> some View {
    modifier(AutoUpdaterViewModifier(autoUpdater: updater, updateInterval: updateInterval))
  }
}

struct AutoUpdaterViewModifier: ViewModifier {

  #if os(iOS)
  static let foregroundNotification = UIApplication.didBecomeActiveNotification
  static let backgroundNotification = UIApplication.didEnterBackgroundNotification
  #elseif os(macOS)
  static let foregroundNotification = NSApplication.didBecomeActiveNotification
  static let backgroundNotification = NSApplication.didResignActiveNotification
  #endif

  let autoUpdater: AutoUpdater
  let updateInterval: TimeInterval

  func body(content: Content) -> some View {
    content
      .onAppear {
        autoUpdater.enable(updateInterval: updateInterval)
      }
      .onDisappear {
        autoUpdater.disable()
      }
      .onReceive(NotificationCenter.default.publisher(for: Self.foregroundNotification)) { _ in
        autoUpdater.enable(updateInterval: updateInterval)
      }
      .onReceive(NotificationCenter.default.publisher(for: Self.backgroundNotification)) { _ in
        autoUpdater.disable()
      }
  }

}

The view I'm using in my tests:

private struct TestView: View {
  let autoUpdater: AutoUpdater
  let updateInterval: TimeInterval
  var didAppear: ((Self) -> Void)?

  var body: some View {
    Text("Hello, world!")
      .onAppear {
        didAppear?(self)
      }
      .autoUpdating(updater: autoUpdater, updateInterval: updateInterval)
  }
}

And an example test case for when the app is backgrounded:

  func test_viewModifier_background() throws {
    let autoUpdater = AutoUpdater()
    var view = TestView(autoUpdater: autoUpdater, updateInterval: 0.25)
    let viewExpectation = view.on(\.didAppear) { _ in }
    ViewHosting.host(view: view)
    wait(for: [viewExpectation], timeout: 1.0)

    XCTAssertEqual(autoUpdater.isEnabled, true) // triggered by modifier's onAppear

    let notification = AutoUpdaterViewModifier.backgroundNotification
    let notificationExpectation = expectation(forNotification: notification, object: nil)
    NotificationCenter.default.post(name: notification, object: nil)
    wait(for: [notificationExpectation], timeout: 1.0)

    XCTAssertEqual(autoUpdater.isEnabled, false) // triggered by modifier's onReceive
  }

When running that test targeting an iOS device, it succeeds every time, and the order in which the view modifiers inside AutoUpdaterViewModifier are called is what one would expect: onAppear, onReceive, and onDisappear. When running the test targeting my Mac, however, the test fails and the order is always onAppear, onDisappear , and then onReceive .

Stack trace shows both the onAppear and onDisappear being triggered by the call to window.layoutIfNeeded() inside ViewHosting.host(). Further, I'm seeing this log message in the Xcode console:

2023-09-29 20:05:00.946192-0400 xctest[32700:9428330] [Window] Warning: Window NSWindow 0x10381cde0 ordered front from a non-active application and may order beneath the active application's windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
more info needed The progress is halted due to insufficient information
Projects
None yet
Development

No branches or pull requests

3 participants