View with FetchRequest doesn't update on change - swiftui

This is my first post to this forum and really hope that somebody can help me here. I would highly appreciate any help!
I am writing my first app with Swift UI (never used UIKit before) which I want to publish later on.
This is also my first app which has CoreData implemented.
For example I use the following entities:
Family, Person
1 Person can have 1 Family
1 Family can have many Persons
My app is structured as follows:
ContentView:
Contains a TabView with 2 other views in it. A Settings View and a View with a LazyVGrid.
LazyVGrid View:
This View shows a GridItem for every Family. I get the Families with the following Fetchrequest:
#Environment(\.managedObjectContext) private var viewContext
// These are the Families from the FetchRequest
#FetchRequest(entity: Family.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Family.created, ascending: false)]
) var families: FetchedResults<Family>
Every GridItem is linking to a "FamilyDetailView" via NavigationLink. So i pass the family as the following:
NavigationLink(destination: FamilyDetailView(family: family).environment(\.managedObjectContext, self.viewContext), label: {Text("Family Name")
In the FamilyDetailView I get the Family with a property wrapper:
#State var family : Family
In this FamilyDetailView is the problem i have.
Here I also have a LazyVGrid, which shows 1 NavigationLink for every Person in the Family in a GridItem . In this GridItem I also show for example the "name" of the Person.
When tapping the NavigationLink i get to the last View, the PersonDetailView. This View gets the Person which is also an entity which has a relationship to the Family Entity.
I pass it as the follow:
NavigationLink(
destination: PersonDetailView(person: person),
label: {Text("Person")})
In the PersonDetailView I now change the name of the person and save the changed to CoreData.
The change is saved without a problem, the problem is that when I go back, using the topleading back button from the NavigationView, the Views are not updated. I have to restart the App to see the changes..
I know that the Problem has to be with passing the Data, but I cant figuring out what I did wrong.
I really appreciate everyone trying to help me!
Thank you very very much!!

CoreData objects are reference types, i.e. classes, conformed to ObservableObject protocol, so instead of state wrap corresponding property with ObservedObject, like
#ObservedObject var family : Family

Thank you very much, this helped me a lot. The FamilyDetail View is updating with that fix. Just one thing does not update. I have a "FamilyRowView" which is display for every GridItem in my Family LazyVGrid. This now also gets uses "#ObservedObject var family : Family" but still doent update. Any ideas for that? Thank you so much!
FamilyListView:
NavigationLink(destination: FamilyDetailView(family: family)
.environment(\.managedObjectContext, self.viewContext),
label: {**FamilyRowView**(family: family)
.environment(\.managedObjectContext, self.viewContext)})
FamilyRowView:
struct FamilyRowView: View {
#Environment(\.managedObjectContext) private var viewContext
#ObservedObject var family : Family
var body: some View {
It seems like to be the same as the other views that are working not but it doesnt update the View..

Related

Name-Field in SwiftUI

How can you make a TextField, that knows it's a Name-Field?
I need the Phone to suggest the User His Name when he taps my TextField.
Any ideas how to do that in SwiftUI?
Thanks for your help!
Boothosh
It sounds like you might be referring to one of the textContentType options.
For example:
struct ContentView: View {
#State private var textFieldContent = ""
var body: some View {
TextField("User name", text: $textFieldContent)
.textContentType(.givenName)
}
}
In this example, the system will suggest a given name for the field based on the contact card set to be the device's owner. On iOS, you can see a list of the UITextContentType options here. On macOS, there's a similar list here, albeit with many fewer options.

FirstResponder & :onCommit on TextField in SwiftUI

In a SwiftUI app, I need to set the focus on a TextField and bring the keyboard automatically, in standard Swift this would be done with:
field.becomeFirstResponder()
But this does not seem to exist in SwiftUI.
I found a work around here.
But, my field uses :onCommit; which is not in the sample code.
What is the way to set the :onCommit functionality when using UIViewRepresentable ?
iOS 15+ has a solution for this.
#FocusState combined with the focused(_:) modifier can be used to control first responder status for textfields.
struct ExampleView: View {
#FocusState private var isFocused: Bool
#State private var textInput = ""
var body: some View {
TextField("Example", text: $textInput)
.focused($isFocused)
Button("Confirm") {
if textInput {
isFocused = true
}
}
}
}
For iOS15
There is a solution implemented by apple (as mentioned by #AlphaWulf)
For iOS14
In my opinion, the best approach is to implement your own version of TextField using the UIRepresentable protocol. This might sound like something difficult but it is actually quite simple.
Why it is better to implement your own text field over the solutions using view hierarchy introspection?
One is that a solution based on traversing underlying views is hacky by nature and even a minor iOS version update might break it.
Secondly, in a real-world app, you will want to set additional things on the text field (like return button type and supplementary view) but Apple didn't make a way of doing so and you will be forced to wrap a UITextField in any case.
https://blog.agitek.io/swiftui-2-first-responder-b6a828243268
In this post I have a detailed solution that is similar to what Apple has implemented in SwiftUI 3.
There is an open-source project for your needs, at https://github.com/mobilinked/MbSwiftUIFirstResponder
TextField("Name", text: $name)
.firstResponder(id: FirstResponders.name, firstResponder: $firstResponder, resignableUserOperations: .all)
TextEditor(text: $notes)
.firstResponder(id: FirstResponders.notes, firstResponder: $firstResponder, resignableUserOperations: .all)

Getting two different results using ForEach in SwiftUI

I have list of structs that I display in the view using a ForEach, I am trying to add to the list and when the user taps they see information for that item. Using two approaches I get two different results.
First Approach
The view updates the view when items are added perfectly, but changing the note in the Details changes values of all notes.
#Binding var person: Person
ForEach(self.person.notes) {
note in
DetailsCard(person: self.$person, note: notes)
}
Second Approach
The view does not update when notes are added, only when the view reappears does it show the new items. But I when the items are shown, the details view works as expected.
#Binding var person: Person
ForEach(self.person.notes.indices) { index in
VStack {
DetailsCard(person: self.$person, note: self.person.notes[index])
}
}
DetailView
#Binding var person: Person
#State var note: Note
This should be a fairly simple task but working with SwiftUI I am confused by the two different results for similar approaches. Does anyone have any ideas on why this is occurring and how to correctly dynamically update the view and pass the values.
Try using dynamic ForEach loops, ie. with an explicit id parameter:
ForEach(self.person.notes, id:\.self)
ForEach(self.person.notes.indices, id:\.self)
Note: id must conform to Hashable. If you decide to use self as an id, it will only work if your item conforms to Hashable. Otherwise you may want to use some property (or just add the conformance).

How can I "forward" the entire SwiftUI environment to another view?

It seems that certain SwiftUI views create new environment contexts, such as NavigationLink. None of the environment is available in the new view.
As a workaround, I've been just manually forwarding through environment variables, like this:
struct ExampleView: View {
#EnvironmentObject var foo: UserStore
#EnvironmentObject var bar: UserStore
var body: some View {
NavigationLink(destination:
SomeOtherView()
.environmentObject(self.foo)
.environmentObject(self.bar)
) {
Text("Open View")
}
}
}
However this seems broken, as it violates the purpose of the environment. Also, it's confusing because it's not clear (or documented?) where these boundaries are and it causes a runtime error when a view depends on a missing EnvironmentObject.
Is there a better way to do this?
Similarly, I want to create a wrapper UIViewControllerRepresentable that can contain SwiftUI children (via UIHostingController) and I would like those children to have access to the environment as well.

Implementing business and other logic in SwiftUI

(This is my first SwiftUI project; please be kind if this is a stupid question.)
I have a collection of objects which are displayed in a Picker. The picker selection is $selectedIndex, where
#State private var selectedIndex: Int = 0
I also have a
#State private var opts: OptsStruct = OptsStruct()
where elements of the OptsStruct structure are bound to SwiftUI views. The value of opts needs to change when the selectedIndex changes, because the opts property is the option shown in and selected by the Picker. (Also, I want to save the current value of selectedIndex in UserDefaults.) The problem is that I don't understand how to express these actions in SwiftUI.
I tried
#State private var selectedIndex: Int = 0 {
mutating didSet {
// save selectedIndex to UserDefaults
opts = f(selectedIndex)
}
but this causes a Segmentation Fault.
Where is the 'correct' place to put this logic. (And in general, can someone suggest some reading on how to connect changes to SwiftUI #States with general business logic.)
Thanks,
Rick
The idea of a #State variable is for it to be the single source of truth (wikipedia). This means that one variable should be the only thing that contains the "state" of your picker. In this case, I suggest using this:
$opts.selectionIndex
as the Binding for your picker. selectionIndex would then be a Int property of your OptsStruct type.