Upon further review, reintroducing the value into the environment seems to work for some parts of my view hierarchy but not others. However, passing by a parameter works for all my views (thus far).
There's some pretty weird stuff I see when I try to figure out what's happening with the environment. I assume the problem is that I don't fully understand how the Environment works and maybe side-effects that certain code has on Environment values...
As an example of some of the weirdness I see... here's a simplified version of my code (target is Apple Watch):
import SwiftUI
struct ContentView: View {
@Environment(\.scenePhase) var scenePhase
var body: some View {
Child(paramScenePhase: scenePhase)
}
}
struct Child: View {
var paramScenePhase: ScenePhase
var body: some View {
ChildButton(paramScenePhase: paramScenePhase)
}
}
struct ChildButton: View {
var paramScenePhase: ScenePhase
@State private var isPresentingProblemView: Bool = false
var body: some View {
Button("Press me") {
isPresentingProblemView = true
}
.sheet(isPresented: $isPresentingProblemView) {
ProblemView(paramScenePhase: paramScenePhase)
}
}
}
struct ProblemView: View {
@Environment(\.scenePhase) var enviroScenePhase
var paramScenePhase: ScenePhase
var body: some View {
Text("Hi")
.onChange(of: enviroScenePhase) { phase in
print("enviroScenePhase " + scenePhaseString(phase))
}
.onChange(of: paramScenePhase) { phase in
print("paramScenePhase " + scenePhaseString(phase))
}
}
}
func scenePhaseString(_ phase: ScenePhase) -> String {
switch(phase) {
case .active:
return "phase: ACTIVE"
case .inactive:
return "phase: INACTIVE"
case .background:
return "phase: BACKGROUND"
default:
return "phase: UNKNOWN"
}
}
When I run this simplified code in the simulator, press the "Press me" button, and then press the "always on" button in the watch simulator twice (to go inactive and then active again), I get the following in the console (and this is the expected behavior):
enviroScenePhase phase: INACTIVE
paramScenePhase phase: INACTIVE
enviroScenePhase phase: ACTIVE
paramScenePhase phase: ACTIVE
I then commented out var paramScenePhase: ScenePhase within ProblemView (and then comment out only the other parts of the code to get rid of compilation errors). Running the same experiment, this is what I get in the console:
enviroScenePhase phase: INACTIVE
This is not the expected behavior. And that's all I see no matter how many times I press the always-on button to toggle between inactive and active phases.
If I then comment out var paramScenePhase: ScenePhase in the ChildButton view (and change the ChildButton instantiation to ChildButton()), then nothing prints to the console at all no matter how many times I press the always-on simulator button.
I just don't understand how changing what parameters are used in a view also changes the behavior of an environment variable.
Post
Replies
Boosts
Views
Activity
I have found a solution.
Apparently using @Environment(\.scenePhase) var scenePhase in the modal sheet's view does not work by itself. The scenePhase inside the sheet's view just seems to stay equal to .active even if the app is closed (by, for example, covering the watch with your hand).
However, I found a solution at https://stackoverflow.com/questions/72620039/trigger-an-action-when-scenephase-changes-in-a-sheet
If I "send" the scenePhase value that the root view pulls from the environment explicitly to the sheet view or one of its ancestors using the modifier .environment(\.scenePhase, scenePhase), then the @Environment(\.scenePhase) var scenePhase in the sheet view gets the actual current phase and can monitor its changes. It also works to send it as a parameter to the sheet's view. However, since I want to access it in multiple sheets opened from yet another sheet, putting it back into the environment for the full child view hierarchy seemed more appropriate.
Ah - I see my root view indeed gets a scenePhase of inactive and then background when I cover the watch when the root view is showing. Checking scenePhase changes in the non-root views appears to not do anything at all (my onChange(of: scenePhase) code in non-root views seems to be never executed).
So I'm still not sure (yet) how to detect changes in scene phase while a sheet is being shown. So a slightly different problem than I thought I had. Time for more research...
Okay - I solved my problem, which is that I THOUGHT I was following instructions correctly, but I wasn't. Confirmation bias hits again!
It's embarrassing, but I will provide this information to hopefully help anyone else making the same mistake and spare them the embarrassment of posting the same problem.
WeatherKit has to be "enabled" for each app target in TWO PLACES.
WeatherKit needs to be added as a CAPABILITY for the app target. It appears that this can be done either in the Certificates, Identifiers & Profiles developer web page or within Xcode. But you have to go to the web page anyway because...
The WeatherKit SERVICE needs to be enabled for the app target. It appears this can ONLY be done in the Certificates, Identifiers & Profiles developer web page. Click on the app target in question. There are TWO tabs - "Capabilities" and "App Services". Click App Services and check WeatherKit.
My problem was that once I had WeatherKit checked in Capabilities, I thought I was done, so I didn't even notice that "App Services" was a tab or think that I would need to look in there (despite the instructions on setting up WeatherKit in the documentation). Glazed right over that part.
Hopefully as I learn more about iOS development I'll learn why someone might actually want the capability without the service, since WeatherKit is the only thing that appears on both tabs......... ;-P
Just an addendum - I thought perhaps I hadn't waited long enough, but I still get the same type of error the next day. I had added weather kit capability via +Capability in Xcode. I checked Certificates, Identifiers & Profiles in developer.apple.com, and it shows a checkmark next to WeatherKit for both the original iOS app as well as the new watch target.
I also created a completely new project containing a new watch app with associated iOS app and again added the WeatherKit capability for both targets in Xcode, and get the same type of errors. I am now also getting some errors that say "Info.plist contained no UIScene configuration dictionary (looking for configuration named "(no name)")", but the app "runs" and I can get location, etc... just not weather.
Just in case the problem is that adding the WeatherKit capability in Xcode isn't "good enough", I deleted the capability in the new project in Xcode, deleted the AppID in Certificates, Identifiers & Profiles, then recreated the AppID in Certificates, Identifiers & Profiles with the bundle ID already shown in Xcode, and with WeatherKit checked during AppID creation. This resulted in the exact same errors.
I have exactly the same problem. I have an iOS app that uses WeatherKit just fine. Added a Watch app for existing iOS app, added WeatherKit capability for the watch app, get this error. Okay, not exactly - mine says that the error is Error Domain=WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors Code=2 "(null)"
After a while and quitting/restarting Xcode while my phone was connected, the message changed to say developer mode was disabled on my watch. Then the developer mode setting magically appeared and I could enable it. Then the Xcode message changed back to waiting for first unlock. Then I had to trust the computer (again - I already did this on the new watch while trying to debug this problem). It still wasn't working until I quit and restarted Xcode yet one more time. Then it finally worked.
Developer setting is missing for me on an Apple Watch 8 with watchOS 9.1. It had been there on my old Apple Watch 4 running a previous OS (and I had successfully used it).
The developer mode button is missing for me too. There's nothing below App Privacy Report on my new Apple Watch.
I had Xcode playing nice with my older Apple Watch, and was able to load apps that I wrote onto it. Since I got a new watch, I can no longer test apps on my watch. It is listed as "waiting for first unlock" in the simulator list despite it and my phone both being unlocked... I thought the solution might be that I needed to turn on developer mode--that's when I discovered the option is missing).