How can I use navigationBarTitleDisplayMode on watchOS 7 and below? - swiftui

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)

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?

Code behind #available(iOS 16.0, *) check is called on iOS 15, causing crash on symbol not found (in ViewModifier)

I made a compatibility version of .contentTransition like this:
public struct ContentTransitionNumericText: ViewModifier {
public func body(content: Content) -> some View {
if #available(iOS 16.0, watchOS 9.0, tvOS 16.0, macCatalyst 16.0, macOS 13.0, *) {
content
.contentTransition(.numericText())
} else {
content
}
}
}
public extension View {
func contentTransitionNumericText() -> some View {
modifier(ContentTransitionNumericText())
}
}
However, running this on iOS 15 (with Xcode 14 beta) causes the app to crash with Symbol not found: _$s7SwiftUI17ContentTransitionVMn error. It seems that the availability check is completely ignored.
Is there a known solution to this?
EDIT: Seems like a Xcode/Swift bug, reported as FB11143522
https://stackoverflow.com/a/70603710/38729
https://developer.apple.com/forums/thread/688678
EDIT2: The workaround from https://swiftui-lab.com/bug-os-check/ doesn't help inside ViewModifier, still crashes.
I found similar case in xcode 14's release note https://developer.apple.com/documentation/xcode-release-notes/xcode-14-release-notes
search for "Link Binary With Libraries"
Workaround: For each target using a StoreKit API listed above, navigate to the “Build Phases” tab in the project editor with the target selected and add StoreKit.framework under “Link Binary With Libraries” if it isn’t already present. Set the “Status” column to “Optional.”
and for ContentTransition just add SwiftUI.framework
I tried and worked.

Conditionally add commands to SwiftUI WindowGroup

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
}

On iOS 15, the UIHostingController is adding some weird extra padding to its hosting SwiftUI view (_UIHostingView)

UPDATE: 2022-09-26
This issue has been fixed on iOS 16. Although the issue is still present on iOS 15 even when the project is compiled with the iOS 16 SDK.
Original question:
On iOS 15, the UIHostingController is adding some weird extra padding to its hosting SwiftUI view (_UIHostingView).
See screenshot below (Blue = extra space, Red = actual view’s):
Does anyone know why this happens?
I've reported this bug, Apple folks: FB9641883
PD: I have a working project reproducing the issue which I attached to the Feedback Assistant issue. If anyone wants it I can upload it too.
I found out that subclassing UIHostingController as follows fixes the issue with extra padding:
final class HostingController<Content: View>: UIHostingController<Content> {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.setNeedsUpdateConstraints()
}
}
It also fixes a problem of the UIHostingController not resizing correctly when its SwiftUI View changes size.
I’ve tried to find why is this happening without luck. The only thing I’ve found to fix it is setting a height constraint to its intrinsic content size in a subclass of UIHostingController:
private var heightConstraint: NSLayoutConstraint?
override open func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 15.0, *) {
heightConstraint = view.heightAnchor.constraint(equalToConstant: view.intrinsicContentSize.height)
NSLayoutConstraint.activate([
heightConstraint!,
])
}
}
override open func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
heightConstraint?.constant = view.intrinsicContentSize.height
}

SwiftUI iOS 14 beta TextField 100% CPU

Using iOS 14 and Xcode 12.0 beta 6 if I try and use a simple TextField anywhere
import SwiftUI
struct ContentView: View {
#State private var name: String = "Tim"
var body: some View {
VStack {
TextField("Enter your name", text: $name)
Text("Hello, \(name)!")
}
}
}
the keyboard opens but then the CPU goes to 99%/100% and app is frozen.
Is this a known issue? How do I fix it?
This bug exists since the 14.0 betas and has not been fixed so far :/ I tried to search for workarounds or solutions but there seems to be none at the moment.
Once the user activates an input field, the CPU goes to 95%-100% and stays there until you actually quit the app.
I found some reason, If you use some .onAppear listener, When device keyboard is opened, application is being crazy if you set or change any #EnvironmentObject variable using .onAppear listener anywhere on your app. But it is not for all .onAppear... it was really weird. I searched piece by piece those when I have noticed.