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

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)!

Related

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 on macOS: list with detail view and multiple selection

TL;DR:
I cannot have a list with a detail view and multiple selections on macOS.
In more detail:
For demonstration purposes of my issue, I made a small example project. The UI looks as follows:
This is the "app" when launched, with a list on top and a detail representation below. Because I am using the List's initialiser init(_:selection:rowContent:), where selection is of type Binding<SelectionValue?>? according to Apple's documentation, I get selecting items with the keyboard arrow keys for free.
Here's the complete code:
import SwiftUI
#main
struct UseCurorsInLisstApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(ViewModel())
}
}
}
class ViewModel: ObservableObject {
#Published var items = [Item(), Item(), Item(), Item(), Item()]
#Published var selectedItem: Item? = nil
}
struct Item: Identifiable, Hashable {
let id = UUID()
}
struct ContentView: View {
#EnvironmentObject var vm: ViewModel
var body: some View {
VStack {
List(vm.items, id: \.self, selection: $vm.selectedItem) { item in
VStack {
Text("Item \(item.id.uuidString)")
Divider()
}
}
Divider()
Group {
if let item = vm.selectedItem {
Text("Detail item \(item.id.uuidString)")
} else {
Text("No selection…")
}
}
.frame(minHeight: 200.0, maxHeight: .infinity)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Now, having had success with this so far, I figured being able to select more than one row would be useful, so I took a closer look into List(_:selection:rowContent:), where selection is of type Binding<Set<SelectionValue>>?. To be able to have a detail view, I just made a few minor changes to
the ViewModel:
class ViewModel: ObservableObject {
#Published var items = [Item(), Item(), Item(), Item(), Item()]
#Published var selectedItem: Item? = nil
#Published var selectedItems: Set<Item>? = nil {
didSet {
if selectedItems?.count == 1, let item = selectedItems?.first {
selectedItem = item
}
}
}
}
and the ContentView:
struct ContentView: View {
#EnvironmentObject var vm: ViewModel
var body: some View {
VStack {
List(vm.items, id: \.self, selection: $vm.selectedItems) { item in
VStack {
Text("Item \(item.id.uuidString)")
Divider()
}
}
Divider()
Group {
if vm.selectedItems?.count == 1, let item = vm.selectedItems?.first {
Text("Detail item \(item.id.uuidString)")
} else {
Text("No or multiple selection…")
}
}
.frame(minHeight: 200.0, maxHeight: .infinity)
}
}
}
The problem now is that I cannot select an item of the row any more, neither by clicking, nor by arrow keys. Is this a limitation I am running into or am I "holding it wrong"?
Use the button and insert it into the set. Keyboard selection also works with shift + (up/down arrow)
class ViewModel: ObservableObject {
#Published var items = [Item(), Item(), Item(), Item(), Item()]
#Published var selectedItem: Item? = nil
#Published var selectedItems: Set<Item> = []
}
struct ContentView: View {
#EnvironmentObject var vm: ViewModel
var body: some View {
VStack {
List(vm.items, id: \.self, selection: $vm.selectedItems) { item in
Button {
vm.selectedItem = item
vm.selectedItems.insert(item)
} label: {
VStack {
Text("Item \(item.id.uuidString)")
Divider()
}
}
.buttonStyle(PlainButtonStyle())
}
Divider()
Group {
if let item = vm.selectedItem {
Text("Detail item \(item.id.uuidString)")
} else {
Text("No or multiple selection…")
}
}
.frame(minHeight: 200.0, maxHeight: .infinity)
}
}
}
Add remove:
Button {
vm.selectedItem = item
if vm.selectedItems.contains(item) {
vm.selectedItems.remove(item)
} else {
vm.selectedItems.insert(item)
}
}
Edit
In simple need to give a blank default value to set. because in nil it will never append to set need initialization.
#Published var selectedItems: Set<Item> = [] {
Actually my error was pretty dumb – making the selectedItems-set optional prevents the list from working correctly. Shoutout to #Raja Kishan, who pushed me into the right direction with his proposal.
Here's the complete working code:
import SwiftUI
#main
struct UseCurorsInLisstApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(ViewModel())
}
}
}
class ViewModel: ObservableObject {
#Published var items = [Item(), Item(), Item(), Item(), Item()]
#Published var selectedItems = Set<Item>()
}
struct Item: Identifiable, Hashable {
let id = UUID()
}
struct ContentView: View {
#EnvironmentObject var vm: ViewModel
var body: some View {
VStack {
List(vm.items, id: \.self, selection: $vm.selectedItems) { item in
VStack {
Text("Item \(item.id.uuidString)")
Divider()
}
}
Divider()
Group {
if vm.selectedItems.count == 1, let item = vm.selectedItems.first {
Text("Detail item \(item.id.uuidString)")
} else {
Text("No or multiple selection…")
}
}
.frame(minHeight: 200.0, maxHeight: .infinity)
}
}
}

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

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 !!
}
}
}

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")
}
}
}

SwiftUI - ObservableObject, EnvironmentObject, SummaryView

After creating the code, i would like to create a summary view that allows me to view the values that have been chosen in the picker.
How can I do? I read some forums about #ObservableObject and about #EnvironmentObject, but I can't understand ...
Thanks very much :)
import SwiftUI
//SUMMARYPAGE
struct SummaryView: View {
var body: some View {
NavigationView {
Form {
VStack(alignment: .leading, spacing: 6) {
Text("First Animal: \("firstAnimalSelected")")
Text("First Animal: \("secondAnimalSelected")")
}
}
}
}
}
struct SummaryView_Previews: PreviewProvider {
static var previews: some View {
SummaryView()
}
}
enum Animal: String, CaseIterable {
case select
case bear
case cat
case dog
case lion
case tiger
}
struct ContentView: View {
#State private var firstAnimal = Animal.allCases[0]
#State private var secondAnimal = Animal.allCases[0]
var body: some View {
NavigationView {
Form {
Section(header: Text("Animals")
.foregroundColor(.black)
.font(.system(size: 15))
.fontWeight(.bold)) {
Picker(selection: $firstAnimal, label: Text("Select first animal")) {
ForEach(Animal.allCases, id: \.self) { element in
Text(element.rawValue.capitalized)
}
}
Picker(selection: $secondAnimal, label: Text("Select second animal")) {
ForEach(Animal.allCases.filter { $0 != firstAnimal || firstAnimal == .select }, id: \.self) { element2 in
Text(element2.rawValue.capitalized)
}
}
}.font(.system(size: 15))
}.navigationBarTitle("List", displayMode: .inline)
}
}
}
You can move your #State properties to an ObservableObject:
class ViewModel: ObservableObject {
#Published var firstAnimal = Animal.allCases[0]
#Published var secondAnimal = Animal.allCases[0]
}
and access them from an #EnvironmentObject:
struct ContentView: View {
#EnvironmentObject var viewModel: ViewModel
var body: some View {
...
Picker(selection: $viewModel.firstAnimal, label: Text("Select first animal")) {
ForEach(Animal.allCases, id: \.self) { element in
Text(element.rawValue.capitalized)
}
}
}
}
struct SummaryView: View {
#EnvironmentObject var viewModel: ViewModel
var body: some View {
NavigationView {
Form {
VStack(alignment: .leading, spacing: 6) {
Text("First Animal: \(viewModel.firstAnimal.rawValue)")
Text("Second Animal: \(viewModel.secondAnimal.rawValue)")
}
}
}
}
}
Remember to inject your ViewModel to your root view:
ContentView().environmentObject(ViewModel())

Resources