Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: Navigation Bar Title in Reusable Cross-Platform (iOS & macOS) View

I'm attempting to create reusable SwiftUI View for a framework, which can then be used across both iOS/iPadOS and macOS.

This generally works fine; however, because macOS views don't have navigation bars, including a navigation bar title (important for iOS) causes an error when the view's included in a macOS target:

.navigationBarTitle(Text("Page Title"))

Value of type '...' has no member 'navigationBarTitle'

Any suggestions on a conditional compilation (or any other) approach that can allow the same view to build for both platforms?

The closest I've come is the following. Including the extra Text view is easy enough, but because the bar title's a modifier, wrapping just that part in conditional compilation produces other errors:

public struct ExampleView: View {

    private let pageTitle = "Page Title"
    
    public var body: some View {

        VStack(alignment: .center) {
            
            #if os(macOS)
            Text(pageTitle)
                .font(.title)
            #endif

            Spacer()           
            Text("This is the view content")
            Spacer()
        }

        #if !os(macOS)
        .navigationBarTitle(Text(pageTitle))
        #endif
    }
}

Unexpected platform condition (expected 'os', 'arch', or 'swift')

Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type

like image 692
TheNeil Avatar asked Mar 04 '23 01:03

TheNeil


2 Answers

I established one approach: Move the main content to another View property, then modify it to add the navbar title (if needed) before returning it as body. When it's separated this way, conditional compilation can be used.

It's a bit inelegant, but it works. It can also be used to set macOS-specific things, such as the view's overall frame size. Suggestions on a better approach are welcome!

Swift v5.1

public struct ExampleView: View {

    private let pageTitle = "Page Title"

    #if !os(macOS)
    public var body: some View {
        main.navigationBarTitle(Text(pageTitle))
    }
    #else
    public var body: some View {
        main.frame(
            minWidth: 500,
            minHeight: 500
        )
    }
    #endif
    public var main: some View {

        VStack(alignment: .center) {

            #if os(macOS)
            Text(pageTitle)
                .font(.title)
            #endif

            Spacer()           
            Text("This is the view content")
            Spacer()
        }
    }
}
like image 187
TheNeil Avatar answered Mar 05 '23 14:03

TheNeil


you can add an extension to View struct. It will put conditional compilation outside the block and work as you expect

#if os(macOS)
extension View {
    func navigationBarTitle(_ title: Text) -> some View {
        return self
    }
}
#endif
like image 27
sluneau Avatar answered Mar 05 '23 15:03

sluneau