I am trying to create a Preferences style toolbar (Icon over text) for my SwiftUI Settings view on macOS. Using the following code:
struct PreferencesView: View {
//MARK: Constants and Variables
@AppStorage("authToken") var authToken: String = ""
@AppStorage("author") var author: String = ""
//MARK: Main
var body: some View {
VStack {
HStack {
VStack (alignment: .leading, spacing: 10) {
Text("Author:")
Text("GitHub Auth Token:")
}
VStack {
TextField("", text: $author)
TextField("", text: $authToken)
}
}
.padding()
Spacer()
}
.toolbar {
ToolbarItem(placement: .principal) {
Button {
} label: {
Label {
Text("General")
} icon: {
Image (systemName: "gearshape")
}
.labelStyle (VerticalLabelStyle()) //custom label style
}
}
}
.frame(width: 480, height: 320)
}
}
The result is a button whose only bottom is shown.
The Label Style is defined as such:
struct VerticalLabelStyle: LabelStyle {
@ViewBuilder
func makeBody(configuration: Configuration) -> some View {
VStack {
configuration.icon
.font(.largeTitle)
configuration.title
}
}
}
Anyone know how to create this successfully in SwiftUI on macOS?
CodePudding user response:
Are you using the SwiftUI App Lifecycle? If so, add a TabView
to your Settings
view and the tabs will appear in the toolbar automatically:
Settings {
TabView {
PreferencesView()
.tabItem {
Label("General", systemImage: "gearshape")
}
}
}
If you’re not using the SwiftUI app lifecycle, then I think you’ll need to build this style of toolbar manually using NSToolbar
.
CodePudding user response:
Here a possible way for you:
struct ContentView: View {
@AppStorage("authToken") var authToken: String = ""
@AppStorage("author") var author: String = ""
var body: some View {
VStack {
HStack {
VStack (alignment: .leading, spacing: 10) {
Text("Author:")
Text("GitHub Auth Token:")
}
VStack {
TextField("", text: $author)
TextField("", text: $authToken)
}
}
.padding()
Spacer()
}
.toolbar(content: {
ToolbarItem(placement: .principal) {
HStack {
ToolbarItemView(string: "General", imageSystemName: "gearshape", action: actionOfGeneral)
ToolbarItemView(string: "About", imageSystemName: "info.circle", action: actionOfAbout)
}
}
})
.frame(width: 480, height: 320)
}
private func actionOfGeneral() { print("General clicked!") }
private func actionOfAbout() { print("About clicked!") }
}
struct ToolbarItemView: View {
let string: String
let imageSystemName: String
let action: () -> Void
@State private var isPressing: Bool = Bool()
var body: some View {
VStack(spacing: 2.0) {
Image(systemName: imageSystemName)
Text(string)
}
.frame(width: 50)
.background(Color.white.opacity(0.01))
.padding(.horizontal, 5.0)
.padding(.vertical, 2.0)
.overlay(RoundedRectangle(cornerRadius: 5.0).stroke(Color.secondary.opacity(0.5), style: StrokeStyle(lineWidth: 1.0)))
.opacity(isPressing ? 0.75 : 1.0)
.scaleEffect(isPressing ? 0.95 : 1.0)
.gesture(DragGesture(minimumDistance: .zero, coordinateSpace: .local).onChanged { _ in isPressing = true }.onEnded { _ in isPressing = false; action() })
.animation(Animation.interactiveSpring(), value: isPressing)
}
}
CodePudding user response:
I found the Apple recommended way of doing this by putting in a TabView. Works great.
var body: some View {
TabView {
GeneralSettingsView
.tabItem {
Label("General", systemImage: "gearshape")
}
}
}