Which common view modifiers are platform specific (navigationBarTitleDisplayMode, onHover, hoverEffect, focusSection, the tvOS remote commands, digitalCrownRotation), and how do you write a view that compiles everywhere?

20.9.2 · L3 · Interview

Predict first

Does navigationBarTitleDisplayMode compile for macOS?

import SwiftUI
struct V: View {
    var body: some View { Text("a").navigationBarTitleDisplayMode(.inline) }
}
  • Yes
  • No, unavailable in macOS

Predict first

Do the remote command modifiers compile for tvOS?

import SwiftUI
struct V: View {
    var body: some View { Text("a").onMoveCommand { _ in }.onPlayPauseCommand {}.onExitCommand {} }
}
  • Yes
  • No

Predict first

And for iOS?

import SwiftUI
struct V: View {
    var body: some View { Text("a").onMoveCommand { _ in } }
}
  • Yes
  • No, unavailable in iOS

Predict first

Does digitalCrownRotation compile for watchOS?

import SwiftUI
struct V: View {
    var body: some View { Text("a").digitalCrownRotation(.constant(0.0)) }
}
  • Yes
  • No

Mental model. A universal remote with every button printed on it: the platform decides which buttons are wired up, and pressing an unwired one is a compile error, not silence.

Type-checking one view per modifier, the results are: navigationBarTitleDisplayMode compiles on iOS, watchOS and visionOS and is unavailable on tvOS and macOS. onHover is available on iOS (iPad pointer), macOS and visionOS and unavailable on tvOS and watchOS. hoverEffect is on iOS, tvOS and visionOS and unavailable on the watch and Mac. focusSection() exists on tvOS and macOS only. The remote commands onMoveCommand, onPlayPauseCommand and onExitCommand (Q420, Q421) are tvOS only (plus onMoveCommand on macOS). digitalCrownRotation is watchOS only, and glassBackgroundEffect() and ornament are visionOS only. @Environment(\\.horizontalSizeClass) compiles on every platform, so size-class code does not need a guard, though it is not meaningful everywhere.

The way to keep shared views clean is a small ViewModifier or extension per capability that is a no-op where the capability does not exist: func pointerHover(_ f: @escaping (Bool) -> Void) -> some View with the #if inside, so call sites stay free of conditionals; avoid #if inside a view’s body chain for more than one modifier, since the result type changes with the branch. A modifier that is unavailable is a compile error, not a no-op, so CI that builds every destination is the net. Deployment-target gaps appear as errors too (“only available in visionOS 26.0 or newer”), so the matrix has a version axis. Not run: the behaviour of these modifiers on a device (hover on an iPad pointer, the Crown), only that they compile.

It is an iOS, watchOS and visionOS modifier.

They are the tvOS remote hooks.

They are not iOS APIs.

The Crown is the watch’s input.

Common traps

  • Chaining a platform-only modifier in the middle of a shared body. Wrap it in a modifier that hides the #if.
  • Using navigationBarTitleDisplayMode in shared code that builds for macOS or tvOS. It is unavailable there.

They might follow up with

  • What replaces onHover on tvOS? Focus: @FocusState and the focus effects (Q420).
  • Is there a way to list a view’s unavailable modifiers? Build the destination; the compiler names each.

Related questions

  • 20.4.1 How does the tvOS focus engine work: focusable items, preferred focus, focus guides, and SwiftUI’s focusable…
  • 20.4.2 What input and layout rules define a good tvOS interface: the Siri Remote, controllers, the 10-foot UI, and…
  • 20.1.1 How do you structure one codebase for several Apple platforms: shared cores, UI leaves, #if os, canImport and…
  • 20.9.1 Which SwiftUI scene types exist on which platform (Settings, MenuBarExtra, Window, DocumentGroup…

← Which SwiftUI scene types exist on which platform (Settings…   How do windows, volumes and immersive spaces fit together in SwiftUI… →