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

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.

Related

Two views within a view, top with button and the lower not showing the firebase data

Simple project to be able to understand the concept.
I have two views UpperView and LowerView. The UpperView has a button, when clicked the button calls a ViewModel that fetches data from firebase. My problem is displaying the fetched data in the LowerView. I initialize the ViewModel in the LowerView so that I can access the fetched data through a #Published property but it doesn't work. It's a pretty simple case that I have built in order to understand the concept. Here is the code for UpperView, LowerView and the ViewModel. HomeView is the combination of the UpperView and the LowerView. It feels as if the data is loaded after the LowerView is displayed. All help will be appreciated!!
import Foundation
class MergeViewModel: ObservableObject {
#Published var clients: [Client] = [Client]()
func fetchAllClients() {
COLLECTION_CLIENTS.getDocuments { querySnapshot, error in
if let error = error {
print(error.localizedDescription)
return
}
guard let documents = querySnapshot?.documents else { return }
self.clients = documents.compactMap({ try? $0.data(as: Client.self)})
print(self.clients.count)
}
}
}
import SwiftUI
struct UpperView: View {
#ObservedObject var viewModel = MergeViewModel()
#State var numberOfClients: Int = 0
#State var buttonPressed: Int = 0
#State var clients: [Client] = [Client]()
var body: some View {
ZStack {
Color(.red)
VStack{
Text("This is UPPER VIEW ")
.foregroundColor(.white)
Text("We have \(numberOfClients) of clients!")
Text("Button pressed \(buttonPressed)")
Button(action: {
viewModel.fetchAllClients()
numberOfClients = viewModel.clients.count
buttonPressed += 1
}, label: {
Text("Press")
.frame(width: 100, height: 50)
.background(Color.white.opacity(0.50))
.cornerRadius(10)
})
}
}.ignoresSafeArea()
}
}
struct UpperView_Previews: PreviewProvider {
static var previews: some View {
UpperView()
}
}
import SwiftUI
struct LowerView: View {
#ObservedObject var viewModel = MergeViewModel()
var body: some View {
VStack {
Text("This is LOWER VIEW")
.foregroundColor(.black)
Text("\(viewModel.clients.count)")
.foregroundColor(.black)
List(viewModel.clients) { client in
Text(client.clientName)
.foregroundColor(.black)
}
}
}
}
struct LowerView_Previews: PreviewProvider {
static var previews: some View {
LowerView()
}
}
import SwiftUI
struct HomeView: View {
var body: some View {
NavigationView {
VStack {
UpperView()
LowerView()
Spacer()
}
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
Image("logo_silueta")
.resizable()
.scaledToFit()
.frame(width: 30)
Text("TheJump")
.font(.subheadline)
.foregroundColor(.gray.opacity(0.8))
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
AuthViewModel.shared.signOut()
}, label: {
Text("logout")
})
}
}
}
}
}
Thanks for your input!
Here is how it works correctly:
ViewModel
import Foundation
class MergeViewModel: ObservableObject {
#Published var clients: [Client] = [Client]()
init(){
fetchAllClients()
}
func fetchAllClients() {
COLLECTION_CLIENTS.getDocuments { querySnapshot, error in
if let error = error {
print(error.localizedDescription)
return
}
guard let documents = querySnapshot?.documents else { return }
self.clients = documents.compactMap({ try? $0.data(as: Client.self)})
print(self.clients.count)
}
}
}
UpperView
import SwiftUI
struct UpperView: View {
#ObservedObject var viewModel: MergeViewModel
#State var numberOfClients: Int = 0
#State var buttonPressed: Int = 0
#State var clients: [Client] = [Client]()
var body: some View {
ZStack {
Color(.red)
VStack{
Text("This is UPPER VIEW ")
.foregroundColor(.white)
Text("We have \(numberOfClients) of clients!")
Text("Button pressed \(buttonPressed)")
Button(action: {
viewModel.fetchAllClients()
numberOfClients = viewModel.clients.count
buttonPressed += 1
}, label: {
Text("Press")
.frame(width: 100, height: 50)
.background(Color.white.opacity(0.50))
.cornerRadius(10)
})
}
}.ignoresSafeArea()
}
}
struct UpperView_Previews: PreviewProvider {
static var previews: some View {
UpperView(viewModel: MergeViewModel())
}
}
LowerView
struct LowerView: View {
#ObservedObject var viewModel: MergeViewModel
var body: some View {
VStack {
Text("This is LOWER VIEW")
.foregroundColor(.black)
Text("\(viewModel.clients.count)")
.foregroundColor(.black)
List(viewModel.clients) { client in
Text(client.clientName)
.foregroundColor(.black)
}
}
}
}
struct LowerView_Previews: PreviewProvider {
static var previews: some View {
LowerView(viewModel: MergeViewModel())
}
}

EditButton bug with StackNavigationViewStyle

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

SwiftUI Let View disappear automatically

I have a view that is triggered by a button touch. It appears nicely, all good. Now I want the View to disappear automatically again after a few seconds.
The view should disappear automatically without having to hit the button again.
Below my test project
import SwiftUI
struct ContentView: View {
#State private var presentClipboardView = false
#State private var scale: CGFloat = 1.0
var body: some View {
VStack{
Button(action: {
let pasteboard = UIPasteboard()
pasteboard.string = "http://I_AM_A_URL.com"
withAnimation(.easeInOut(duration: 2)) {
self.presentClipboardView.toggle()
}
}, label: {
HStack {
Image(systemName: "list.dash")
.padding(.trailing)
VStack(alignment: .leading) {
Text("Open URL")
.font(.headline)
}
Spacer()
}
}
)
if(self.presentClipboardView){
LabelView()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct LabelView: View {
var body: some View {
Text("URL copied to clipboard!")
.padding(10)
.font(.title)
.foregroundColor(.white)
.background(RoundedRectangle(cornerRadius: 8).fill(Color.green).shadow(color: .gray, radius: 3))
}
}
Try this on LabelView()
LabelView().onAppear {
Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { timer in
withAnimation(.easeInOut(duration: 2)) {
self.presentClipboardView.toggle()
}
}
}
lets try
import SwiftUI
struct ContentView: View {
#State var flag = false
let time = 3.0
var body: some View {
VStack {
if flag {
DetailView(flag: $flag, showTime: time)
}
Button(action: {
self.flag.toggle()
}) {
Text("show for \(time.description) seconds")
}.disabled(flag)
}
}
}
struct DetailView: View {
#Binding var flag: Bool
let showTime: Double
var body: some View {
Text("Welcome").font(.largeTitle).foregroundColor(Color.orange)
.onAppear {
let _delay = RunLoop.SchedulerTimeType(.init(timeIntervalSinceNow: self.showTime))
RunLoop.main.schedule(after: _delay) {
self.flag.toggle()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Dismiss sheet SwiftUI

I'm trying to implement a dismiss button for my modal sheet as follows:
struct TestView: View {
#Environment(\.isPresented) var present
var body: some View {
Button("return") {
self.present?.value = false
}
}
}
struct DataTest : View {
#State var showModal: Bool = false
var modal: some View {
TestView()
}
var body: some View {
Button("Present") {
self.showModal = true
}.sheet(isPresented: $showModal) {
self.modal
}
}
}
But the return button when tapped does nothing. When the modal is displayed the following appears in the console:
[WindowServer] display_timer_callback: unexpected state (now:5fbd2efe5da4 < expected:5fbd2ff58e89)
If you force unwrap present you find that it is nil
How can I dismiss .sheet programmatically?
iOS 15+
Starting from iOS 15 we can use DismissAction that can be accessed as #Environment(\.dismiss).
There's no more need to use presentationMode.wrappedValue.dismiss().
struct SheetView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
Text("Sheet")
.toolbar {
Button("Done") {
dismiss()
}
}
}
}
}
Use presentationMode from the #Environment.
Beta 6
struct SomeView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("Ohay!")
Button("Close") {
self.presentationMode.wrappedValue.dismiss()
}
}
}
}
For me, beta 4 broke this method - using the Environment variable isPresented - of using a dismiss button. Here's what I do nowadays:
struct ContentView: View {
#State var showingModal = false
var body: some View {
Button(action: {
self.showingModal.toggle()
}) {
Text("Show Modal")
}
.sheet(
isPresented: $showingModal,
content: { ModalPopup(showingModal: self.$showingModal) }
)
}
}
And in your modal view:
struct ModalPopup : View {
#Binding var showingModal:Bool
var body: some View {
Button(action: {
self.showingModal = false
}) {
Text("Dismiss").frame(height: 60)
}
}
}
Apple recommend (in WWDC 2020 Data Essentials in SwiftUI) using #State and #Binding for this. They also place the isEditorPresented boolean and the sheet's data in the same EditorConfig struct that is declared using #State so it can be mutated, as follows:
import SwiftUI
struct Item: Identifiable {
let id = UUID()
let title: String
}
struct EditorConfig {
var isEditorPresented = false
var title = ""
var needsSave = false
mutating func present() {
isEditorPresented = true
title = ""
needsSave = false
}
mutating func dismiss(save: Bool = false) {
isEditorPresented = false
needsSave = save
}
}
struct ContentView: View {
#State var items = [Item]()
#State private var editorConfig = EditorConfig()
var body: some View {
NavigationView {
Form {
ForEach(items) { item in
Text(item.title)
}
}
.navigationTitle("Items")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: presentEditor) {
Label("Add Item", systemImage: "plus")
}
}
}
.sheet(isPresented: $editorConfig.isEditorPresented, onDismiss: {
if(editorConfig.needsSave) {
items.append(Item(title: editorConfig.title))
}
}) {
EditorView(editorConfig: $editorConfig)
}
}
}
func presentEditor() {
editorConfig.present()
}
}
struct EditorView: View {
#Binding var editorConfig: EditorConfig
var body: some View {
NavigationView {
Form {
TextField("Title", text:$editorConfig.title)
}
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(action: save) {
Text("Save")
}
.disabled(editorConfig.title.count == 0)
}
ToolbarItem(placement: .cancellationAction) {
Button(action: dismiss) {
Text("Dismiss")
}
}
}
}
}
func save() {
editorConfig.dismiss(save: true)
}
func dismiss() {
editorConfig.dismiss()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(items: [Item(title: "Banana"), Item(title: "Orange")])
}
}

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