It does not read that the setting has changed - swiftui

I have a problem I change the "isDynamic" setting in the "SettingView" and I exit the setting window and the "SongbookView" does not register that the setting has changed. I want to change the search engine depending on what option is selected in the settings. What is the cause of this situation?
SongbookView:
import CoreData
import SwiftUI
struct SongbookView: View {
#State var searchText: String = ""
#State var isSettings: Bool
#ObservedObject var userSettings: UserSettings = UserSettings()
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(
entity: Song.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Song.number, ascending: true)]
) var songs: FetchedResults<Song>
var body: some View {
NavigationView{
VStack{
if userSettings.isDynamic == false {
SearchBar(text: $searchText)
} else {
DynamicSearchBar(text: $searchText)
}
List(songs.filter({searchText.isEmpty ? true : removeNumber(str: $0.content!.lowercased()).contains(searchText.lowercased()) || String($0.number).contains(searchText)}), id:\.objectID) { song in
NavigationLink(destination: DetailView(song: song, isSelected: song.favorite)) {
HStack{
Text("\(String(song.number)). ") .font(.headline) + Text(song.title ?? "Brak tytułu")
if song.favorite {
Spacer()
Image(systemName: "heart.fill")
.accessibility(label: Text("To jest ulubiona pieśń"))
.foregroundColor(.red)
}
}.lineLimit(1)
}
}.id(UUID())
.listStyle(InsetListStyle())
}
.padding(.top, 10)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
HStack{
Text(String(self.userSettings.isDynamic))
Spacer()
Text("Śpiewnik")
.font(.system(size: 20))
.bold()
Spacer()
Button(action: {
isSettings.toggle()
print(userSettings.isDynamic)
}) {
Image(systemName: "gearshape")
.resizable()
.frame(width: 16.0, height: 16.0)
}
.sheet(isPresented: $isSettings) {
SettingView(isPresented: $isSettings)
}
}
}
}
}
}
func removeNumber(str: String) -> String {
var result = str
let vowels: Set<Character> = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
result.removeAll(where: { vowels.contains($0) })
return result
}
}
SettingView:
import SwiftUI
struct SettingView: View {
#ObservedObject var userSettings = UserSettings()
#Binding var isPresented: Bool
var body: some View {
NavigationView {
Form {
Toggle("Dynamiczna wyszukiwarka", isOn: $userSettings.isDynamic)
.onChange(of: userSettings.isDynamic) { value in
print(value)
}
Button(action: {
print(userSettings.isDynamic)
isPresented = false
}) {
Text("Test")
}
}
.navigationBarTitle("Ustawienia")
}
}
}
UserSettings:
import Foundation
import Combine
class UserSettings: ObservableObject {
#Published var isDynamic: Bool {
didSet {
UserDefaults.standard.set(isDynamic, forKey: "isSearchDynamic")
}
}
init() {
self.isDynamic = UserDefaults.standard.object(forKey: "isSearchDynamic") as? Bool ?? false
}
}

You're using two different instances of UserSettings. When you update isDynamic on one of those instances, the other, even though it has a reference to UserDefaults has no reason to know that it needs to update.
The easiest solution here is to share a single instance of UserSettings:
struct SongbookView: View {
#State var searchText: String = ""
#State var isSettings: Bool
#ObservedObject var userSettings: UserSettings = UserSettings()
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(
entity: Song.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Song.number, ascending: true)]
) var songs: FetchedResults<Song>
var body: some View {
//...
.sheet(isPresented: $isSettings) {
SettingView(userSettings: userSettings,isPresented: $isSettings)
}
//...
}
}
struct SettingView: View {
#ObservedObject var userSettings : UserSettings
#Binding var isPresented: Bool
var body: some View {
//...
}
}
You could also look into property wrappers like #AppStorage (https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-appstorage-property-wrapper) that actually update dynamically when the UserDefaults values change.

Related

Unwrapping Optional Entity

Let's say that Item is a CoreData entity:
struct ItemDetailView: View {
#Binding var item: Item?
#Binding var isEditing: Bool
var body: some View{
if isEditing {
TextField( "Name", text: Binding($item.data)! )
}else{
Text(item!.data!)
}
}
}
Error: Value of optional type 'Item?' must be unwrapped to refer to member 'data' of wrapped base type 'Item'
Edit All the code:
import SwiftUI
import CoreData
struct ItemDetailView: View {
#Binding var item: Item?
#Binding var isEditing: Bool
var body: some View{
if isEditing {
TextField( "Name", text: Binding($item.data)! )
}else{
Text(item!.data!)
}
}
}
struct ItemEditorView: View {
#Environment(\.managedObjectContext) private var viewContext
#Environment(\.dismiss) private var dismiss
let isNew: Bool
#State var isEditing: Bool = false
#State var item: Item?
#Binding var newItem: Item?
var body: some View {
if isNew {
}
NavigationView{
ItemDetailView(item: isNew ? $newItem : $item, isEditing: $isEditing)
.toolbar {
ToolbarItem {
Button(isNew ? "Add" : (isEditing ? "Done" : "Edit")) {
//TBI
if isNew {
}
}
}
ToolbarItem(placement:.cancellationAction){
Button("Cancel"){
dismiss()
}
}
}
.navigationTitle("Item Editor")
}
}
}
struct ItemsListView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.data, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
#State var presentNewItemEditorView = false
#State var newItem: Item?
var body: some View {
NavigationView {
VStack{
Text(newItem?.data ?? "nil")
List {
ForEach(items){ item in
NavigationLink(item.data!, destination: ItemEditorView(isNew: false, item:item, newItem: $newItem))
}
}
}
.fullScreenCover(isPresented: $presentNewItemEditorView, content: {
ItemEditorView(isNew: true, isEditing: true, newItem: $newItem)
})
.navigationTitle("Main")
.toolbar {
ToolbarItem {
Button("New goal"){
presentNewItemEditorView = true
}
}
}
.task {
newItem = Item(context: viewContext)
newItem!.data = "New item text"
}
}
}
}
I it unclear what are you trying to achieve. Here are options that will compile.
Value type
struct Item {
var data: String
}
struct ItemDetailView: View {
#Binding var item: Item
#Binding var isEditing: Bool
var body: some View{
if isEditing {
TextField( "Name", text: Binding(projectedValue: $item.data) )
}else{
Text(item.data)
}
}
}
Reference type and value type
class Item {
var data: String? = ""
}
struct ItemDetailView: View {
#Binding var item: Item? // Has no sense, because #Binding does't work with clsses!
#Binding var isEditing: Bool
var body: some View{
if isEditing {
TextField("Name", text: Binding(get: {
item!.data!
}, set: { value in
item?.data = value
}))
} else {
Text(item!.data!)
}
}
}
Implementation by Conny Wals sugested by #vadian
import CoreData
class EditViewModel {
/// **new** instance of item to edit. Don't edit the item, if any, that we pass to VM
var item: Item4
/// Context to use in the isolated changes
let context: NSManagedObjectContext
/// Pass an item if you want to edit one, or nil to generate one
init(item: Item4? = nil){
self.context = PersistenceController.shared.childViewContext()
if let item = item {
self.item = PersistenceController.shared.copyForEditing(of: item, in: context)
} else {
self.item = PersistenceController.shared.newTemporaryInstance(in: context)
}
}
func persist(){
PersistenceController.shared.persist(item)
}
}
import SwiftUI
struct EditItemView: View {
#State var viewModel: EditViewModel
#Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
VStack{
TextField( "Item Name", text: Binding(get: {viewModel.item.name ?? ""}, set: {viewModel.item.name = $0}))
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
}
.navigationTitle("Edit Item")
.navigationBarItems(leading: Button("Cancel"){
dismiss()
}, trailing: Button("Save"){
viewModel.persist()
dismiss()
})
}
}
}
struct ItemsView04: View {
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item3.name, ascending: true)],
animation: .default)
private var items: FetchedResults<Item4>
#State var editItemViewIsPresent = false
var body: some View {
NavigationView{
List {
ForEach(items){ item in
NavigationLink(item.name ?? ""){
EditItemView(viewModel: EditViewModel(item: item))
}
}
}
.toolbar {
ToolbarItem {
Button {
editItemViewIsPresent = true
}label:{
Text("+")
}
}
}
.fullScreenCover(isPresented: $editItemViewIsPresent){
EditItemView(viewModel: EditViewModel())
}
}
}
}

Get PDF-Scanner in fullscreen in a sheet in SwiftUI

I am working on a PDF-Scanner and want to realize it with SwiftUI. I know that I have to work with UIViewRepresentable to get this done.
So the user should open the Camera (to scan the document) in a Sheetview, but the camera doesn't open. If I put it in a Navigationview, it works fine.
My questions are:
Is it possible to open the camera in Sheetviews (fullscreen). Or is it possible put the CameraView in a limited frame in the SheetView to get this done.
Thanks a lot
import SwiftUI
import VisionKit
import PDFKit
import UIKit
#main
struct testetApp: App {
var body: some Scene {
WindowGroup {
ContentView(scannerModel: ScannerModel())
}
}
}
struct ContentView: View {
#State var showNextView = false
#StateObject var vm = CoreDataRelationshipViewModel()
#ObservedObject var scannerModel: ScannerModel
var body: some View {
NavigationView {
VStack {
Button {
showNextView.toggle()
} label: {
Text("via Sheet")
}.sheet(isPresented: $showNextView) {
ContentView2(scannerModel: scannerModel, showNextView: $showNextView).environment(\.managedObjectContext, vm.mangager.container.viewContext)
}.padding()
NavigationLink("via Navigationlink", destination: ContentView3(scannerModel: scannerModel))
}
.padding()
}
}
}
struct ContentView2: View {
#StateObject var vm = CoreDataRelationshipViewModel()
#ObservedObject var scannerModel: ScannerModel
#State var files : [String] = []
#State var PDFview = false
#State var addDoc = true
#Binding var showNextView: Bool
var body: some View {
ZStack{
NavigationView{
VStack{
VStack {
NavigationLink(destination: ScanView(files: $files, scannerModel: scannerModel, vm: vm)){
VStack{
Image(systemName: "plus").font(.largeTitle).padding(.bottom)
Text("Scan Document").font(.caption)
}
}
}
}
}
}
}
}
struct ContentView3: View {
#StateObject var vm = CoreDataRelationshipViewModel()
#ObservedObject var scannerModel: ScannerModel
#State var files : [String] = []
#State var PDFview = false
#State var addDoc = true
var body: some View {
ZStack{
VStack{
VStack {
NavigationLink(destination: ScanView(files: $files, scannerModel: scannerModel, vm: vm)){
VStack{
Image(systemName: "plus").font(.largeTitle).padding(.bottom)
Text("Scan Document").font(.caption)
}
}
}
}
}
}
}
struct ScanView: View{
#Environment(\.presentationMode) var mode
#State var pdfName = ""
#State var addDoc = true
#Binding var files : [String]
#ObservedObject var scannerModel: ScannerModel
#ObservedObject var vm: CoreDataRelationshipViewModel
var body: some View{
ZStack{
VStack{
if let error = scannerModel.errorMessage {
Text(error)
} else {
ForEach(scannerModel.imageArray, id: \.self) { image in
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit).contextMenu {
Button {
let items = [image]
let ac = UIActivityViewController(activityItems: items, applicationActivities: nil)
UIApplication.shared.windows.filter({$0.isKeyWindow}).first?.rootViewController?.present(ac, animated: true)
} label: {
Label("share Document", systemImage: "square.and.arrow.up")
}
Divider()
Button {
scannerModel.removeImage(image: image)
} label: {
Label("delete document", systemImage: "delete.left")
}
}
}
}
}.navigationBarItems( trailing: Button(action:{
vm.createSavedPDF(a: scannerModel.imageArray)
guard pdfName.count > 0 else{
return
}
self.mode.wrappedValue.dismiss()
saveDocument(a: scannerModel.imageArray, pdfName: pdfName)
scannerModel.imageArray.removeAll()
files = getDocumentsDirectory()
}){
Text("Save")
})
if(addDoc){
VStack{
VStack{
Button(action: {
UIApplication.shared.windows.filter({$0.isKeyWindow}).first?.rootViewController?.present(scannerModel.getDocumentCameraViewController(), animated: true, completion: nil)
addDoc = false
}){
VStack {
Image(systemName: "camera").font(.title)
Text("Scan Doc").font(.title)
}
}
}.padding()
}
}
}
}
}
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
pdfView.pageBreakMargins = UIEdgeInsets(top: 50, left: 30, bottom: 50, right:30)
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
}
}
struct ActivityViewController: UIViewControllerRepresentable {
var activityItems: [Any]
var applicationActivities: [UIActivity]? = nil
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}
}

SwiftUI- passing #State variables to multiple views trouble

Im trying to make an app similar to iphones reminders app in terms of UI. I have a view where I have a list that I can add and delete items from, I also have a view for adding an item that allows me to name the item and select some options, and I have another view for when an item in the list is selected and I want to be able to show the name and options I made but it doesn't display. The code for the list view
struct DotView: View {
enum ActiveSheet: String, Identifiable {
case SwiftUIView, EditView
var id: String {
return self.rawValue
}
}
#EnvironmentObject var listViewModel: ListViewModel
#AppStorage("dotApiKey") var selectedDotApi: String = ""
#State var dotName:String = ""
#State var dotNumber:String = ""
#State var selection:String = ""
#State var triggerSelection:String = ""
#State var searchText:String = ""
#State var plugUsername:String = ""
#State var plugPassword:String = ""
#State var toggleNotification:Bool
#State var activeSheet : ActiveSheet? = nil
#Binding var show : Bool
var body: some View {
ZStack{
HStack {
EditButton()
.padding(.leading)
Spacer()
Button(action: {
self.activeSheet = .SwiftUIView
}, label: {
Text("Add")
})
.padding(.trailing)
}
ZStack {
List {
ForEach(listViewModel.dotitems, id:\.dotId){ dotitem in
Button(action: { self.activeSheet = .EditView }, label: {
DotItemListView(dotitem: dotitem)
})
}
.onDelete(perform: listViewModel.deleteItem)
.onMove(perform: listViewModel.moveItem)
.listRowBackground(Color("textBG"))
}.listStyle(PlainListStyle())
.background(Color("textBG"))
.frame(height: 300)
.cornerRadius(10)
.padding(.horizontal)
}
}
.sheet(item: $activeSheet){ sheet in
switch sheet {
case .SwiftUIView:
SwiftUIView(dotName: dotName, dotNumber: dotNumber, selection: selection, triggerSelection: triggerSelection, searchText: searchText, plugUsername: plugUsername, plugPassword: plugPassword, show: $show)
case .EditView:
EditView(show:$show)
}
}
When I add an item to the list it shows this in each row
struct DotItemListView:View {
let dotitem: DotItem
var body: some View{
HStack {
Text(dotitem.dotName)
Spacer()
Text(dotitem.selection)
Spacer()
Text(dotitem.dotNumber)
Spacer()
}
}
}
This is how I'm adding each item to the list
struct DotItem:Equatable, Codable{
var dotId = UUID().uuidString
let dotName:String
let dotNumber:String
let selection:String
}
class ListViewModel: ObservableObject {
#Published var dotitems: [DotItem] = [] {
didSet {
saveItem()
}
}
let dotitemsKey: String = "dotitems_list"
init() {
getDotItems()
}
func getDotItems() {
guard
let data = UserDefaults.standard.data(forKey: dotitemsKey),
let savedDotItems = try? JSONDecoder().decode([DotItem].self, from: data)
else { return }
self.dotitems = savedDotItems
}
func deleteItem(indexSet: IndexSet){
dotitems.remove(atOffsets: indexSet)
}
func moveItem(from: IndexSet, to: Int){
dotitems.move(fromOffsets: from, toOffset: to)
}
func addItem(dotName: String, dotNumber: String, selection: String){
let newItem = DotItem(dotName: dotName, dotNumber: dotNumber, selection: selection)
dotitems.append(newItem)
print(newItem)
}
func saveItem() {
if let encodedData = try? JSONEncoder().encode(dotitems) {
UserDefaults.standard.set(encodedData, forKey: dotitemsKey)
}
}
}
This is the view that I'm entering the data for each item
struct SwiftUIView: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var listViewModel: ListViewModel
#AppStorage("dotApiKey") var selectedDotApi: String = ""
#State var dotName:String
#State var dotNumber:String
#State var selection:String
#State var triggerSelection:String
#State var selectedColor = Color.black
#State var searchText:String
#State var plugUsername:String
#State var plugPassword:String
#ObservedObject var vm = getDeviceNames()
#State var triggerDot:Bool = false
#State var toggleOnOff:Bool = false
#State var toggleLightColor:Bool = false
#State var isSearching:Bool = false
#StateObject var camera = CameraModel()
#Binding var show : Bool
var body: some View {
NavigationView {
Form {
Section(header: Text("Info")) {
TextField("Name", text: $dotName)
TextField("Number", text: $dotNumber)
Picker(selection: $selection, label: Text("Discover Plug")) {
ForEach(vm.dataSet, id:\.self) { item in
Text(item.Device).tag(item.Device)
}
}
Toggle(isOn: $isSearching, label: {
Text("Have smart plugs?")
})
if isSearching {
HStack{
Text("Casa")
Spacer()
Button(action: {sendPlugDict()}, label: {
Text("Login")
})
}
TextField("Username", text: $plugUsername)
TextField("Password", text: $plugPassword)
}
}
Section {
Toggle(isOn: $toggleOnOff, label: {
Text("On/Off")
})
}
Section {
Toggle(isOn: $toggleLightColor, label: {
Text("Light Color")
})
if toggleLightColor {
ColorPicker("Choose Light Color", selection: $selectedColor)
}
}
Section {
if listViewModel.dotitems.isEmpty == false {
Toggle(isOn: $triggerDot, label: {
Text("Add a DOT to trigger")
})
if triggerDot {
Picker(selection: $triggerSelection, label: Text("Select DOT")) {
ForEach(listViewModel.dotitems, id:\.dotId){ dotitem in
DotItemListView(dotitem: dotitem)
}
}
}
}
}
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
and this is the view that I'm trying to show the data when any list item is selected which is basically the same as above except in a few places
struct EditView: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var listViewModel: ListViewModel
#ObservedObject var vm = getDeviceNames()
#State var dataSet = [Result]()
#State var dotName:String = ""
#State var dotNumber:String = ""
#State var selection:String = ""
#State var triggerSelection:String = ""
#State var selectedColor = Color.black
#State var searchText:String = ""
#State var plugUsername:String = ""
#State var plugPassword:String = ""
#State var triggerDot:Bool = false
#State var toggleOnOff:Bool = false
#State var toggleLightColor:Bool = false
#State var isSearching:Bool = false
#Binding var show : Bool
var body: some View {
NavigationView {
Form {
Section(header: Text("Info")) {
TextField(dotName, text: $dotName)
TextField(dotNumber, text: $dotNumber)
Picker(selection: $selection, label: Text("Discorver Plug")) {
ForEach(dataSet, id:\.self) { item in
Text(item.Device).tag(item.Device)
}
}
Toggle(isOn: $isSearching, label: {
Text("Have smart plugs?")
})
if isSearching {
HStack{
Text("Casa")
Spacer()
Button(action: {
SwiftUIView(dotName: dotName, dotNumber: dotNumber, selection: selection, triggerSelection: triggerSelection, searchText: searchText, plugUsername: plugUsername, plugPassword: plugPassword, show: $show).sendPlugDict()}, label: {
Text("Login")
})
}
TextField("Username", text: $plugUsername)
TextField("Password", text: $plugPassword)
}
}
Section {
Toggle(isOn: $toggleOnOff, label: {
Text("On/Off")
})
}
Section {
Toggle(isOn: $toggleLightColor, label: {
Text("Light Color")
})
if toggleLightColor {
ColorPicker("Choose Light Color", selection: $selectedColor)
}
}
Section {
if listViewModel.dotitems.isEmpty == false {
Toggle(isOn: $triggerDot, label: {
Text("Add a DOT to trigger")
})
if triggerDot {
Picker(selection: $triggerSelection, label: Text("Select DOT")) {
ForEach(listViewModel.dotitems, id:\.dotId){ dotitem in
DotItemListView(dotitem: dotitem)
}
}
}
}
}
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
I know this is a lot to go through but any help would be greatly appreciated
use #Binding on the view that you want to pass state to .
View1 :
#State var a = true
View2(a: $a)
View2 :
#Binding var a : Bool
for passing data globally use #EnvoirmentObject :
example :
#main
struct YourApp: App {
#StateObject var session = Session()
var body: some Scene {
WindowGroup {
SplashView()
.environmentObject(session)
}
}
}
Session :
class Session: ObservableObject {
/// user signin state
#Published var isSignedIn : Bool = false
}
View1 ,View2 , View3 .... :
struct SplashView: View {
#EnvironmentObject var session : Session
var body: some View {
VStack{
SignInView()
.opacity(session.isSignedIn ? 0:1)
}
.background(Color.background.ignoresSafeArea())
}
}

Wrong List Item Selected in NavigationLink

I'm trying to build out a simple navigation where you can click on items in a link and pop back to the root controller from a sheet view. As you can see from the video below, when I tap on an item in the list, the wrong item is loaded (there's an offset between the row I click and the one that gets highlighted and loaded).
I also get the error SwiftUI encountered an issue when pushing aNavigationLink. Please file a bug.
Here's all my code:
import SwiftUI
struct ContentView: View {
#State var rootIsActive:Bool = false
var body: some View {
NavigationView{
AllProjectView(rootIsActive: self.rootIsActive)
}
.navigationBarTitle("Root")
.navigationViewStyle(StackNavigationViewStyle())
.environment(\.rootPresentationMode, self.$rootIsActive)
}
}
struct AllProjectView: View {
#State var rootIsActive:Bool = false
#State var projects: [String] = ["1", "2", "3"]
var body: some View{
List{
ForEach(projects.indices, id: \.self){ idx in
ProjectItem(name: self.$projects[idx], rootIsActive: self.$rootIsActive)
}
}.navigationBarTitle("All Projects")
}
}
struct ProjectItem: View{
#Binding var name: String
#Binding var rootIsActive: Bool
init(name: Binding<String>, rootIsActive: Binding<Bool>){
self._name = name
self._rootIsActive = rootIsActive
}
var body: some View{
NavigationLink(
destination: ProjectView(name: self.name),
isActive: self.$rootIsActive){
Text(name)
}
.isDetailLink(false)
.padding()
}
}
struct ProjectView: View {
var name: String
#State var isShowingSheet: Bool = false
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
#Environment(\.rootPresentationMode) private var rootPresentationMode: Binding<RootPresentationMode>
var body: some View{
VStack{
Text(name)
Button("Show Sheet"){
self.isShowingSheet = true
}
}
.sheet(isPresented: $isShowingSheet){
Button("return to root"){
self.isShowingSheet = false
print("pop view")
self.presentationMode.wrappedValue.dismiss()
print("pop root")
self.rootPresentationMode.wrappedValue.dismiss()
}
}
.navigationBarTitle("Project View")
}
}
// from https://stackoverflow.com/a/61926030/1720985
struct RootPresentationModeKey: EnvironmentKey {
static let defaultValue: Binding<RootPresentationMode> = .constant(RootPresentationMode())
}
extension EnvironmentValues {
var rootPresentationMode: Binding<RootPresentationMode> {
get { return self[RootPresentationModeKey.self] }
set { self[RootPresentationModeKey.self] = newValue }
}
}
typealias RootPresentationMode = Bool
extension RootPresentationMode {
public mutating func dismiss() {
self.toggle()
}
}
You only have one isRootActive variable that you're using. And, it's getting repeated for each item on the list. So, as soon as any item on the list is tapped, the isActive property for each NavigationLink turns to true.
Beyond that, your isRootActive isn't actually doing anything right now, since your "Return to root" button already does this:
self.isShowingSheet = false
self.presentationMode.wrappedValue.dismiss()
At that point, there's nothing more to dismiss -- it's already back at the root view.
My removing all of the root and isActive stuff, you get this:
struct ContentView: View {
var body: some View {
NavigationView{
AllProjectView()
}
.navigationBarTitle("Root")
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct AllProjectView: View {
#State var projects: [String] = ["1", "2", "3"]
var body: some View{
List{
ForEach(projects.indices, id: \.self){ idx in
ProjectItem(name: self.$projects[idx])
}
}.navigationBarTitle("All Projects")
}
}
struct ProjectItem: View{
#Binding var name: String
var body: some View{
NavigationLink(
destination: ProjectView(name: self.name)
){
Text(name)
}
.isDetailLink(false)
.padding()
}
}
struct ProjectView: View {
var name: String
#State var isShowingSheet: Bool = false
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var body: some View{
VStack{
Text(name)
Button("Show Sheet"){
self.isShowingSheet = true
}
}
.sheet(isPresented: $isShowingSheet){
Button("return to root"){
self.isShowingSheet = false
print("pop view")
self.presentationMode.wrappedValue.dismiss()
}
}
.navigationBarTitle("Project View")
}
}
If you had an additional view in the stack, you would need a way to keep track of if the root were active. I've used a custom binding here that converts an optional String representing the project's name to a Bool value that gets passed down the view hierarchy:
struct ContentView: View {
var body: some View {
NavigationView{
AllProjectView()
}
.navigationBarTitle("Root")
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct AllProjectView: View {
#State var projects: [String] = ["1", "2", "3"]
#State var activeProject : String?
func activeBindingForProject(name : String) -> Binding<Bool> {
.init {
name == activeProject
} set: { newValue in
activeProject = newValue ? name : nil
}
}
var body: some View{
List{
ForEach(projects.indices, id: \.self){ idx in
InterimProjectView(name: self.$projects[idx],
isActive: activeBindingForProject(name: self.projects[idx]))
}
}.navigationBarTitle("All Projects")
}
}
struct InterimProjectView: View {
#Binding var name : String
#Binding var isActive : Bool
var body : some View {
NavigationLink(destination: ProjectItem(name: $name, isActive: $isActive),
isActive: $isActive) {
Text("Next : \(isActive ? "true" : "false")")
}
}
}
struct ProjectItem: View {
#Binding var name: String
#Binding var isActive: Bool
var body: some View{
NavigationLink(
destination: ProjectView(name: self.name, isActive: $isActive)
){
Text(name)
}
.isDetailLink(false)
.padding()
}
}
struct ProjectView: View {
var name: String
#Binding var isActive : Bool
#State var isShowingSheet: Bool = false
var body: some View{
VStack{
Text(name)
Button("Show Sheet"){
self.isShowingSheet = true
}
}
.sheet(isPresented: $isShowingSheet){
Button("return to root"){
self.isShowingSheet = false
print("pop root")
self.isActive.toggle()
}
}
.navigationBarTitle("Project View")
}
}

How is it possible to work with a struct inside of a struct in SwiftUI in different Views?

I have gut a simple project with a struct inside of a struct and want to add at first the names and hobbies of a single user and than want to add this user to a whole pool of users. The code is the following:
import SwiftUI
struct User: Identifiable, Hashable {
var id = UUID()
var firstName = ""
var lastName = ""
var hobbiesOfUser = [Hobbies]()
}
struct Hobbies: Identifiable, Hashable {
var id = UUID()
var nameOfHobby = ""
var nameClub = ""
}
class UsersStorage: ObservableObject {
#Published var users = [User]()
}
struct ContentView: View {
#EnvironmentObject var userStorage: UsersStorage
#State private var isPresented = false
var body: some View {
NavigationView {
List(userStorage.users) { singleUser in
VStack {
HStack {
Text(singleUser.firstName)
Text(singleUser.lastName)
}
}
}
.navigationBarItems(trailing:
Button(action: {
self.isPresented = true
}) {
Text("New User")
}.sheet(isPresented: $isPresented, onDismiss: {
self.isPresented = false
}) {
AddUserView(isPresented: self.$isPresented, user: User()).environmentObject(self.userStorage)
}
)
}
}
}
struct AddUserView: View {
#EnvironmentObject var userStorage: UsersStorage
#Binding var isPresented: Bool
#State var user: User
#State var hobbiesOfUser = [Hobbies]()
var body: some View {
NavigationView {
VStack {
Text("First Name")
TextField("First Name", text: $user.firstName)
Text("Last Name")
TextField("Last Name", text: $user.lastName)
NavigationLink(destination: AddHobbieView(hobbie: Hobbies())) {
Text("Add New Hobbie")
}
List(user.hobbiesOfUser) { singleHobbie in
VStack {
HStack {
Text(singleHobbie.nameOfHobby)
Text(singleHobbie.nameClub)
}
}
}
HStack {
Button(action: {
self.isPresented = false
}){
Text("Cancel")
}
Button(action: {
self.userStorage.users.append(self.user)
self.isPresented = false
}){
Text("Save")
}
}
}
}
}
}
struct AddHobbieView: View {
#EnvironmentObject var userStorage: UsersStorage
#State var hobbie: Hobbies
var body: some View {
VStack {
Text("Hobby")
TextField("First Name", text: $hobbie.nameOfHobby)
Text("Club")
TextField("Last Name", text: $hobbie.nameClub)
HStack {
Button(action: {
// self.userStorage.users.append(self.hobbie)
}){
Text("Cancel")
}
Button(action: {
}){
Text("Save")
}
}
}
}
}
My question is: how can I add hobbies to the list in the AddUserView and get the buttons in the AddHobbieView let me go back to the AddUserView.
You add #Environment(\.presentationMode) var presentationMode to AddHobbieView and call self.presentationMode.wrappedValue.dismiss() when you want to dismiss the view
I hope this helps!