Conditional modifiers: if #available iOS 14

I would love my SwiftUI views to support the new modifiers in iOS 14 (e.g. .navigationTitle), but still run on iOS 13 (e.g. using deprecated .navigationBarTitle). What is the best way to add conditional #if available code for view modifiers?

The only way I can find to do it is by extracting every subview, and putting #if available blocks around every view declaration — this seems very tedious, as I am redefining each view twice.

E.g.
Code Block Swift
struct SettingsView: View {
    var body: some View {
        NavigationView {
            if #available(iOS 14.0, *) {
                SettingsContentView()
                    .navigationTitle("Settings")
            } else {
                SettingsContentView()
                    .navigationBarTitle("Settings")
            }
        }
    }
}


Is there a better way to just add conditional code only around the modifiers? Thanks in advance.

Replies

You can create a custom modifier to handle this, like so:
Code Block Swift
@available(iOS 14, *)
struct NavigationTitleViewModifier: ViewModifier {
var text: String
func body(content: Content) -> some View {
content
.navigationTitle(text)
}
}
struct NavigationBarTitleViewModifier: ViewModifier {
var text: String
func body(content: Content) -> some View {
content
.navigationBarTitle(text)
}
}
extension View {
@ViewBuilder
func customNavigationTitle(with text: String) -> some View {
if #available(iOS 14, *) {
self
.modifier(NavigationTitleViewModifier(text: text))
}
else {
self.modifier(NavigationBarTitleViewModifier(text: text))
}
}
}


Then, instead of having to write those conditionals every time you want to add a navigation title, you can simply do:
Code Block Swift
SettingsContentView()
.customNavigationTitle(with: "Settings")



Thanks JakeShort, that is helpful, and would save extracting every view and passing many bindings down the view hierarchy! I've not explored the ViewBuilder property wrapper much, but noticed it when browsing the code in the Fruta sample app.

I was hoping that Apple might add built in support for this, or a ternary operator for modifiers: e.g.

Code Block Swift
MyView()
#available (iOS 14, *) ? .navigationTitle : .navigationBarTitle


Would the ViewBuilder method work for modifiers which are new in iOS 14 and don't have an iOS 13.0 alternative? E.g.
Code Block Swift
.help("Alt Text")


Another (more drastic) option I thought of would be to have two definitions for each view, an iOS 14 and iOS 13 one.
Code Block Swift
@available(iOS 14.0, *)
struct Settings: View {
    var body: some View {
        Text("Hello, World")
            .navigationTitle("Title")
    }
}
struct SettingsDeprecated: View {
    var body: some View {
        Text("Hello, World")
            .navigationBarTitle("Title")
    }
}
struct ContentView: View {
    var body: some View {
        if #available(iOS 14.0, *) {
            Settings()
        } else {
            SettingsDeprecated()
        }
    }
}


But I get an "Function declares an opaque return type" error, and would be concerned about inconsistencies in keeping them both up to date.
@JakeShort - this is good stuff - thank you so much, was looking for that.

@JakeShort thank you so much! This was exactly what I needed