Sidebar Menu for macOS in SwiftUI - swift

I'm trying to implement a menu, so far this is what have:
NavigationView
struct macOS_NavigationView: View {
#State private var selectedTab: HostingBarCategories = .Screen1
var body: some View {
NavigationView {
// SideBar Menu
List {
ForEach(1 ... 10, id: \.self) { index in
NavigationLink(destination:
Text("\(index)")
.frame(maxWidth: .infinity, maxHeight: .infinity)
) {
Text("Link \(index)")
}
}
}
.listStyle(SidebarListStyle())
// Primary View
Text("Select a menu...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
The part where I'm stuck is trying to implement my current model that I'm using for iOS in the TabBar:
HostingBarCategories
enum HostingBarCategories: Hashable {
case Screen1
case Screen2
case Screen3
case Screen4
case Screen5
}
So how can I use that model so when a user clicks a menu it goes to that screen? (the model can be expanded, it doesn't have to be that one specifically)
Edit: Let me add the current iOS TabBar so it's more visually understandable, this is just for reference for the above and has nothing to do with the question:
struct iOS_TabBarView: View {
#State private var selectedTab: HostingBarCategories = .Screen1
var body: some View {
TabView(selection: $selectedTab) {
Text("1")
.tag(0)
.tabItem {
Image(systemName: "pencil.and.outline")
Text("1")
}
Text("2")
.tag(1)
.tabItem {
Image(systemName: "checkmark")
Text("2")
}
Text("3")
.tag(2)
.tabItem {
Image(systemName: "calendar.circle.fill")
Text("3")
}
Text("4")
.tag(3)
.tabItem {
Image(systemName: "flame")
Text("4")
}
Text("5")
.tag(3)
.tabItem {
Image(systemName: "slider.horizontal.3")
Text("5")
}
}
}
}

You need to make your enum case-iterable to use it as model in ForEach, like
enum HostingBarCategories: Hashable, CaseIterable {
case Screen1
case Screen2
case Screen3
case Screen4
case Screen5
var string: String { String(describing: self) }
}
struct macOS_NavigationView: View {
#State private var selectedTab: HostingBarCategories = .Screen1
var body: some View {
NavigationView {
// SideBar Menu
List {
ForEach(HostingBarCategories.allCases, id: \.self) { screen in
NavigationLink(destination:
Text(screen.string)
.frame(maxWidth: .infinity, maxHeight: .infinity)
) {
Text("Link \(screen.string)")
}
}
}
.listStyle(SidebarListStyle())
// Primary View
Text("Select a menu...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}

Related

Boundary on top of NavigationView

When I was trying to change the color of my navigation view page I realized that there is a weird boundary on top. I can't figure out what it is or how to get rid of it. Would anyone happen to know?
Here is the code.
Image with displayMode: .inline
The parent view code is the view that is presenting the page I am having trouble with.
Parent View:
Code:
import Foundation
import SwiftUI
import UIKit
struct ContentView: View {
// variable for view model
#ObservedObject var viewModel = VariableViewModel()
// SWIFT UI START
var body: some View {
// Main page
NavigationView {
ZStack {
Color(.orange).edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
VStack {
HStack {
Spacer()
NavigationLink(destination:
SettingsView()
){
Image(systemName: "gearshape.fill").font(.system(size: 25))
}
Spacer()
Spacer()
Spacer()
Spacer()
Spacer()
Spacer()
NavigationLink(destination:
Text("You")
){
Image(systemName: "chart.bar.xaxis").font(.system(size: 25))
}
Spacer()
}
Text("Pick a mode!").font(.largeTitle).bold().offset(x: 0, y: 30)
ZStack {
VStack {
Spacer()
// ADDITION SECTION
NavigationLink(destination:
VStack {
Spacer()
MathView(operatorName: "Addition")
}
){
HStack {
Text("Addition")
Image(systemName: "plus.square")
}.font(.largeTitle)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.padding(10)
.border(Color.blue, width: 5)
}
Spacer()
// SUBTRACTION SECTION
NavigationLink(destination:
VStack {
Spacer()
MathView(operatorName: "Subtraction")
}
){
HStack {
Text("Subtraction")
Image(systemName: "minus.square")
}.font(.largeTitle)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.padding(10)
.border(Color.blue, width: 5)
}
Spacer()
// MULTIPLICATION SECTION
NavigationLink(destination:
VStack {
Spacer()
MathView(operatorName: "Multiplication")
}
){
HStack {
Text("Multiplication")
Image(systemName: "multiply.square")
}.font(.largeTitle)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.padding(10)
.border(Color.blue, width: 5)
}
Spacer()
// DIVISION SECTION
NavigationLink(destination:
VStack {
Spacer()
MathView(operatorName: "Division")
}
){
HStack {
Text("Division")
Image(systemName: "divide.square")
}.font(.largeTitle)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.padding(10)
.border(Color.blue, width: 5)
}
}
}.navigationBarHidden(true)
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Code:
import SwiftUI
struct MathView: View {
#ObservedObject var viewModel = VariableViewModel()
let operatorName: String
var body: some View {
ZStack {
Color.orange.edgesIgnoringSafeArea(.all)
VStack {
Spacer()
NavigationLink(destination:
MathContentView(operatorName: "Addition", operatorSymbol: "plus", difficultyNumber1: 5, difficultyNumber2: 5)
) {
Text("Easy")
.font(.title2)
.padding(35)
.foregroundColor(.white)
.background(Color(.systemGreen))
.cornerRadius(40)
.onAppear(perform: {
if operatorName == "Addition" {
self.viewModel.result = self.viewModel.num1 + self.viewModel.num2
} else if operatorName == "Subtraction" {
self.viewModel.result = self.viewModel.num1 - self.viewModel.num2
} else if operatorName == "Multiplication" {
self.viewModel.result = self.viewModel.num1 * self.viewModel.num2
};
withAnimation {
viewModel.resetVariables()
// numbers generator
}
})
}
Spacer()
}
}
}
}
To remove the empty space below the NavigationView add .navigationBarTitleDisplayMode(.inline) to the top view:
ZStack {
// ...
}
.navigationBarTitleDisplayMode(.inline)
Then, the slim line between the navigationViewTitle and the content below comes from the Spacer at the top of the VStack in NavigationLink that pushes the MathView.
NavigationLink(destination:
VStack {
Spacer() // this causes the *slim line*
MathView(operatorName: "Addition")
}
)
You need to remove the Spacer (and the VStack as well):
NavigationLink(destination:
MathView(operatorName: "Addition")
)

TabBar with cases instead of int - SwiftUI

I'm trying to reuse some old code from a question I had previously asked and it's not working anymore, did something happen under the hood?
When I lunch the app the TabBar goes to index 0 instead of 2. This is my current code:
struct iOS_TabBarView: View {
#State private var selectedTab: HostingBarCategories = .Screen3
var body: some View {
TabView(selection: $selectedTab) {
Text("1")
.tag(0)
.tabItem {
Image(systemName: "pencil.and.outline")
Text("1")
}
Text("2")
.tag(1)
.tabItem {
Image(systemName: "checkmark")
Text("2")
}
Text("3")
.tag(2)
.tabItem {
Image(systemName: "calendar.circle.fill")
Text("3")
}
Text("4")
.tag(3)
.tabItem {
Image(systemName: "flame")
Text("4")
}
Text("5")
.tag(3)
.tabItem {
Image(systemName: "slider.horizontal.3")
Text("5")
}
}
}
}
and the HostingBarCategories:
enum HostingBarCategories: Hashable, CaseIterable {
case Screen1
case Screen2
case Screen3
case Screen4
case Screen5
var string: String { String(describing: self) }
}
What's going on?
I assume tag & selection should be the same type, so try
struct iOS_TabBarView: View {
#State private var selectedTab: HostingBarCategories = .Screen3
var body: some View {
TabView(selection: $selectedTab) {
Text("1")
.tag(HostingBarCategories.Screen1) // << like this for all !!

Filling HStack swiftUI

I want to create a button stack like having a play and record button using SwiftUI, after creating it just does not look anything like what I wanted.
var body: some View {
HStack(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, spacing: 8) {
Spacer()
Button(action: {
print("Recordinggg")
}, label: {
Text("Record")
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(40.0)
})
Spacer()
Button(action: {
print("Recordinggg")
}, label: {
Text("Play")
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(40.0)
})
Spacer()
}
}
What I actually want is something like this
Use proper frame, padding you can achieve this. Here is an example code.
Create ButtonStyle.
struct ThemeButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding([.top, .bottom], 10)
.foregroundColor(.white)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.background(Color.blue)
.cornerRadius(40.0)
}
}
Your view
struct ContentView: View {
#State private var phase: CGFloat = 0
var body: some View {
HStack(alignment: .center, spacing: 0) {
Button(action: {
print("Recordinggg")
}, label: {
Text("Record")
})
.buttonStyle(ThemeButtonStyle())
Spacer()
Button(action: {
print("Recordinggg")
}, label: {
Text("Play")
})
.buttonStyle(ThemeButtonStyle())
}
.padding()
}
}

SwiftUI Set Image Boundary

i want to set my right image frame boundaries like the left one without moving image or text. How can I do that. I tried a couple way but I couldn't find the solution.
You should use the built-in TabView. It provides all the functionality for you, and already made with accessibility in mind.
Here is an example (you can change it for your text and images):
struct ContentView: View {
#State private var selection: Int = 1
var body: some View {
TabView(selection: $selection) {
Text("Tab Content 1")
.tabItem {
Label("1", systemImage: "1.square")
}
.tag(1)
Text("Tab Content 2")
.tabItem {
Label("2", systemImage: "2.square")
}
.tag(2)
Text("Tab Content 3")
.tabItem {
Label("3", systemImage: "3.square")
}
.tag(3)
}
}
}
Result:
Custom version (highly unrecommended):
struct ContentView: View {
var body: some View {
VStack {
Spacer()
Text("Main Content")
Spacer()
HStack {
VStack {
Button {
//
} label: {
Label("1", systemImage: "1.square")
}
}.frame(maxWidth: .infinity)
VStack {
Button {
//
} label: {
Label("2", systemImage: "2.square")
}
}.frame(maxWidth: .infinity)
VStack {
Button {
//
} label: {
Label("3", systemImage: "3.square")
}
}.frame(maxWidth: .infinity)
}.frame(height: 50)
}
}
}

Collapse sidebar in SwiftUI (Xcode 12)

I'm trying to do a simple application in SwiftUI taking advantage of SwiftUI 2.0's new multiplatform project template, and I wish to add the option to collapse the sidebar as many other apps do.
I tried adding a boolean state variable that controls whether the sidebar should show or not, but that doesn't work because then the main view is turned translucent (I guess this is because macOS thinks the sidebar is now the main view).
Is there a way to natively achieve this in SwiftUI?
Please note I'm using macOS Big Sur, Xcode 12, and SwiftUI 2.0
Thanks in advance.
My code
ContentView.swift
import SwiftUI
struct ContentView: View {
#if os(iOS)
#Environment(\.horizontalSizeClass) private var horizontalSizeClass
#endif
#ViewBuilder var body: some View {
#if os(iOS)
if horizontalSizeClass == .compact {
TabController()
} else {
SidebarNavigation()
}
#else
SidebarNavigation()
.frame(minWidth: 900, maxWidth: .infinity, minHeight: 500, maxHeight: .infinity)
#endif
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.sizeThatFits)
}
}
Sidebar.swift
import SwiftUI
struct SidebarNavigation: View {
enum HyperspaceViews {
case home
case localTimeline
case publicTimeline
case messages
case announcements
case community
case recommended
case profile
}
#State var selection: Set<HyperspaceViews> = [.home]
#State var searchText: String = ""
#State var showComposeTootView: Bool = false
#State var showNotifications: Bool = false
#State private var showCancelButton: Bool = false
var sidebar: some View {
VStack {
HStack {
TextField("Search...", text: $searchText)
.cornerRadius(4)
}
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
List(selection: self.$selection) {
Group {
NavigationLink(destination: Timeline().container.frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Home", systemImage: "house")
}
.accessibility(label: Text("Home"))
.tag(HyperspaceViews.home)
NavigationLink(destination: Text("Local").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Local", systemImage: "person.2")
}
.accessibility(label: Text("Local"))
.tag(HyperspaceViews.localTimeline)
NavigationLink(destination: Text("Public").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Public", systemImage: "globe")
}
.accessibility(label: Text("Public"))
.tag(HyperspaceViews.localTimeline)
NavigationLink(destination: Text("Messages").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Messages", systemImage: "bubble.right")
}
.accessibility(label: Text("Message"))
.tag(HyperspaceViews.localTimeline)
Divider()
NavigationLink(destination: Text("Announcements").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Announcements", systemImage: "megaphone")
}
.accessibility(label: Text("Announcements"))
.tag(HyperspaceViews.announcements)
NavigationLink(destination: Text("Community").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Community", systemImage: "flame")
}
.accessibility(label: Text("Community"))
.tag(HyperspaceViews.community)
NavigationLink(destination: Text("Recommended").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Recommended", systemImage: "star")
}
.accessibility(label: Text("Community"))
.tag(HyperspaceViews.recommended)
Divider()
NavigationLink(destination: Text("Recommended").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("hyperspacedev", systemImage: "tag")
}
.accessibility(label: Text("Community"))
.tag(HyperspaceViews.recommended)
}
}
.overlay(self.profileButton, alignment: .bottom)
.listStyle(SidebarListStyle())
}
}
var profileButton: some View {
VStack(alignment: .leading, spacing: 0) {
Divider()
NavigationLink(destination: ProfileView().container.frame(maxWidth: .infinity, maxHeight: .infinity)) {
HStack {
Image("amodrono")
.resizable()
.clipShape(Circle())
.frame(width: 25, height: 25)
Text("amodrono")
.font(.headline)
}
.contentShape(Rectangle())
}
.accessibility(label: Text("Your profile"))
.padding(.vertical, 8)
.padding(.horizontal, 16)
.buttonStyle(PlainButtonStyle())
}
.tag(HyperspaceViews.profile)
}
var body: some View {
NavigationView {
#if os(macOS)
sidebar.frame(minWidth: 100, idealWidth: 180, maxWidth: 200, maxHeight: .infinity)
#else
sidebar
#endif
Text("Content List")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.sheet(isPresented: self.$showComposeTootView) {
ComposeTootView(showComposeTootView: self.$showComposeTootView)
.frame(minWidth: 400, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
}
.toolbar {
ToolbarItem {
Button(action: {
self.showNotifications.toggle()
}) {
Image(systemName: "bell")
}
.popover(
isPresented: self.$showNotifications,
arrowEdge: .bottom
) {
LazyVStack {
ForEach(0 ..< 10 ) { i in
Label("#\(i) liked your post!", systemImage: "hand.thumbsup")
.padding()
Divider()
}
}
}
}
ToolbarItem {
Button(action: {
self.showComposeTootView.toggle()
}) {
Image(systemName: "square.and.pencil")
}
}
}
}
}
struct SidebarNavigation_Previews: PreviewProvider {
static var previews: some View {
SidebarNavigation()
}
}
This worked for me -
https://developer.apple.com/forums/thread/651807
struct SwiftUIView: View {
var body: some View {
NavigationView{
}.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: toggleSidebar, label: {
Image(systemName: "sidebar.left")
})
}
}
}
}
func toggleSidebar() {
NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
It shows in the views on iOS so you will need some conditions for macOS only.
Apple provide NSToolbarToggleSidebarItemIdentifier which perform the toggleSidebar(_:) method.
.onAppear {
NSApp.keyWindow?.toolbar?.insertItem(withItemIdentifier: .toggleSidebar, at: 0)
}
This way will appear a button in your toolbar which action will invoke the sidebar toggle and it works perfectly.