Post

Replies

Boosts

Views

Activity

@EnvironmentObject inside NavigationSplitView not available
As a developer, I have been using SwiftUI to build applications, and I have come across a specific scenario that seems to be causing some unexpected behaviour. When utilizing the @EnvironmentObject property wrapper to pass an object through the view hierarchy, I noticed that it becomes inaccessible after pushing and presenting the second view inside a NavigationStack. (which is inside a NavigationSplitView). To illustrate the problem, consider the following scenario: I have a Router class that conforms to ObservableObject, and it holds a NavigationPath to manage navigation within the app. The SettingsView is a SwiftUI view that uses NavigationStack to display different views based on user interactions. Inside this view, I use @EnvironmentObject to pass the Router object. Within the SettingsView, I have a button that, when clicked, programmatically pushes a new destination (AdvancedView) to the Router object using router.push(.advanced). In the AdvancedView I have a Button and a NavigationLink which both should lead to LogsView. When tapping on the Button (which programmatically pushes a new destination to the NavigationPath) the app crashes with the error that the @EnvironmentObject is not acccessbile. When tapping on the NavigationLink (which SwiftUI handles by itself) the app does NOT crash and can successfully push the value (.logs) to the NavigationPath (meaning it indeed has access to the @EnvironmentObject). The issue arises when I use the manually created button to navigate to the LogsView. In this scenario, the Router object, which was previously accessible through the @EnvironmentObject, becomes inaccessible on the AdvancedView. However, when I use the NavigationLink, the Router object is still accessible, and everything works as expected. To summarize: When using NavigationLink, the @EnvironmentObject (Router in this case) is accessible on the destination view (LogsView). When manually navigating using a button, the @EnvironmentObject (Router) is not accessible on the destination view (LogsView) after pushing and presenting the second view. // // ContentView.swift // ViewTester // // Created by Luca Archidiacono on 27.03.23. // import SwiftUI struct ListItem: Identifiable { let id = UUID() let title: String let description: String } enum MenuItem: Identifiable { case settings var id: Self { return self } var title: String { switch self { case .settings: return "settings" } } } struct ContentView: View { let menuItems: [MenuItem] = [.settings] @State var selected: MenuItem? = .settings var body: some View { NavigationSplitView { List(menuItems, selection: $selected) { selected in Text(selected.title) } .navigationTitle("Menu") } detail: { if let selected = selected { switch selected { case .settings: SettingsView() } } } } } enum Destination: Hashable { case advanced case logs } final class Router: ObservableObject { @Published var path = NavigationPath() func push(_ destination: Destination) { path.append(destination) } } struct SettingsView: View { @StateObject private var router = Router() var body: some View { NavigationStack(path: $router.path) { ZStack { Button("Advanced") { router.push(.advanced) } } .navigationDestination(for: Destination.self) { destination in switch destination { case .advanced: AdvancedView() case .logs: LogsView() } } } .environmentObject(router) } } struct AdvancedView: View { @EnvironmentObject private var router: Router var body: some View { // 1. This does not work somehow eventhough we passed router as EnvironmentObject on the NavigationStack. Button("Manual Logs") { router.push(.logs) } // 2. This somehow works, and it even pushes to the NavigationPath, which means SwiftUI should know about Router as EnvironmentObject NavigationLink(value: Destination.logs) { Text("NavigationLink Logs") } } } struct LogsView: View { var body: some View { Text("Logs") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
0
3
676
Jul ’23
UIImage.init(named:in:compatibleWith:) different behavior to Image.init(_:bundle:)
I recently observed a distinction in the lookup behavior for an Asset within a specific Bundle when using UIImage.init(named:in:compatibleWith:) compared to Image.init(_:bundle:). Consider a scenario where we have an asset named camera, and there is also an SF symbol with the name camera.fill. We also have an Asset in our Swift Package and use its Bundle. In that case .module. Now, when we use UIImage.init(named:in:compatibleWith:) and inject camera.fill and .module, the result of this initialization is not nil. It appears that UIImage has located something within our Asset. On the other hand, when we inject camera.fill into Image.init(_:bundle:) using the same bundle, the result of this initialization is an empty Image, accompanied by a log entry stating: "No image named 'camera.fill' found in the asset catalog for /Users/.../.bundle." This behaviour aligns with expectations, as we don't have an explicitly named camera.fill within our Assets inside the bundle .module. It appears that even though we injected camera.fill into UIImage.init(named:in:compatibleWith:), it found the first best match, in this case, the camera Asset. Meanwhile, Image.init(_:bundle:) found nothing explicitly named camera.fill within our Assets. Doesn't it seem logical for both of these APIs to yield the same result? Perhaps, it would be even better if both APIs looked up the given name consistently, taking into account the provided bundle in the same way.
0
0
360
Dec ’23