Reputation: 1281
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.
Click here to see an example of what I would like
My current code is:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
Text("1")
.tabItem {
Text("1")
}
}
}
}
The segmented control needs to be in the toolbar and not the view.
Upvotes: 11
Views: 5352
Reputation: 2294
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()
}
}
}
Upvotes: 1
Reputation: 370
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!
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()
}
}
Upvotes: 5
Reputation: 258461
Here is a simplified demo of possible approach to achieve this. Tested & works with Xcode 11.2.
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)")
}
}
Upvotes: 2