Why is .onDelete not working for macOS app? - swiftui

I have noticed .onDelete is not working for macOS apps. I noticed this first when looking at the Core Data sample code Xcode provided for a macOS app. Then when I tried to implement .onDelete for my project it still did not work. I do not understand why.
I have tried different things but have found no solution to make onDelete work. Here is my current code:
struct ContentView: View {
#StateObject var notes = Notes()
#State private var AddNote = false
var body: some View {
NavigationView {
List {
ForEach(notes.newNotes) { note in
HStack {
Image(systemName: "folder")
VStack(alignment: .leading) {
Text(note.name)
.font(.headline)
}
}
}
.onDelete(perform: removeNote)
}
.toolbar {
Button {
showingAddExpense = true
} label: {
Image(systemName: "plus")
}
}
.sheet(isPresented: $AddNote) {
AddView(notes: notes)
}
}
}
func removeNote(at offsets: IndexSet) {
notes.newNotes.remove(atOffsets: offsets)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

On Mac, use .onDeleteCommand to remove list items.
You also have to keep track of the selected item in the list because Mac lists don't have an Edit button like iOS does.
The following question has multiple approaches for deleting list items on Mac:
SwiftUI on macOS: How to enable UI for onDelete
The following article explain how to remove items from SwiftUI lists on Mac:
Removing Items from SwiftUI Lists in Mac Apps

Related

Navigation bug when dismissing view while focussing on empty .searchable() modifier

When trying to navigate back from a view using the environment Dismiss value while also focussing on an empty searchable modifier the view you navigated back to becomes unresponsive. This is due to an empty UIView blocking any interaction with the view as seen in this screenshot:
Empty UIView blocking view after navigating back
This only occurs when the searchbar is focussed and empty when trying to navigate back. When there's a value in the searchbar everything works:
GIF of the bug
Am I doing something wrong here?
Tested on Xcode 14.2 iPhone 14 Pro (iOS 16.0) simulator.
import SwiftUI
struct MainPage: View {
var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
Text("Main view")
NavigationLink(destination: DetailView()) {
Text("Click me")
}
}
}
}
}
struct DetailView: View {
#Environment(\.dismiss) private var dismiss
#State private var searchText = ""
var body: some View {
VStack {
Text("Detail view")
Button("Go back") {
dismiss()
}
}
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
}
}
This bug only seems to happen when using NavigationStack or NavigationView with a .navigationViewStyle(.stack). When using NavigationView without a navigationViewStyle it seems to work fine. Currently I can work around this using the latter but I would prefer to use NavigationStack as NavigationView has become deprecated since iOS 16.0.
Any help is appreciated.

SwiftUI extra space at top of list above section header. On testing device only

I have been trying to figure out what is causing the space at the top of the screen in my production app, so I made this test app to see if it is a bug or not. The code works as intended on a simulator but when a testing device runs the code it adds extra space. The space goes away after you start scrolling, and does not comeback until the view reloads. I have tried restarting the device and other devices. I took out .navigationTitle and .navigationBarTitleDisplayMode and it did not fix the problem. So far my best guess is that there is some problem with changing the section header in .onAppear(). Changing it to .task() seems to be a workaround for now.
struct DetailView: View {
#State var item: Item
#State private var headerText = "Header"
var body: some View {
List {
Section(header: Text("\(headerText)")) {
Text("Text")
}
HStack {
Text("Red Text")
}.listRowBackground(Color.red)
// Change to .task instead
}.onAppear {
headerText = "Change Header"
}
}
}
Edit: Here is the code for the list view, it is the default new project setup.
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink(destination: DetailView(item: item), label: {
Text(item.timestamp!, formatter: itemFormatter)
})
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
In my case this behaviour was caused by .navigationViewStyle(StackNavigationViewStyle()).
Looks like it's a bug with NavigationView in SwiftUI 4 since it wasn't like this before.
If you are on iOS 16 use NavigationStack instead NavigationView. This fixed all my problems and I didn't even need to use the navigationViewStyle anymore.
NavigationView is deprecated in favor of NavigationStack.

SwiftUI: NavigationLink pops out immediately on WatchOS 8.1RC in Tabview

I have discovered a regression in watchOS 8.1RC with NavigationLink triggered from a TabView.
It's immediately dismissed.
It was working in watchOS 8.0 or in Simulator (watchOS 8.0).
Do you know a workaround ?
Thanks
Sample code:
import SwiftUI
#main
struct TestNavigationApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
}
}
}
struct ContentView: View {
var body: some View {
List {
NavigationLink(destination: ContentView1()) {
Text("To TabView")
}
}
}
}
struct ContentView1: View {
var body: some View {
TabView {
NavigationView {
NavigationLink(destination: ContentView2()) {
Text("To ContentView2")
}
}
VStack {
Text("Screen2")
}
}
}
}
struct ContentView2: View {
var body: some View {
Text("ContentView2")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm experiencing the same issue with watchOS 8.1 (and 8.3 beta) while it was working with previous watchOS versions.
We were able to get it working again by moving the NavigationView inside the TabView. This workaround isn't ideal at all but it does seem to work.
#State private var tabSelection = 1
var body: some Scene {
WindowGroup {
TabView(selection: $tabSelection) {
NavigationView {
// List goes here
}
.tag(1)
VStack(alignment: .center, spacing: 12, content: {
// content 2nd tab: we didn't have a list in the 2nd tab
})
.tag(2)
}
}
}
However, there are 2 things impacted with this fix:
I didn't get the navigationBarTitle working, so there won't be a title on top of the screen.
If you click on an item in the list, it will navigate to your page (as expected) but the TabView dots at the bottom of the screen will remain.

Make List Sections non-collapsible in SwiftUI when embedded into a NavigationView SwiftUI

When I embed a List grouped into Sections into a NavigationView the section headers become collapsible. I'd like to keep them non-collapsible, just like when the List is not embedded into the NavigationView.
My current code (with the NavigationView):
import SwiftUI
struct MyGroup {
var name:String, items:[String]
}
struct ContentView: View {
var groups : [MyGroup] = [
.init(name: "Animals", items: ["🐕","🐩","🐂","🐄","🐈","🦩","🐿","🐇"]),
.init(name: "Vehicles", items: ["🚕","🚗","🚃","🚂","🚟","🚤","🛥","⛵️"])]
var body: some View {
NavigationView {
VStack {
List {
ForEach(groups, id: \.self.name) { group in
Section(header: Text(group.name)) {
ForEach(group.items, id:\.self) { item in
Text(item)
}
}
}
}
}.navigationTitle("collections")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
It is default style applied, you can make it explicitly set for List like below (tested with Xcode 12 / iOS 14)
List {
ForEach(groups, id: \.self.name) { group in
Section(header: Text(group.name)) {
ForEach(group.items, id:\.self) { item in
Text(item)
}
}
}
}.listStyle(InsetGroupedListStyle()) // or GroupedListStyle
Just using the SidebarListStyle in listStyle modifier
.listStyle(SidebarListStyle())
In case you're stumbling on this... The issue doesn't have anything to do with being embedded in a NavigationView as the OP and #Danial mentioned. It's because it's embedded in the the VStack at the first level of the NavigationView in the example code. Seems like a SwiftUI bug to me.

SwiftUI Navigation Controller stuttering with two Navigationlinks per List row

I am trying to create two NavigationLinks in a repeating List. Each has a separate destination. The code all works fine until I imbed the call to the root view in a List/ForEach loop. At which point the navigation becomes very strange.
Try to click on either link and then click the back indicator at the top. It will go to one NavigationLink, and then the other. Sometimes in a different order, and sometimes it will auto-return from one of the links, and othertimes it won't open the second detail view until you return from the first detail view. It does this both in Preview, as well as if you build and run the application.
I have distilled down the code to the most basic below. If you comment the 2 lines as indicated in ContentView, you will then see correct behavior.
I am running Catalina 10.15.5, xCode 11.6, with the application target of IOS 13.6.
How can I modify the code, so that it will work with the List/ForEach loop?
import SwiftUI
struct DetailView1: View {
var body: some View {
HStack {
Text("Here is Detail View 1." )}
.foregroundColor(.green)
}
}
struct DetailView2: View {
var body: some View {
HStack {
Text( "Here is Detail View 2.") }
.foregroundColor(.red)
}
}
struct RootView: View {
var body: some View {
HStack {
VStack {
NavigationLink(destination: DetailView1())
{ VStack { Image(systemName: "ant.circle").resizable()
.frame(width:75, height:75)
.scaledToFit()
}.buttonStyle(PlainButtonStyle())
Text("Tap for Detail 1.")
.foregroundColor(.green)
}
}
NavigationLink(destination: DetailView2())
{ Text("Tap for Detail 2.")
.foregroundColor(.red)
}
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
// Comment the following line for correct behavior
List { ForEach(0..<3) {_ in
RootView()
// Comment the following line for correct behavior
} }
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ContentView()
.navigationBarTitle("Strange Behavior")
}
}
}
In your case both navigation links are activated at once user tap a row, to avoid this below is possible approach
Tested with Xcode 12 / iOS 14
The idea is to have one link which is activated programmatically and destination is selected dynamically depending on which button is clicked
struct RootView: View {
#State private var isFirst = false
#State private var isActive = false
var body: some View {
HStack {
VStack {
Button(action: {
self.isFirst = true
self.isActive = true
})
{ VStack { Image(systemName: "ant.circle").resizable()
.frame(width:75, height:75)
.scaledToFit()
}
Text("Tap for Detail 1.")
.foregroundColor(.green)
}
}
Button(action: {
self.isFirst = false
self.isActive = true
})
{ Text("Tap for Detail 2.")
.foregroundColor(.red)
}
.background(
NavigationLink(destination: self.destination(), isActive: $isActive) { EmptyView() }
)
}
.buttonStyle(PlainButtonStyle())
}
#ViewBuilder
private func destination() -> some View {
if isFirst {
DetailView1()
} else {
DetailView2()
}
}
}