Xcode 12 SwiftUI Preview not highlighting Views embedded in NavigationView - xcode

Using the SwiftUI Xcode preview pane, I can click on the rendered views and see the code responsible for that view highlighted in the code pane (and vice versa). However, when I embed the view in a NavigationView or sometimes even addding a .navigationBarTitle() to the root view, I can't do that anymore. All views in the Navigation group just becomes one big view and the previewer can't identify the separate underlying pieces anymore.
Is this a bug? Is there a way to get around it? It's a really useful feature and most of my views sit in some kind of Navigation object.
Working
import SwiftUI
struct NavSample: View {
var body: some View {
Text("Hello, World!")
}
}
struct NavSample_Previews: PreviewProvider {
static var previews: some View {
NavSample()
}
}
Not Working
import SwiftUI
struct NavSample: View {
var body: some View {
NavigationView { // Adding this
Text("Hello, World!")
}
}
}
struct NavSample_Previews: PreviewProvider {
static var previews: some View {
NavSample()
}
}

Related

FileImporter missing Select option

This is driving me crazy for many days now. I am trying to use the .fileImport() modifier in SwiftUI and apparently I am missing something VERY obvious but for the life of me I cannot find a way to have the "Select" option (see screenshots.
This is like the simplest it can get:
import SwiftUI
import UniformTypeIdentifiers
struct DocImporterView: View {
// #Binding var document: ProofOfBugDocument
#State var isPicking: Bool = false
var body: some View {
Button("Pick") {
isPicking.toggle()
}
.fileImporter(
isPresented: $isPicking,
allowedContentTypes: [.item, .folder, .directory],
allowsMultipleSelection: true, //this btw does not enable the select. Instead just starts the Document Picker in selection mode (but folders cannot be selected)
onCompletion: { result in
print("Picked: \(result)")
})
}
}
struct DocImporterView_Previews: PreviewProvider {
static var previews: some View {
DocImporterView()//(document: .constant(ProofOfBugDocument()))
}
}
Any ideas/help appreciated

macOS & SwiftUI 2: simplest way to turn off beep on keystroke

The following trivial macOS app is written in SwiftUI 2.0.
import SwiftUI
#main
struct TempApp: App {
var body: some Scene {
WindowGroup { ContentView() }
}
}
struct ContentView: View {
var body: some View {
Text("Hello, beep!").padding()
}
}
When in the foreground, this app will emit an error beep on certain keystrokes (like "a"). What's the simplest way to suppress this beep?
An Xcode project illustrating this (and the answer) can be found here.
There are many older related questions on SO, but none of these are specifically about doing this in SwiftUI 2.0.
You can suppress the beep by adding a local monitor for the .keyDown event at the top level. This can be done simply in ContentView.init(), like so:
struct ContentView: View {
var body: some View {
Text("Hello, silence!").padding()
}
init() {
NSEvent.addLocalMonitorForEvents(matching: .keyDown) { _ in return nil }
}
}
This technique was inspired by this answer.

Designing a view with an inspector

I'm building a new version of an app in SwiftUI and have run into a conundrum.
The scenario is that I have a canvas with multiple objects on it, and I would like to have an inspector view that shows details of the object currently under the pointer. The problem is that my initial implementation makes the whole canvas redraw far too many times.
What I have is a #State variable which the inspector displays, so the inspector view needs to see this state. The object view needs to be able to write to the state variable, so that means that when the pointer comes over an object, it changes the state variable, which then means that both the object (really, the canvas) and the inspector get marked as needing to be redrawn.
What would be nice would be for the object view to be able to write to the state that the inspector needs without invalidating its own view, but I can't work out how to do this in SwiftUI. In the old paradigm, I'd send a message to the inspector, but that doesn't seem to be the way in the declarative paradigm of SwiftUI. I suspect that some sort of observable object may be a solution, but I haven't worked out how to do that.
Related is the behaviour of .onHover, which I am using to generate the mouse entered/mouse exited events like so:
.onHover { entered in
inspectorText = entered ? self.descriptionText : ""
}
I seem to get multiple enter/exit events, presumably due to the redrawing, which slows things to a crawl. Is there a better method than using .onHover?
I'm pretty sure that there is a clever way (maybe with Equatable views...?) for preventing re-evaluation of the main body, but for what is worth here is a possible solution using a singleton:
import SwiftUI
class HoverState: ObservableObject {
static let shared: HoverState = HoverState()
#Published var currentItemText: String = ""
}
struct InspectorView: View {
#ObservedObject var hoverState: HoverState = HoverState.shared
var body: some View {
Text(hoverState.currentItemText)
}
}
struct ContentView: View {
var body: some View {
HStack {
VStack {
Color.red
.onHover { _ in HoverState.shared.currentItemText = "Red" }
Color.green
.onHover { _ in HoverState.shared.currentItemText = "Green" }
Color.blue
.onHover { _ in HoverState.shared.currentItemText = "Blue" }
}
.padding()
InspectorView()
.frame(width: 100)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This works because only the inspector view declares a dependency to the hoverState, which also means of course that if you try to reflect a value (as opposed to just setting the value) in the ContentView will not be updated...

EnvironmentObject refresh/hand over problems using NavigationLink

I am currently developing an app for watchOS 6 (independent app) using Swift/SwiftUI in XCode 11.5 on macOS Catalina.
Before a user can use my app, a configuration process is required. As the configuration process consists of several different views which are shown one after each other, I implemented this by using navigation links.
After the configuration process has been finished, the user should click on a button to return to the "main" app (main view). For controlling views which are on the same hierarchical level, my plan was to use an EnvironmentObject (as far as I understood, an EnvironmentObject once injected is handed over to the subviews and subviews can use the EnvironmentObject) in combination with a "controlling view" which controls the display of the views. Therefore, I followed the tutorial: https://blckbirds.com/post/how-to-navigate-between-views-in-swiftui-by-using-an-environmentobject/
This is my code:
ContentView.swift
struct ContentView: View {
var body: some View {
ContentViewManager().environmentObject(AppStateControl())
}
}
struct ContentViewManager: View {
#EnvironmentObject var appStateControl: AppStateControl
var body: some View {
VStack {
if(appStateControl.callView == "AppConfig") {
AppConfig()
}
if(appStateControl.callView == "AppMain") {
AppMain()
}
}
}
}
AppStateControl.swift
class AppStateControl: ObservableObject {
#Published var callView: String = "AppConfig"
}
AppConfig.swift
struct AppConfig: View {
#EnvironmentObject var appStateControl: AppStateControl
var body: some View {
VStack {
Text("App Config Main")
NavigationLink(destination: DetailView1().environmentObject(appStateControl)) {
Text("Show Detail View 1")
}
}
}
}
struct DetailView1: View {
#EnvironmentObject var appStateControl: AppStateControl
var body: some View {
VStack {
Text("App Config Detail View 1")
NavigationLink(destination: DetailView2().environmentObject(appStateControl)) {
Text("Show Detail View 2")
}
}
}
}
struct DetailView2: View {
#EnvironmentObject var appStateControl: AppStateControl
var body: some View {
VStack {
Text("App Config Detail View 2")
Button(action: {
self.appStateControl.callView = "AppMain"
}) {
Text("Go to main App")
}
}
}
}
AppMain.swift
struct AppMain: View {
var body: some View {
Text("Main App")
}
}
In a previous version of my code (without the handing over of the EnvironmentObject all the time) I got a runtime error ("Thread 1: Fatal error: No ObservableObject of type AppStateControl found. A View.environmentObject(_:) for AppStateControl may be missing as an ancestor of this view.") caused by line 41 in AppConfig.swift. In the internet, I read that this is probably a bug of NavigationLink (see: https://www.hackingwithswift.com/forums/swiftui/environment-object-not-being-inherited-by-child-sometimes-and-app-crashes/269, https://twitter.com/twostraws/status/1146315336578469888). Thus, the recommendation was to explicitly pass the EnvironmentObject to the destination of the NavigationLink (above implementation). Unfortunately, this also does not work and instead a click on the button "Go to main App" in "DetailView2" leads to the view "DetailView1" instead of "AppMain".
Any ideas how to solve this problem? To me, it seems that a change of the EnvironmentObject in a view called via a navigation link does not refresh the views (correctly).
Thanks in advance.
One of the solutions is to create a variable controlling whether to display a navigation stack.
class AppStateControl: ObservableObject {
...
#Published var isDetailActive = false // <- add this
}
Then you can use this variable to control the first NavigationLink by setting isActive parameter. Also you need to add .isDetailLink(false) to all subsequent links.
First link in stack:
NavigationLink(destination: DetailView1().environmentObject(appStateControl), isActive: self.$appStateControl.isDetailActive) {
Text("Show Detail View 1")
}
.isDetailLink(false)
All other links:
NavigationLink(destination: DetailView2().environmentObject(appStateControl)) {
Text("Show Detail View 2")
}
.isDetailLink(false)
Then just set isDetailActive to false to pop all your NavigationLinks and return to the main view:
Button(action: {
self.appStateControl.callView = "AppMain"
self.appStateControl.isDetailActive = false // <- add this
}) {
Text("Go to main App")
}

Can't do a Simple Navigate to View and back SwiftUI Navigation Bar Button

I'm trying to do a simple SwiftUI navigation from one view to another and back using
a bar button item. I have tried three different approaches to calling a new view.
Using a Button in the body view works, but using NavigationBarItems in the navigation
bar fails in two different ways.
Here's the start view:
struct ContentView: View {
#State private var showSecondView = false
var body: some View {
NavigationView {
VStack {
Text("This is the content view")
.navigationBarTitle("Nav Title")
//this works ONCE only:
.navigationBarItems(trailing: Button(action: {self.showSecondView.toggle()}) {
Text("SecondView")
})
//this always fails on return to contentview with error:
//Tried to pop to a view controller that doesn't exist
// .navigationBarItems(trailing:
// NavigationLink(destination: SecondView()) {
// Text("SecondNav")
// }
// )
//This always works:
Button(action: {self.showSecondView.toggle()} ) {
Text("Call Modal Second View")
}.padding()
Text(self.showSecondView ? "true" : "false")
}.sheet(isPresented: $showSecondView) {
SecondView()
}
}
}
}
If I use a NavigationLink in the NavigationBarItems, the SecondView is displayed, but
on return to the ContentView, it crashes with the error: "Tried to pop to a view
controller that doesn't exist"
If I use a Button in the NavigationBarItems, the transition to the SecondView works
once and only once. The return to ContentView works but the button no longer functions.
Interestingly, If the first action taken is with the Button in the Body, the
NavigationBarItem does not work even once.
And the simple SecondView:
struct SecondView: View {
#Environment(\.presentationMode) var presentation
var body: some View {
NavigationView {
VStack{
Text("This is the second view")
Button(action: { self.presentation.wrappedValue.dismiss()}) {
Text("Dismiss Modal")
}.padding()
}
}
}
}
I'm confused. Any guidance would be appreciated. Xcode 11.2 (11B44), Catalina 10.15
This is still an issue for me, I am having the same issue with popover (Modal) presentation and pushing Second controller via NavigationLink in navigationBarItems.
This is a really serious bug. The only way it works correctly is when the NavigationLink is inside NavigationView content and not navigationBarItems.
This is a really breaking as NavigationBar buttons are suppose to work that way.
I came across this issue today when I updated my Xcode to 11.2. According to this post it seems to be a bug with 13.2. I tested it on my actual iPhone X, which is still running 13.1.2, and it works just fine there.

Resources