How to toggle the visibility of the third pane of NavigationView? - macos

Assuming the following NavigationView:
Struct ContentView: View {
#State var showRigthPane: Bool = true
var body: some View {
NavigationView {
Sidebar()
MiddlePane()
RightPane()
}.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: toggleSidebar, label: {Image(systemName: "sidebar.left")})
}
ToolbarItem(placement: .primaryAction) {
Button(action: self.toggleRightPane, label: { Image() })
}
}
}
private func toggleRightPane() {
// ?
}
// collapsing sidebar - this works
private func toggleSidebar() {
NSApp.keyWindow?.initialFirstResponder?.tryToPerform(
#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
}
How can I implement the toggleRightPane() function to toggle the visibility of the right pane?

Updated to use a calculated property returning two different navigation views. Still odd behavior with sidebar, but with a work-around it is functional. Hopefully someone can figure out the sidebar behavior.
struct ToggleThirdPaneView: View {
#State var showRigthPane: Bool = true
var body: some View {
VStack {
navigationView
}
.navigationTitle("Show and Hide")
}
var navigationView : some View {
if showRigthPane {
return AnyView(NavigationView {
VStack {
Text("left")
}
.toolbar {
Button(action: { showRigthPane.toggle() }) {
Label("Add Item", systemImage: showRigthPane ? "rectangle.split.3x1" : "rectangle.split.2x1")
}
}
Text("middle")
}
)
} else {
return AnyView(NavigationView {
VStack {
Text("left")
}
.toolbar {
Button(action: { showRigthPane.toggle() }) {
Label("Add Item", systemImage: showRigthPane ? "rectangle.split.3x1" : "rectangle.split.2x1")
}
}
Text("middle")
Text("right")
})
}
}
}

Try the following (cannot test)
struct ContentView: View {
#State private var showRigthPane = true
var body: some View {
NavigationView {
Sidebar()
MiddlePane()
if showRigthPane { // << here !!
RightPane()
}
}.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: self.toggleRightPane, label: { Image() })
}
}
}
private func toggleRightPane() {
withAnimation {
self.showRigthPane.toggle() // << here !!
}
}
}

Related

"Result of 'MainView' initializer is unused" -- trying to filter NavigationView in SwiftUI

I am trying to piece together a custom NavigationView in SwiftUI with Buttons for filtering. In this simple step I am just trying to change '.navigationTitle(title)' through filtering with three buttons ('header1', 'header2', 'header3'), however it is not working.
You can find a visual of this code here
ContentView:
struct ContentView: View {
var body: some View {
MainView(filter: .header1)
}
}
MainView:
struct MainView: View {
enum SourceType {
case header1, header2, header3
}
#EnvironmentObject var sourceItems: SourceItems
let filter: SourceType
var body: some View {
NavigationView {
List {
HeaderView()
ForEach(0..<10) { sourceItem in
NavigationLink(destination: Text("Destination")) {
Text("Item: \(sourceItem)")
}
}
}
.listStyle(.inset)
.navigationTitle(title) // I am trying to make this change
}
}
var title: String {
switch filter {
case .header1:
return "Header 1"
case .header2:
return "Header 2"
case .header3:
return "Header 3"
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView(filter: .header1)
.environmentObject(SourceItems())
}
}
HeaderView (This is where I get the warnings):
struct HeaderView: View {
#StateObject var sourceItems = SourceItems()
var body: some View {
HStack {
Button(action: {
MainView(filter: .header1)
}, label: {
Text("Header 1")
})
.buttonStyle(PlainButtonStyle())
Spacer()
Button(action: {
MainView(filter: .header2)
}, label: {
Text("Header 2")
})
.buttonStyle(PlainButtonStyle())
Spacer()
Button(action: {
MainView(filter: .header3)
}, label: {
Text("Header 3")
})
.buttonStyle(PlainButtonStyle())
}
.environmentObject(sourceItems)
}
}
The code runs but the buttons don't filter.
Thanks for any help (I am new to SwiftUI)!

Swift if statement in modifier

So I have an basic Taskview:
TabView {
NavigationView {
TasksView()
}
.tabItem {
Image(systemName: "bookmark")
Text("Tasks")
}
NavigationView {
ShopView()
}
.tabItem {
Image(systemName: "cart.fill")
Text("Shop")
}
}
.environment(\.colorScheme, isDarkMode ? .dark : .light)
And now I want to add an if statement to .environment. So like if Default: .environment(.colorScheme, isDarkMode ? .dark : .light)
How can I do this?
Use #Environment to call the current color scheme. Then you can create a color variable.
struct ContentView: View {
#Environment(\.colorScheme) private var colorScheme
var body: some View {
TabView {
NavigationView {
TasksView()
}
.tabItem {
Image(systemName: "bookmark")
Text("Tasks")
}
.foregroundColor(titleColor) // <- here
NavigationView {
ShopView()
}
.tabItem {
Image(systemName: "cart.fill")
Text("Shop")
}
.foregroundColor(titleColor) // <- here
}
}
private var titleColor: Color {
switch colorScheme {
case .light:
return .red
case .dark:
return .yellow
#unknown default:
return .blue
}
}
}
Here's a possible solution. Use an ObservableObject class, store and save the variable. Set it as a StateObject and set as environmentObject in your App, then you can handle updates in your View by accessing the variable.
class ColorScheme: ObservableObject {
#AppStorage("dark") var dark = false
}
struct MyApp: App {
#StateObject var colorscheme = ColorScheme()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(colorscheme)
.colorScheme(colorscheme.dark ? .dark : .light)
}
}
}
struct ContentView: View {
#EnvironmentObject var colorscheme: ColorScheme
var body: some View {
NavigationView {
List {
Toggle("Color Scheme", isOn: colorscheme.$dark)
}
}
}
}

SwiftUI - Picker .onChange and didSet

I'm trying to achieve the behavior in the attached GIF:
Sorry for the High Speed, I had to compress it dramatically to be able to upload it here. The App is "Documents" from Readdle if you want to have a look on your own.
Anyways: I'm exactly trying to achieve this behavior (sorting and filtering, including the dynamic arrow up down icon).
I tried the following approach, however I'm not able to achieve this "ontap" expierience. On Change only triggers when I change the value but when I want to sort an existing value ascending and descending it's not working (which is obvious because it's not changing). I already played around with "didSet" but this also did not work.
Do you have an idea how this can be accomplished?
Below is my code:
import SwiftUI
struct ContentView: View {
#State var selection = 0
#State var sortByAsc = true
#State var filterColumn = "A"
//Test to set case via picker but picter doesnt execute didSet
#State var myFilterTest: MyFilters = .alphabetical {
didSet {
switch myFilterTest {
case .creationDate:
sortByAsc.toggle()
print("c")
case .rating:
sortByAsc.toggle()
print("b")
case .alphabetical:
sortByAsc.toggle()
print("a")
}
}
}
var body: some View {
NavigationView {
Text("Hello, World!")
.padding()
.navigationTitle("SwiftUI")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu(content: {
Picker("My Picker", selection: $selection) {
Label("Title", systemImage: sortByAsc ? "arrow.down" : "arrow.up")
.tag(0)
Label("Rating", systemImage: sortByAsc ? "arrow.down" : "arrow.up")
.tag(1)
.onTapGesture {
print("tap")
}
}
.onChange(of: selection) { tag in
print("Selected Tag: \(tag)")
sortByAsc.toggle()
if(tag == 0) {
filterColumn = "Title"
}
if(tag == 1) {
filterColumn = "Rating"
}
}
}, label: {
Image(systemName: "ellipsis.circle")
})
}
}
}
}
}
enum MyFilters: CaseIterable {
case alphabetical
case rating
case creationDate
}
Solved It. Here's the Code:
struct PickerView: View {
#State private var pickerIndex = 0
#State private var previousPickerIndex = 0
#State var sortByAsc = true
var body: some View {
let pickerSelection = Binding<Int>(get: {
return self.pickerIndex
}, set: {
self.pickerIndex = $0
if(pickerIndex == previousPickerIndex) {
sortByAsc.toggle()
}
previousPickerIndex = pickerIndex
})
NavigationView {
Text("Hello, World!")
.padding()
.navigationTitle("SwiftUI")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu(content: {
Picker("My Picker", selection: pickerSelection) {
ForEach(0..<4, id: \.self) { index in
Label("Title \(index)", systemImage: getSortingImage(menuItem: index))
.tag(index)
}
}
}, label: {
Image(systemName: "ellipsis.circle")
})
}
}
}
}
func getSortingImage(menuItem: Int) -> String {
if(menuItem == pickerIndex) {
if(sortByAsc) {
return "arrow.down"}
else {
return "arrow.up"
}
}
else {
return ""
}
}
}

SwiftUI for macOS - trigger sheet .onDismiss problem

In a multiplatform app I'm showing a sheet to collect a small amount of user input. On iOS, when the sheet is dismissed, the relevant .onDismiss method is called but not on macOS.
I've read that having the .onDismiss in the List can cause problems so I've attached it to the button itself with no improvement. I've also tried passing the isPresented binding through and toggling that within the sheet itself to dismiss, but again with no success.
I am employing a NavigationView but removing that makes no difference. The following simplified example demonstrates my problem. Any ideas? Should I even be using a sheet for this purpose on macOS?
I just want to make clear that I have no problem closing the sheet. The other questions I found were regarding problems closing the sheet - I can do that fine.
import SwiftUI
#main
struct SheetTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
ListView()
}
}
}
The List view.
struct ListView: View {
#State private var isPresented: Bool = false
var body: some View {
VStack {
Text("Patterns").font(.title)
Button(action: {
isPresented = true
}, label: {
Text("Add")
})
.sheet(isPresented: $isPresented, onDismiss: {
doSomethingAfter()
}) {
TestSheetView()
}
List {
Text("Bingo")
Text("Bongo")
Text("Banjo")
}
.onAppear(perform: {
doSomethingBefore()
})
}
}
func doSomethingBefore() {
print("Johnny")
}
func doSomethingAfter() {
print("Cash")
}
}
This is the sheet view.
struct TestSheetView: View {
#Environment(\.presentationMode) var presentationMode
#State private var name = ""
var body: some View {
Form {
TextField("Enter name", text: $name)
.padding()
HStack {
Spacer()
Button("Save") {
presentationMode.wrappedValue.dismiss()
}
Spacer()
}
}
.frame(minWidth: 300, minHeight: 300)
.navigationTitle("Jedward")
}
}
Bad issue.. you are right. OnDismiss is not called. Here is a workaround with Proxybinding
var body: some View {
VStack {
Text("Patterns").font(.title)
Button(action: {
isPresented = true
}, label: {
Text("Add")
})
List {
Text("Bingo")
Text("Bongo")
Text("Banjo")
}
.onAppear(perform: {
doSomethingBefore()
})
}
.sheet(isPresented: Binding<Bool>(
get: {
isPresented
}, set: {
isPresented = $0
if !$0 {
doSomethingAfter()
}
})) {
TestSheetView()
}
}

SwiftUI macOS right sidebar inspector

I have a document-based SwiftUI app. I'd like to make a inspector sidebar like the one in Xcode.
Starting with Xcode's Document App template, I tried the following:
struct ContentView: View {
#Binding var document: DocumentTestDocument
#State var showInspector = true
var body: some View {
HSplitView {
TextEditor(text: $document.text)
if showInspector {
Text("Inspector")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.toolbar {
Button(action: { showInspector.toggle() }) {
Label("Toggle Inspector", systemImage: "sidebar.right")
}
}
}
}
Which yielded:
How can I extend the right sidebar to full height like in Xcode?
NavigationView works for left-side sidebars, but I'm not sure how to do it for right-side sidebars.
Here is some stripped down code that I have used in the past. It has the look and feel that you want.
It uses a NavigationView with .navigationViewStyle(.columns) with essentially three panes. Also, the HiddenTitleBarWindowStyle() is important.
The first (navigation) pane is never given any width because the second (Detail) pane is always given all of the width when there is no Inspector, or all of the width less the Inspector's width when it's present. The ToolBar needs to be broken up and have its contents placed differently depending on whether the Inspector is present or not.
#main
struct DocumentTestDocumentApp: App {
var body: some Scene {
DocumentGroup(newDocument: DocumentTestDocument()) { file in
ContentView(document: file.$document)
}
.windowStyle(HiddenTitleBarWindowStyle())
}
}
struct ContentView: View {
#Binding var document: DocumentTestDocument
#State var showInspector = true
var body: some View {
GeometryReader { window in
if showInspector {
NavigationView {
TextEditor(text: $document.text)
.frame(minWidth: showInspector ? window.size.width - 200.0 : window.size.width)
.toolbar {
LeftToolBarItems(showInspector: $showInspector)
}
Inspector()
.toolbar {
RightToolBarItems(showInspector: $showInspector)
}
}
.navigationViewStyle(.columns)
} else {
NavigationView {
TextEditor(text: $document.text)
.frame(width: window.size.width)
.toolbar {
LeftToolBarItems(showInspector: $showInspector)
RightToolBarItems(showInspector: $showInspector)
}
}
.navigationViewStyle(.columns)
}
}
}
}
struct LeftToolBarItems: ToolbarContent {
#Binding var showInspector: Bool
var body: some ToolbarContent {
ToolbarItem(content: { Text("test left toolbar stuff") } )
}
}
struct RightToolBarItems: ToolbarContent {
#Binding var showInspector: Bool
var body: some ToolbarContent {
ToolbarItem(content: { Spacer() } )
ToolbarItem(placement: .primaryAction) {
Button(action: { showInspector.toggle() }) {
Label("Toggle Inspector", systemImage: "sidebar.right")
}
}
}
}
struct Inspector: View {
var body: some View {
VStack {
Text("Inspector Top")
Spacer()
Text("Bottom")
}
}
}

Resources