SwiftUI Core Data: Best way to access children array? - core-data

What is the best / most efficient way to access core data children in a multilevel one-to-many model?
I have a model with Board > List > Card > ..., always in a one-to-many relation.
At the start view all Boards are fetched.
Let's say we are in the SingleListView, looking at all cards from one List, that has been passed in. Now I can either directly use the Core Data class accessor from the list (NSSet transformed to Array):
#ObservedObject var list: ListEntity
...
ForEach(list.cardsArray) {
....
or I can fetch the cards with predicate
#FetchRequest
private var cards: FetchedResults<CardEntity>
init(list: ListEntity) {
_cards = FetchRequest<CardEntity>(
sortDescriptors: [ ... ],
predicate: NSPredicate(format: "ofList == %#", list),
animation: .default)
}
Is any of these better, more appropriate, more efficient towards view updates ... ?
Especially when I go deeper into the tree structure, it feels strange that all is relying on one first FetchRequest up the chain.

Overall it is best to use another #FetchRequest to fetch the child objects using the parent in the predicate, that is why we have inverse relations and means you can benefit to change tracking of the relation objects. However you will also need to wrap this child View inside an intermediate View so that when the parent View's body runs and inits all the child Views it doesn't needlessly re-fetch all the child fetch requests. This is because although #FetchRequest is a property wrapper, unlike #State it causes body to run every time its View is init, so you want to avoid that with the wrapper view that will not re-run the body when its the same parent object passed in. You can turn on SQL debugging to check what queries are hitting the database.

Related

How to bind non-UI entity with UI entity in Bevy

Description
I'm trying to implement trigger logic when the player faced the trigger I should remove the UI element from the screen.
Spawning the trigger point
/// Create a trigger point and when the user faced with
/// it I'll mark the tutorial as `in-progress` and
/// remove it when the collision between tutorial
/// and player is stopped
commands
.insert(Sensor(true))
.insert(Collider::cuboid(8.0, 8.0))
.insert(ActiveEvents::COLLISION_EVENTS)
.insert_bundle(SpriteBundle {
sprite: Sprite {
color: Color::rgb(0.1, 0.1, 0.1),
custom_size: Some(Vec2::new(16.0, 16.0)),
..Default::default()
},
transform: *tutorial_transform,
..Default::default()
})
// Tutorial is a component which I'll filter as `tutorial_entity`
.insert(Tutorial);
Create a UI
commands
.spawn_bundle(NodeBundle {
///
})
/// Trying to bind UI element with `Tutorial` entity
/// to remove it from the screen when the user faced with collider
.insert(Parent(tutorial_entity))
When the user faced collision
// I want to despawn all children UI elements that are linked with this non-UI element
commands.entity(tutorial_entity).despawn_recursive()
Error
I've got an error and no UI on the screen at all
Styled child in a non-UI entity hierarchy. You are using an entity with UI components as a child of an entity without UI components, results may be unexpected
Question
Do you know how to link a non-UI element with a UI element to remove the non-UI element and remove all linked UI elements with it?
I don't know if its still relevant but you could always just create a separate UI entity and add your own reference component.
I guess the for structurings sake i would create ( for example ) a UILinkComponent(pub Entity) and attach it to the world entity.
Pretty sure you shouldn't use Parent for that kind of relationship. The hierarchy is typically used for spatial relationships.
You should instead create a new component that stores the other Entity. You can still destroy it in the same way, but this way it isn't part of the hierarchy so the other UI elements don't get confused.

Is there a way to dynamically switch between FetchRequest and SectionedFetchRequest?

I've been playing around with CoreData in the last couple days, trying to build an app to review money spent on shopping. Right now its still pretty simple with just a single Data Model for the individual shops.
I have a list view displaying all of them and I've integrated sorting into the list, first through older workarounds around the predicates, but than I found this video from this years wwdc and I basically just copied. I've really been fascinated by the grouping feature from the SectionedFetchRequest and I wanted to integrate it, while maintaining the original non sectioned List. So I thought I'd skip the FetchRequest in my List and just pass the results to the list view instead of the SortDescriptor
MainView{
ListView(descriptor: SortDescriptor)
}
ListView{
FetchRequest(sortDescriptors: descriptor)
}
changed to:
MainView{
ListView(FetchRequest(sortDescriptors: descriptor))
}
ListView{
FetchedResults
}
But that still leaves me unable to just push a button to turn sectioning on or off.
I'm kind of stuck on how to go on from here.. First idea coming to my mind is creating a wrapper around the ListView handeling which FetchRequest to send out to the ListView based on Button toggle state like
MainView{
Wrapper(sortDescriptors, toggleState)
}
Wrapper{
ListView(FetchRequest(sortDescriptors: descriptor))
}
ListView{
FetchedResults
}
but I still would have the problem that I'd need 2 Variables in my ListView, one for the normal, and one for the sectionedFetchResults.
Has anyone an idea how to handle this ?
TLDR I want to dynamically switch between FetchRequest and SectionedFetchRequest
According to the documentation you can't not section a SectionedFetchRequest, therefore you would have to support both. Therefore, I would make two separate sub views, and show them in a parent view that has logic to control which one is shown. You would need to do this anyway if you are supporting pre-iOS 15 OS's.
MainView{
if sectioned
ListViewSectioned(sortDescriptors: descriptor, sectionID: sectionID)
} else {
ListView(sortDescriptors: descriptor)
}
}
ListView{
FetchRequest(sortDescriptors: descriptor)
}
ListViewSectioned{
SectionedFetchRequest(sectionIdentifier: sectionID, sortDescriptors: descriptor)
}
The main view doesn't have to know any more to choose and set up the different list views. I didn't put an OS check in, but you will need that as well.

create or inject ViewModel when building a "tabs" application

We try to build an application with a few tabs. As reference-project we use that example: http://slodge.blogspot.co.uk/2013/06/n25-tabs-n1-days-of-mvvmcross.html
To get the ViewModel-instances we need to create the tabs, we used the "HomeViewModel"-pattern as mentioned in that post: Create View Model using MVVMCross built in factory?
What I don't like at this approach is the initialisation of ViewModel's with "new". As far as I understand, it skips the whole ViewModel-Lifecycle (https://github.com/slodge/MvvmCross/wiki/View-Model-Lifecycle) which we really like. In our current project, we'd like to use the "start()" lifecycle-method, but it's never called due to initialisation with "new".
What worked for us was to go that way:
var loaderService = Mvx.Resolve<IMvxViewModelLoader>();
var vm = (UserListViewModel)loaderService.LoadViewModel(new MvxViewModelRequest(typeof(UserListViewModel), null, null, null), null);
So my question: Is that the way to do the job or is it just a dirty workaround and there is a much better solution?
Update: We came to that solution:
CreateTabFor<SettingsViewModel>("Settings", "settings");
//This method loads the ViewModel
private UIViewController CreateTabFor<TTargetViewModel>(string title, string imageName)
where TTargetViewModel : class, IMvxViewModel
{
var controller = new UINavigationController();
controller.NavigationBar.TintColor = UIColor.Black;
var viewModelRequest = new MvxViewModelRequest(typeof(TTargetViewModel), null, null, null);
var screen = this.CreateViewControllerFor<TTargetViewModel>(viewModelRequest) as UIViewController;
SetTitleAndTabBarItem(screen, title, imageName);
controller.PushViewController(screen, false);
return controller;
}
The 'viewmodel lifecycle' is an area of conflicting interests in MvvmCross. The root cause is the conflict between:
viewmodel's which are just the models for any view
viewmodel's which are specifically used within the 'ShowViewModel' navigation process
For simple 'whole page' User Experiences, the C-I-R-S viewmodel lifecycle is easy to support and to ensure it gets consistently used.
However, as soon as the user experience starts to merge in tabs, flyouts, hamburger menus, dialogs, split views, etc then:
the developers sometimes want to control viewmodel lifecycles themselves
it's not as easy for the framework to ensure that view models are always created, activated and tombstoned/rehydrated consistently
Personally, I like your approach - of trying to ensure all viewmodels are independent and all constructed the same way - but MvvmCross doesn't force this approach on all developers.
Specifically for tabs, most of the existing examples do use the 'owned sub-viewmodel' pattern that you've identified.
However, it should be relatively easy to implement other mechanisms if you want to - just as you already have.
In particular, you can:
use the loaderService directly - getting hold of it via Mvx.Resolve<IMvxViewModelLoader>();
use ShowViewModel with a custom presenter to create both views and viewmodels - the beginnings of this is illustrated in that N=25 video but you could take it much further and actually add the tabs in response to ShowViewModel calls.
use alternative calls to create the child tabs and their viewmodels inside the Views - e.g. where the Touch sample currently calls
var screen = this.CreateViewControllerFor(viewModel) as UIViewController;
this could easily be replace with something like:
var screen = this.CreateViewControllerFor<ChildViewModel>() as UIViewController;;
(or one of the other overloads from MvxCanCreateIosViewExtensionMethods.cs)
One repo where I know some users have taken some of these ideas and played with them is the Sliding menu repo - I think they have chosen to use this.CreateViewControllerFor<TViewModel> to create their view models. This may or may not be the way you choose to go - but it might be of interest for you to experiment with.

Relationships are (null) when -awakeFromInsert message received by new object in NSOrderedSet

The following -awakeFromInsert implementation sets properties of new objects instantiated by array controllers in a UI when the user presses an add button:
- (void)awakeFromInsert
{
[super awakeFromInsert];
NSLog(#"Adding perceptron %ld to layer %ld", self.indexInLayer, self.layer.indexInNetwork);
NSLog(#"New perceptron added to layer %#", self.layer );
// more code here to do the configuration
}
The problem is that the self.parent relationship of the new object is not set when -awakeFromInsert is called (it is nil) so I can't use it to access the parent object (for example how many childs there are or what index the new object is).
Output of the above code:
2012-12-17 21:36:39.309 MLPManager[98112:403] Adding perceptron 0 to layer 0
2012-12-17 21:36:39.309 MLPManager[98112:403] New perceptron added to layer (null)
I'm pretty sure that the new objects are being connected up correctly because the indexInLayer method works perfectly when the UITableView calls it to add indexes of the objects to the view:
- (NSUInteger)indexInLayer
{
NSUInteger index = [self.layer.perceptrons indexOfObject:self];
//NSLog(#"indexInLayer returning %ld", index );
return index;
}
My data model has three entities: Network, Layer, Perceptron arranged as ordered sets and connected to the next by to-many relationships (i.e. Parent - Child - Grandchild). My UI has three array controllers and three UITableViews. I've set it up so that the Child array controller only contains the children of the selected Parent, and the Grandchild array controller only contains the grandchildren of the selected Child. When I add childs or grandchilds to these arraycontrollers they are automatically set as children of the currently selected parent. That all works fine.
At what point is self.layer set by the UI? Can someone confirm that this is occurring after -awakeFromInsert? And if so, how am I supposed to configure a new object if I can't do it from within -awakeFromInsert? I note that the Apple documentation for -awakeFromInsert says it is "invoked automatically by the Core Data framework when the receiver is first inserted into a managed object context."
The reason I need information on the layer object and other parts of the data structure is that I need to automatically instantiate various other objects (weights which are children of perceptrons) at the same time as the new perceptron object. Should I be using -awakeFromInsert for these kind of tasks?
First, there appears to be coupling in your code between model (NSManagedObject) and view (UITableView) objects. That is not recommended according to the model-view-controller design pattern.
layer and indexInLayer are not standard attributes of NSManagedObject so I assume these are attributes in your entity. As an alternative to doing the setup in awakeFromInsert, I wonder if you can instead implement your own setter methods for your attributes so that you can do the necessary work at the time the required data is available.
If you choose to implement your own setter methods, you need to follow Apple's guidance in the Managed Object Accessor Methods documentation, specifically:
You must ensure that you invoke the relevant access and change
notification methods (willAccessValueForKey:, didAccessValueForKey:,
willChangeValueForKey:, didChangeValueForKey:,
willChangeValueForKey:withSetMutation:usingObjects:, and
didChangeValueForKey:withSetMutation:usingObjects:).
I do not know why layer and indexInLayer are not usable in your awakeFromInsert method; it's possible you need to solve that problem instead.

What's the best practice to creating different views when sharing one child frame in an MFC MDI app?

I'm not necessarily looking for code help, but rather a high level answer so I can research the solution myself. Basically, I have an MDI app with multiple docs and their views, I'd like all the views to open up as tabs in the one child frame that I have. The thing is my child frame is statically configured with a splitter window with two views, a form and a list view, in the OnCreateClient method. I'd like to keep this as the default tab that appears when the app is launched.
I have a third view (editview) with it's own document template, which I'd like to be able to open as a separate tab. I will have other views that will behave this way. What's the best way to approach this?
Will I need to create separate child frames for each view? Will I lose the 'tab' feature if I create separate child frames?
Or will I have to modify the child frame's OnCreateClient method to test which document template is the current one and create the view for that doc template? I'd like to know how some of you seasoned programmers have had or would do it.
Thanks.
In case this helps others, from what I've gathered, it is perfectly acceptable to create a new child frame class derived from CChildFrame or just use that as your frame with your new view. The doc, frame, and view will be added to the doc template in the initInstance method. for example, let say you have a pair of trios (2 docs, 2 views, 2 frames):
pDocTemplate = new CMultiDocTemplate(IDR_testappTYPE,
RUNTIME_CLASS(CMydoc1),
RUNTIME_CLASS(CMyframe1),
RUNTIME_CLASS(CMyview1));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
pDocTemplate2 = new CMultiDocTemplate(IDR_testappTYPE,
RUNTIME_CLASS(CMydoc2),
RUNTIME_CLASS(CMyframe2),
RUNTIME_CLASS(CMyview2));
if (!pDocTemplate2)
return FALSE;
AddDocTemplate(pDocTemplate2);
If you add another trio with a different childframe because this new frame doesn't use splitters like the ones above, you would do it this way.
pDocTemplate3 = new CMultiDocTemplate(IDR_mditest3TYPE,
RUNTIME_CLASS(CMydoc), //same doc
RUNTIME_CLASS(CMyframeWithoutSplitters), //new frame
RUNTIME_CLASS(CMyview3)); //new view
if (!pDocTemplate3)
return FALSE;
AddDocTemplate(pDocTemplate3);

Resources