11

I am trying to create a macOS app with SwiftUI. I need a TabView or something similar, but when I use TabView the segmented control is not in the macOS Toolbar.

Here is an example of what I would like:

enter image description here

My current code is:

import SwiftUI

struct ContentView: View {
    var body: some View {
        TabView {
            Text("1")
                .tabItem {
                    Text("1")
            }
        }
    }
}

The result is here as an image

The segmented control needs to be in the toolbar and not the view.

3 Answers 3

5

I stumbled upon your question when I wanted to build something similar on macOS BigSur. I am using Xcode 12.2.

Here is what my solution would look like inspired by the answer from Asperi. It was important to set the title of the window group to an empty string "" otherwise it look weird.

Note that it only works when you run the app, not in the preview!

TabBar Example

App File

import SwiftUI

@main
struct SegmentedToolbarApp: App {
    var body: some Scene {
        WindowGroup("") {
            ToolbarItemPlacement()
        }
    }
}

ToolbarItemPlacement View

The important part was the placement with principal.

It was also important to set a bigger minWidth - otherwise the toolbar would disappear!

import SwiftUI

struct ToolbarItemPlacement: View {
    
    private let tabs = ["Watch Now", "Movies", "TV Shows", "Kids", "Library"]
    @State private var selectedTab = 0
    
    var body: some View {
        VStack {
            ChildTabView(title: self.tabs[self.selectedTab], index: self.selectedTab)
        }
        .toolbar {
            ToolbarItem(placement: .principal) {
                
                Picker("", selection: $selectedTab) {
                    ForEach(tabs.indices) { i in
                        Text(self.tabs[i]).tag(i)
                    }
                }
                .pickerStyle(SegmentedPickerStyle())
                .padding(.top, 8)
            }
        }
        .frame(minWidth: 800, minHeight: 400)
    }
}

ChildTabView

struct ChildTabView: View {
    var title: String
    var index: Int

    var body: some View {
        Text("\(title) - Index \(index)")
            .padding()
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

What, if you add some TextFields in the ChildView? Or any other controls? This solution is not working, when you have controls, because the ChildTabView get's redrawn every time, when you change the tab...
2

Here is a simplified demo of possible approach to achieve this. Tested & works with Xcode 11.2.

demo

1) Prepare window to have needed style and background in AppDelegate

func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Create the SwiftUI view that provides the window contents.
    let contentView = ContentView()
        .edgesIgnoringSafeArea(.top)
        .frame(minWidth: 480, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)

    // Create the window and set the content view. 
    window = NSWindow(
        contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
        styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
        backing: .buffered, defer: false)
    window.center()
    window.titlebarAppearsTransparent = true
    window.titleVisibility = .hidden

    window.setFrameAutosaveName("Main Window")
    window.contentView = NSHostingView(rootView: contentView)
    window.makeKeyAndOrderFront(nil)
}

2) Prepare window content view to have needed behavior

struct ContentView: View {
    private let tabs = ["Watch Now", "Movies", "TV Shows", "Kids", "Library"]
    @State private var selectedTab = 0
    var body: some View {
        VStack {
            HStack {
                Spacer()
                Picker("", selection: $selectedTab) {
                    ForEach(tabs.indices) { i in
                        Text(self.tabs[i]).tag(i)
                    }
                }
                .pickerStyle(SegmentedPickerStyle())
                .padding(.top, 8)
                Spacer()
            }
            .padding(.horizontal, 100)
            Divider()
            GeometryReader { gp in
                VStack {
                    ChildTabView(title: self.tabs[self.selectedTab], index: self.selectedTab)
                }
            }
        }
    }
}

struct ChildTabView: View {
    var title: String
    var index: Int

    var body: some View {
        Text("\(title)")
    }
}

2 Comments

Thank you for your answer. I am really looking for something that is like Apple's apps, even if in AppKit. Your response is appreciated though. Thanks
I believe that Apple showed a SwiftUI Mac app at WWDC 2019 with a segmented control in the toolbar.
1

It unfortunately seems that as of macOS 13.0 and SwiftUI 4.0 the only way for this to work is if your app is SwiftUI app.

@main
struct SettingsViewDemoApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        
        Settings {
            SettingsView()
        }
    }
}

Source: https://serialcoder.dev/text-tutorials/macos-tutorials/presenting-the-preferences-window-on-macos-using-swiftui/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.