EditButton bug with StackNavigationViewStyle - swiftui

when use StackNavigationViewStyle, EditButton doesn't work
var body: some View {
NavigationView {
List {
ForEach(books, id: \.self) { book in
....
}
.onDelete(perform: deleteBooks)
}
...
.navigationBarItems(leading: EditButton(), trailing: ...)
....
}
.navigationViewStyle(StackNavigationViewStyle())
}

// Press edit button first:
import SwiftUI
struct Order: Identifiable, Equatable {
let id = UUID()
}
struct ContentView: View {
#State private var orders = [Order(), Order(), Order()]
var body: some View {
NavigationView {
List {
ForEach(orders) { order in
NavigationLink(destination: DetailView(order: order, orders: $orders)) {
Text(order.id.uuidString)
}
}
.onDelete { indexSet in
orders.remove(atOffsets: indexSet)
}
}
.navigationBarItems( leading: EditButton())
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct DetailView: View {
let order: Order
#Binding var orders: [Order]
var body: some View {
if orders.contains(order) {
Text(order.id.uuidString)
} else {
Text("No selection")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

SwiftUI Half-swipe back from navigation causes error

I noticed issue in SwiftUI when using NavigationStack
Once I swipe-back on a half and revert it -> it stops working
Also I attached sample code if you want to try it
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
ListView()
}
}
}
struct ListView: View {
var body: some View {
List {
NavigationLink(destination: ViewA(viewModel: .init()), label: {
Text("A")
})
NavigationLink(destination: ViewB(), label: {
Text("B")
})
}
}
}
struct ViewA: View {
#StateObject var viewModel: Observed
var body: some View {
ZStack {
List {
Button(action: {
viewModel.action()
}, label: {
Text("label")
})
}
NavigationLink(isActive: $viewModel.shouldShowViewB, destination: {
ViewB()
}, label: {EmptyView()})
}
.navigationTitle("view a")
}
}
struct ViewB: View {
var body: some View {
List {
Button(action: {
print("actionb")
}, label: {
Text("labelb")
})
}
.navigationTitle("view b")
}
}
class Observed: ObservableObject {
#Published var shouldShowViewB = false
func action() {
print("action from model")
shouldShowViewB = true
}
}
Expected: whatever I do it should work as expected - when I tap it should open new view
Anyone else found this issue? How to fix it?
Issue 1 is you create the ObservedObject inside the NavigationLink with .init and then have a #StateObject declaration in the Subview ViewA(). That doesn't feel right. Create the Object with #StateObject in the parent view and pass it down.
Issue 2 is the new SwiftUI Navigation model, with NavigationLink)destination: label:) being deprecated. I adapted your code to the new navigation logic:
struct ContentView: View {
var body: some View {
NavigationStack {
ListView()
}
}
}
struct ListView: View {
#StateObject var viewModel = Observed() // create ObservedObject here
var body: some View {
List {
NavigationLink("A") {
ViewA(viewModel: viewModel) // pass down
}
NavigationLink("B") {
ViewB()
}
}
}
}
struct ViewA: View {
#ObservedObject var viewModel: Observed // passed down Object
var body: some View {
ZStack {
List {
Button(action: {
viewModel.action()
print(viewModel.shouldShowViewB)
}, label: {
Text("label")
})
}
.navigationDestination(isPresented: $viewModel.shouldShowViewB, destination: { ViewB() })
}
.navigationTitle("view a")
}
}
struct ViewB: View {
var body: some View {
List {
Button(action: {
print("actionb")
}, label: {
Text("labelb")
})
}
.navigationTitle("view b")
}
}
class Observed: ObservableObject {
#Published var shouldShowViewB = false
func action() {
print("action from model")
shouldShowViewB = true
}
}

My environmentObject isn't working.I tap on navigationLink and see nothing in there

My environmentObject isn't working.I tap on navigationLink and see nothing in there.
I change note but it does not get updated.I made viewModel and share data from it everywhere I need it
I made the second TextEditor to do changes to my notes, but I cannot see changes.I just want to write smith and data should be updated
So how can I fix that?
import SwiftUI
#main
struct WhatToDoAppApp: App {
#StateObject private var vm = NoteViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(vm)
}
}
}
//ContentView.swift
import SwiftUI
struct ContentView: View {
#EnvironmentObject var vm: NoteViewModel
#State private var showSheet = false
#State private var searchText = ""
var body: some View {
NavigationView {
List {
ForEach(vm.notes) { item in
NavigationLink(destination: NoteDetailView()) {
Text(item.task)
.lineLimit(1)
}
}
.onDelete(perform: vm.deleteTask)
.onMove(perform: vm.moveTask)
}
.searchable(text: $searchText) {
if !searchResult.isEmpty {
ForEach(searchResult) { item in
NavigationLink(destination: NoteDetailView()) {
Text(item.task)
.lineLimit(1)
}
}
}
}
.navigationBarTitle("Notes")
.safeAreaInset(edge: .bottom) {
Color.clear
.frame(maxHeight: 40)
.background(.gray.opacity(0.7))
HStack {
Spacer(minLength: 160)
Text("\(vm.notes.count) notes")
.foregroundColor(.black.opacity(0.3))
Spacer()
Button {
showSheet = true
} label: {
Image(systemName: "square")
.font(.largeTitle)
.padding(.trailing)
}
}
}
.sheet(isPresented: $showSheet) {
NoteView()
}
}
}
var searchResult: [ToDoItem] {
guard !searchText.isEmpty else { return vm.notes }
return vm.notes.filter { $0.task.contains(searchText) }
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.preferredColorScheme(.dark)
ContentView()
.preferredColorScheme(.light)
}
.environmentObject(NoteViewModel())
}
}
//NoteDetailView.swift
import SwiftUI
struct NoteDetailView: View {
#EnvironmentObject var vm: NoteViewModel
var body: some View {
VStack {
TextEditor(text: $vm.text)
Spacer()
}
}
}
struct NotedetailView_Previews: PreviewProvider {
static var previews: some View {
NoteDetailView().environmentObject(NoteViewModel())
}
}
//NoteView.swift
import SwiftUI
struct NoteView: View {
// #State private var text = ""
#EnvironmentObject var vm: NoteViewModel
#Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
VStack {
TextEditor(text: $vm.text)
}
.padding()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
addTask()
dismiss()
vm.text = ""
}, label: {
Text("Done")
.font(.system(size: 25))
.foregroundColor(.accentColor)
})
}
}
}
}
func addTask() {
vm.add(ToDoItem(task: vm.text))
}
}
struct NoteView_Previews: PreviewProvider {
static var previews: some View {
NoteView()
.environmentObject(NoteViewModel())
}
}
import Foundation
struct ToDoItem: Identifiable, Codable {
var id = UUID()
var task : String
}
class NoteViewModel: ObservableObject {
#Published var notes = [ToDoItem]()
#Published var text = ""
let saveKey = "SavedKey"
init() {
if let data = UserDefaults.standard.data(forKey: saveKey) {
if let decoded = try? JSONDecoder().decode([ToDoItem].self, from: data) {
notes = decoded
return
}
}
notes = []
}
private func save() {
if let encoded = try? JSONEncoder().encode(notes) {
UserDefaults.standard.set(encoded, forKey: saveKey)
}
}
func add(_ note: ToDoItem) {
notes.append(note)
save()
}
func deleteTask(indexSet: IndexSet) {
indexSet.forEach { index in
self.notes.remove(at: index)
save()
}
}
}
The detail view should be a #Binding, and you can use the array that you have in the viewModel as an Bindable List here the fixes:
List {
ForEach($vm.notes) { $item in
NavigationLink(item.task, destination: NoteDetailView(note: $item))
}
The detail view should look like this:
struct NoteDetailView: View {
#Binding var note: ToDoItem
#EnvironmentObject var vm: NoteViewModel
var body: some View {
VStack {
TextEditor(text: $note.task)
Spacer()
}
.onDisappear {
vm.save()
}
}}
This way every time the user updates and closes the modal, the list will be saved.

Trouble using #environmentObject and #StateObject

I am creating my first app and I am having trouble using #EnvironmentObject and #StateObject. When I run the simulator it opens my ContentView(). I am trying to get it to where it opens the MainView(), which has a tabView. And now the tabView is not showing my ContentView(). I am assuming I have the #EnvironmentObject and #StateObject in the wrong places. In my ContentView it shows a list and addButton. I am basically trying to have a program that is updated by the user by filling out a form.
Here is the Main Method
import SwiftUI
#main
struct Location_ScoutApp: App {
#StateObject var listViewModels: ListViewModel = ListViewModel()
var body: some Scene {
WindowGroup {
// MainView(){
ContentView()
.environmentObject(listViewModels)
//}
}
}
}
Here is my MainView.
import SwiftUI
struct MainView: View {
var body: some View {
TabView {
MapView()
.tabItem {
Label("Map", systemImage: "map.circle")
}
// this is where i am having trouble.
ContentView()
.tabItem {
Label("Explore", systemImage: "magnifyingglass")
}
ProfileView()
.tabItem {
Label("Profile", systemImage: "person.crop.circle")
}
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
Here is my ContentView:
import SwiftUI
struct ContentView: View {
#EnvironmentObject var listViewModel: ListViewModel
var body: some View {
List {
ForEach(listViewModel.items) { item in
// this is where i am getting my new error.
ListRowView(item: item)
.onTapGesture {
listViewModel.updateItem(item: item)
}
}
}
.navigationTitle("Explore")
.navigationBarItems(
leading: EditButton(),
trailing:
NavigationLink("Add", destination: addALandmarkForm()))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ContentView()
}
.environmentObject(ListViewModel())
}
}
Without a reproducible example, it's hard to debug. This code, however, which is based on yours, works fine. Perhaps you can find the difference between your implementation and mine:
struct Item : Identifiable {
var id = UUID()
var title : String
}
class ListViewModel: ObservableObject {
#Published var items : [Item] = [.init(title: "Test 1"),.init(title: "Test 2")]
}
#main
struct Location_ScoutApp: App {
#StateObject var listViewModels: ListViewModel = ListViewModel()
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(listViewModels)
}
}
}
struct MainView: View {
var body: some View {
TabView {
Text("Map")
.tabItem {
Label("Map", systemImage: "map.circle")
}
ContentView()
.tabItem {
Label("Explore", systemImage: "magnifyingglass")
}
Text("profile")
.tabItem {
Label("Profile", systemImage: "person.crop.circle")
}
}
}
}
struct ContentView: View {
#EnvironmentObject var listViewModel: ListViewModel
var body: some View {
NavigationView {
List {
ForEach(listViewModel.items) { item in
Text(item.title)
}
}
.navigationTitle("Explore")
}
}
}

HStack NavigationLink in Form tappable area small

I would like to be able to tap the entire row in this form to navigate to the next view; what am I doing wrong? It only allows me to navigate if I tap the symbol at the end.
This code should compile and run in your simulator.
Thanks.
import SwiftUI
struct SwiftUIView: View {
let selectedTags = ["A", "B", "C"]
var body: some View {
NavigationView {
Form {
NavigationLink(destination: DetailView()) {
ScrollView(.horizontal) {
HStack {
ForEach(selectedTags, id: \.self) { tag in
Text(tag)
}
}
}
}
}
}
}
}
struct DetailView: View {
var body: some View {
Text("Welcome")
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
copy - paste - run
import SwiftUI
struct ContentView: View {
let selectedTags = ["A", "B", "C"]
#State var active = false
var body: some View {
NavigationView {
Form {
NavigationLink( destination: DetailView(), isActive: $active) {
ScrollView(.horizontal) {
HStack {
ForEach(selectedTags, id: \.self) { tag in
Text(tag)
}
}
}.onTapGesture {
self.active.toggle()
}
}
}
}
}
}
struct DetailView: View {
var body: some View {
Text("Welcome")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Save selected item in List

This looks like a very simple thing, but I can't figure out how to do this:
I have a List embedded in a NavigationView, containing a NavigationLink to view the detail of the item.
I have a save bar button where I would like to save the selected item. But how can I access the selected item?
It isn't visible in the button's action closure.
struct ItemList : View {
#EnvironmentObject var items: ItemsModel
var body: some View {
NavigationView {
List(items) { item in
NavigationLink(destination: ItemDetail(item: item)) {
Text(item.name)
}
}
.navigationBarTitle(Text("Item"))
.navigationBarItems(trailing: Button(action: {
self.save(/*item: item */) // How can I access item here?
}, label: {
Text("Save")
}))
}
}
func save(item: Item) {
print("Saving...")
}
}
Navigation links are not obligatory to accomplish this.
import SwiftUI
struct ContentView: View {
struct Ocean: Identifiable, Hashable {
let name: String
var id: Self { self }
}
private var oceans = [
Ocean(name: "Pacific"),
Ocean(name: "Atlantic"),
Ocean(name: "Indian"),
Ocean(name: "Southern"),
Ocean(name: "Arctic")
]
#State private var selectedOceans = [Ocean]()
#State private var multiSelection = Set<Ocean.ID>()
var body: some View {
VStack {
Text("Oceans")
List(oceans, selection: $multiSelection) {
Text($0.name)
}
.navigationTitle("Oceans")
.environment(\.editMode, .constant(.active))
.onTapGesture {
// Walkaround: try how it works without `asyncAfter()`
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05, execute: {
selectedOceans = Array(multiSelection)
print(selectedOceans)
})
}
Divider()
Text("Selected oceans")
List(selectedOceans, selection: $multiSelection) {
Text($0.name)
}
}
Text("\(multiSelection.count) selections")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}