Checklist
What happened?
Consider a simple SwiftUI app:
@main
struct _App: App {
var body: some Scene {
WindowGroup {
__App__
}
}
}
where __App__ is a placeholder for either plain SwiftUI App or its Hooks alternative.
SwiftUI App
struct SwiftUIApp: View {
var body: some View {
let _ = print("SwiftUI - I changed")
Text("")
}
}
Hooks App
func HooksApp() -> some View {
HookScope {
let _ = print("HookScope - I changed")
Text("")
}
}
When you run both apps from Xcode 13.4.1 on iOS 15.5, you will see the following output in the console:
SwiftUIApp() |
HooksApp() |
SwiftUI - I changed |
HookScope - I changed
HookScope - I changed
HookScope - I changed |
Considering both apps are stateless and do not invalidate its body, the console result should match.
When tracking down the issue using SwiftUI's _printChange(), it's clear that it's caused by library's HookScopeBody struct:
private struct HookScopeBody<Content: View>: View {
@Environment(\.self) private var environment
...
var body: some View {
if #available(iOS 15.0, *) {
+ let _ = Self._printChanges()
dispatcher.scoped(environment: environment, content)
}
}
Changes printed by SwiftUI are:
HookScopeBody: @self, @identity, _dispatcher, _environment changed.
HookScopeBody: _environment changed.
HookScopeBody: _environment changed.
The problem is in observing Environment(\.self) which invalidates HookScopeBody's body for each change in environment, causing a view to re-render.
I haven't dig into finding a solution yet, but wanted to keep the issue on sight as it can potentially cause unexpected re-renders of the whole app.
Notes:
- ⚠️
HookView is also affected (as it internally uses HookScope)
- ⚠️ When working on a solution, we need to keep in mind that
Context internally uses environments too.
Expected Behavior
HookScope body should only invalidate for key path specified in useEnvironment or types used in useContext.
Reproduction Steps
- Create a new project and replace @main struct with
_App struct from above
- Replace the
__App__ with SwiftUIApp() and run
- Replace
SwiftUIApp() call with HooksApp()
- Compare console outputs
Swift Version
5.6+
Library Version
<= 0.0.8
Platform
No response
Scrrenshot/Video/Gif
No response
Checklist
What happened?
Consider a simple SwiftUI app:
where
__App__is a placeholder for either plain SwiftUI App or its Hooks alternative.SwiftUI App
Hooks App
When you run both apps from Xcode 13.4.1 on iOS 15.5, you will see the following output in the console:
SwiftUIApp()HooksApp()SwiftUI - I changedHookScope - I changedHookScope - I changedHookScope - I changedConsidering both apps are stateless and do not invalidate its body, the console result should match.
When tracking down the issue using SwiftUI's
_printChange(), it's clear that it's caused by library'sHookScopeBodystruct:private struct HookScopeBody<Content: View>: View { @Environment(\.self) private var environment ... var body: some View { if #available(iOS 15.0, *) { + let _ = Self._printChanges() dispatcher.scoped(environment: environment, content) } }Changes printed by SwiftUI are:
The problem is in observing
Environment(\.self)which invalidatesHookScopeBody's body for each change in environment, causing a view to re-render.I haven't dig into finding a solution yet, but wanted to keep the issue on sight as it can potentially cause unexpected re-renders of the whole app.
Notes:
HookViewis also affected (as it internally usesHookScope)Contextinternally uses environments too.Expected Behavior
HookScopebody should only invalidate for key path specified inuseEnvironmentor types used inuseContext.Reproduction Steps
_Appstruct from above__App__withSwiftUIApp()and runSwiftUIApp()call withHooksApp()Swift Version
5.6+
Library Version
<= 0.0.8
Platform
No response
Scrrenshot/Video/Gif
No response