Conditionally add commands to SwiftUI WindowGroup - swiftui

With macOS 12 Apple fixed the omission of „Continuity Camera“ in SwiftUI by introducing the command group ImportFromDevicesCommands which can simply be added to a window.
But when you try to use it in an app supporting macOS 11 and 12, you are missing the conditionality support as it is common when combining SwiftUI views.
I have tried the following:
import SwiftUI
#main
struct ThrowAwayApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
if #available(macOS 12, *) {
ImportFromDevicesCommands()
}
}
}
}
But the compiler brings up the following error message:
Closure containing control flow statement cannot be used with result builder 'CommandsBuilder'
I understand the CommandsBuilder does not support buildIf(), buildEither(first:) and buildEither(second:) and is therefore missing the if-block support as for SwiftUI views.
Is there a way to conditionally add commands to the WindowGroup?
Can I do some trickery with #available? (I'm really missing the #unavailable...)

I've come up with a solution which allows me to check whether code is compiled for macOS 11 or macOS 12 by defining derived User-Defined build configuration in Xcodes project settings:
TARGET_MAJOR = $(SUPPORTED_PLATFORMS:upper)$(MACOSX_DEPLOYMENT_TARGET:base)
And then referring to this variable in the Other Swift Flags
OTHER_SWIFT_FLAGS = -DTARGET_$(TARGET_MAJOR)
Afterwards I can conditionally compile code for macOS 12 by using:
WindowGroup {
ContentView()
}
.commands {
#if TARGET_MACOSX12
ImportFromDevicesCommands()
#elseif TARGET_MACOSX11
#warning("ImportFromDevicesCommands not implemented")
#endif
}

Related

SwiftUI backwards compatibility with WatchOS 6: how to write body?

If I create a brand new xCode project with a WatchKit extension this is what I get created by default in the main app file:
struct WorkoutApp: App {
#available(watchOSApplicationExtension 7.0, *)
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
}
WKNotificationScene(controller: NotificationController.self, category: "myCategory")
}
}
However, many types such as Scene, WindowGroup and the #Main modifier are not available in WatchOS 6. I didn't find any example on how to create a watch kit extension with WatchOS 6 in SwiftUI. I either find examples that use the old way (with storyboards, without SwiftUI), or examples that use this construct or examples with storyboards.
How do I rearrange the project to compile and run for WatchOS 6?

How can I use navigationBarTitleDisplayMode on watchOS 7 and below?

When I try to use navigationBarTitleDisplayMode in my project targeting watchOS 6, I get this error:
'navigationBarTitleDisplayMode' is only available in application extensions for watchOS 8.0 or newer
How can I use it in earlier versions of watchOS? I know it won't have an effect there, because the style doesn't exist, I just want to circumvent the error.
You can do it with a ViewBuilder extension:
extension View {
#ViewBuilder
func navBarTitleDisplayMode(_ mode: NavigationBarItem.TitleDisplayMode) -> some View {
if #available(watchOSApplicationExtension 8.0, *) {
self
.navigationBarTitleDisplayMode(mode)
} else {
self
}
}
}
Usage:
someView
.navigationBarTitle("WatchFunk") // Using this for watchOS 6 compatibility.
// Use navigationTitle when targeting
// watchOS 7 and above.
.navBarTitleDisplayMode(.inline)

iOS 14 AssistiveTouch Dwell Control sometimes not working on SwiftUI Buttons

I have a SwiftUI App which uses regular Button views and our customers use AssistiveTouch's DwellControl feature quite often. On iOS 13 DwellControl was working fine but on iOS 14 beta 8 DwellControl fails to be able click on those buttons.
The Buttons can be tapped by finger and clicked with the normal mouse but not with the DwellControl feature.
I have tried adding accessibility traits like isButton, but nothing works.
Has anyone encountered the same problem and has a solution or any insight at all? I know this is quite niche but any help is appreciated!
EDIT:
I have tested it with a very simple example, including one a Button and it works fine. It seems to be a side effect of some sorts in my specific App.
I have several ZStacks which show and hide some views like modals and popovers. Could this be the source of failure?
What I don't get is that the Buttons can be tapped and clicked... If a view was blocking the Button then this shouldn't be possible right?
I have tried Release and Debug builds which does not make a difference.
When DwellControl is activated and the cursor is on the Button that shall be clicked, tapping doesn't work either. But when the cursor is nowhere near the Button, tapping works fine.
I have send a report via Feedback to Apple.
EDIT:
I found the cause. ScrollView somehow prevents all DwellControl clicks from happening. That happens whenever a ScrollView is somewhere present in one of the Views.
Minimal example:
struct ContentView: View {
#State var show = false
var body: some View {
ZStack {
ScrollView {
Button(action: {
print("This should be printed but isn't....")
}, label: {
Text("Button in ScrollView")
})
}
}
}
}
I know its niche but maybe someone needs this.
There are other components which are affected and cause the same behaviour:
ScrollView
TextField
Slider
Stepper
UIViewRepresentable
For me this is a big issue, since our customers use DwellControl quite often.
EDIT:
This can be replicated in the newest iOS 14.2 beta 1:
import SwiftUI
#main
struct TempSwitUIApp: App {
#State private var text: String = "I am a text"
var body: some Scene {
WindowGroup {
Button(action: {
print("\(UUID()) \(self.text)")
}, label: {
Text("Print text to the console")
})
TextField("Hello", text: self.$text)
}
}
}
I updated my issue with Apple but haven't heard from them yet.

What is the equivalent of WKInterfaceController.didAppear() in SwiftUI on watchOS?

I'm building an Apple Watch app, and there is code I want to run every time the app is brought to the foreground.
Previously, if I wanted to do this in a watchOS with a WKInterfaceController, I would put this code in didAppear().
In SwiftUI, there is onAppear(), but when I call that on watchOS it only seems to be called the first time the app loads up, so it behaves like WKInterfaceController.willActivate() instead. The app has just a single view.
If onAppear() is the equivalent of WKInterfaceController.willActivate(), is there a different SwiftUI function that is the equivalent of WKInterfaceController.didAppear()?
Here's my current example code:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello World").font(.footnote)
.onAppear {
print("onAppear called")
}
}
}
In the meantime, I am going to experiment with triggering what I need to do within the ExtensionDelegate, but I'm just trying to learn my way around SwiftUI on WatchOS, so knowing the answer to this would be helpful in the future.

SwiftUI Canvas is blank or has crashed?

My SwiftUI Canvas has disappeared. Has it crashed? I don't see any output in the console.
I have macOS 10.15 Catalina (the requirement for SwiftUI) and Xcode 11+.
Solution 1
If your SwiftUI view were named "TestView," the following would be necessary to display the Canvas:
#if DEBUG
struct TestView_Previews : PreviewProvider {
static var previews: some View {
TestView()
}
}
#endif
Simply copy the above code and change the instances of "TestView" to whatever your file is called. Click "Resume" and the canvas should load up.
Solution 2
If the above did not work, try to clear the derived data from the machine by first closing Xcode, then navigating in Finder to
~/Library/Developer/Xcode/DerivedData
Drag all of the files to the trash and restart Xcode. Don't worry, none of your code will be deleted, this is just the data that Xcode creates to help it to compile your project faster.