How do you get SwiftUI Previews to work when Codegen is in ClassDefinition or Category/Extension? - core-data

I have a view using core data and xcdatamodeld file that contains the definition for an Item struct. If I use Xcode to generate the files for Item and manually manage them, preview works fine. However, when I use Codegen in either of the other formations, I get errors saying that the entire struct is undefined. This prevents the previews from working.
Code:
struct ArchiveView: View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#FetchRequest(entity: Item.entity(), sortDescriptors: []) var fetchedResults: FetchedResults<Item>
var body: some View {
return NavigationView {
List(fetchedResults, id: \.self ) { (fetchedResult: Item) in
return PurchaseView(name: fetchedResults.name price: fetchedResults.price, purchaseDate: fetchedResults.date)
}
}.navigationBarTitle("Order")
}
}
Both the Item+CoreDataClass and Item+CoreDataProperties are missing since they are automatically generated by xcode.
I am now using manual Codegen to be able to see the previews, but am curious whether I could use the other options. How can I use Class Defintion Codegen for the core data files and still be able to use SwiftUI previews?

After some testing, something appears to be wrong with the xcode code generator itself. Whenever I change the code generator to anything other than Class Definition at least once, this breaks the linking with previews.
The fix was to create a new project and copy over the old files.

Related

Implementing ForEach.onMove() with Core Data

I am trying to implement ForEach.onMove using Core Data. I have two entities, Parent with a To-Many relationship to Child, with the relationship marked as Ordered. The view is something like this:
struct ParentView : View {
#ObservedObject var parent : Parent
var body : some View {
List {
ForEach($parent.children! as! [Child]) { child in
ChildView(child)
}.onMove {
parent.managedObjectContext!.performAndWait { indices, to in
(parent.children! as! NSMutableOrderedSet).moveObjects(at: indices, to: to)
try! parent.managedObjectContext!.save()
parent.objectWillChange.send()
}
}
}
}
}
Results are:
No errors of any kind.
During debug of the onMove function, I can see that the items are re-ordered as required
The managedObjectContext.updatedObjects is empty at the same debug step, before the call to save()
When reloading the app, the re-ordering is obviously not saved (apparently because the updatedObjects set was empty at #3.)
What am I doing wrong? How can I make the MOC realize the re-ordering change?
I fixed this, apparently with a simple solution. I am posting this for future generations of google searchers. :D
The problem is with this line:
(parent.children! as! NSMutableOrderedSet).moveObjects(at: indices, to: to)
Apparently, taking the immutable version and making it mutable works, but doesn't update the managed object. This works:
parent.mutableOrderedSetValue(forKey: "children").moveObjects(at: indices, to: to)

How to create Mapbox Marker Onclick Eventlistener Android Studio?

Hello Stackoverflow community,
I am trying to develop an Android application with Mapbox.
I followed this guide to create markers on the map.
https://docs.mapbox.com/android/maps/examples/default-point-annotation/
Thus my code is the following:
public fun createMarker(id: String, lon: Double, lat: Double) {
// Create an instance of the Annotation API and get the PointAnnotationManager.
var marker: PointAnnotation? = bitmapFromDrawableRes(
drawercontext,
R.drawable.red_marker
)?.let {
val annotationApi = binding.mapBoxView.mapView?.annotations
val pointAnnotationManager =
annotationApi?.createPointAnnotationManager(binding.mapBoxView.mapView!!)
// Set options for the resulting symbol layer.
val pointAnnotationOptions: PointAnnotationOptions = PointAnnotationOptions()
// Define a geographic coordinate.
.withPoint(Point.fromLngLat(lon, lat))
// Specify the bitmap you assigned to the point annotation
// The bitmap will be added to map style automatically.
.withIconImage(it)
// Add the resulting pointAnnotation to the map.
pointAnnotationManager?.create(pointAnnotationOptions)
}
}
Unfortunately, I can not find any solution to add a click listener to markers (to show extra information outside of the map). In my opinion, this should be an important event, so I don't get why there is so little support. I want to replicate something like this:
https://bl.ocks.org/chriswhong/8977c0d4e869e9eaf06b4e9fda80f3ab
But in Android Studio with Kotlin.
One workaround I have seen is to add a click listener to the map and from there determine the marker with the closest coordinates, but I think that would not be as nice of a solution. Do you know any solutions or workarounds to my problem?
Thanks for the help in advance!
try this:
pointAnnotationManager.apply {
addClickListener(
OnPointAnnotationClickListener {
Toast.makeText(this#MainActivity, "id: ${it.id}", Toast.LENGTH_LONG).show()
false
}
)
}

CloudKit - How did my Entities end up in a Private Database (and can I change to Public)?

In xCode I created some Core Data Entities for my app in the Default Configuration and I set up my Data Controller class as follows:
class DataController: ObservableObject {
let container: NSPersistentCloudKitContainer
class DataController: ObservableObject {
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "KoyaQuest", managedObjectModel: Self.model)
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores { _, error in
if let error = error {
fatalError("Fatal error loading data store: \(error.localizedDescription)")
}
...
}
Over in CloudKit Dashboard, I notice that any records being created are stored in the "Private Database." And I'm not sure how that happened as I don't recall (nor can I find any evidence of) my specifying the type of database. I am in Development. Is this a default of that mode?
Also, since I really want these data to be public, is there a way to make the change? I've scoured Apple's documentation and other sources but can't seem to find a simple answer.
Thanks.
Soon after posting this question, I re-discovered this video (which I had actually watched before!). In case anyone has the same confusion as I did, I'm sharing here. Note that one of the two indices that need to be added to the CloudKit schema seems to have been re-named to "modifiedTimestamp" (at least that's the only one I see that looks close enough to "modifiedAt" as shown in the video).

Accessing Core Data Child Objects in Nested SwiftUI List

My end goal is to create a SwiftUI List with children. I have sections (parent) that contain projects (children), and the sections will expand and collapse their list of projects.
I have this built with an NSOutlineView using Realm, but now I'm trying to build it with SwiftUI and Core Data. *GULP* : )
I'm not very experienced with Core Data, but I think I have my data set up right. I have a Section entity and a Project entity, and the Section has a projects attribute that has a to-many relationship.
So, I presume (incorrectly as shown below) section.projects is a collection of Project objects.
For simplicity, I'm just trying to nest to the two inside a List (I'll worry about the DisclosureGroup stuff later):
List{
ForEach(sections, id: \.recordName){ section in
Text(section.name)
ForEach(section.projects, id: \.recordName){ project in //<-- ERROR
Text(project.name)
}
}
}
The line marked above has two errors that seems to suggest I don't have an array of objects to work with in section.projects:
Referencing initializer 'init(_:id:content:)' on 'ForEach' requires that 'NSObject' conform to 'RandomAccessCollection'
Value of optional type 'NSOrderedSet?' must be unwrapped to a value of type 'NSOrderedSet'
If section.projects is an NSOrderedSet, why can't I iterate over it?
I figured this out with the help of this article: https://www.hackingwithswift.com/books/ios-swiftui/one-to-many-relationships-with-core-data-swiftui-and-fetchrequest
I had to change my Core Data entities to have their Codegen setting be Manual/None (in the .xcdatamodeld editor in Xcode). I then select my entities and go to Editor > Create NSManagedObject Subclass... which created a bunch of files that reflect my Core Data models.
Then inside the Section+CoreDataProperties.swift file, I added a computed property to give me access to my projects (sorted by name):
public var projectArray: [Project] {
let set = projects as? Set<Project> ?? []
return set.sorted {
$0.wrappedName < $1.wrappedName
}
}
And then, for convenience, inside Project+CoreDataProperties.swift I added this:
public var wrappedName: String{
name ?? "Unknown Name"
}
...which allows my computed property to sort my projects by name (since Core Data treats strings as optionals by default).
As a result, I was able to iterate over my nested data like this:
ForEach(sections, id: \.self) { section in
Text(section.wrappedName)
ForEach(section.projectArray, id: \.self) { project in
Text(project.wrappedName)
}
}

Navigating to detail views in SwiftUI using core data

I'm trying to build an example BBQ app to learn SwiftUI (XCode 11 beta 5), and have been unable to figure out how to navigate to an object's detail view from a list view of objects coming from Core Data. Xcode is unhelpful, mostly popping unrelated errors depending on what I try.
I've tried applying state, bindings, observable objects etc to the best of my logical ability but haven't been able to crack it.
Here is my list view, which builds just fine, and allows me to navigate to destination (which is just a view containing the id property as a string):
struct CookListView: View {
#ObservedObject var cookListVM: CookListViewModel
#State var cookCreatorVM = CookCreatorViewModel()
init() {
cookListVM = CookListViewModel()
}
var body: some View {
NavigationView {
List {
ForEach(cookListVM.cooks, id: \.id) { cook in
NavigationLink(destination: Text("\(cook.id)")) {
VStack {
Text(cook.protein)
}
}
}
}.navigationBarTitle("BBQ")
}
}
}
But if I change my NavigationLink destination like so:
NavigationView {
List {
ForEach(cookListVM.cooks, id: \.id) { cook in
NavigationLink(destination: CookDetailView(cook: cook)) {
VStack {
Text(cook.protein)
}
}
}
}
}
Xcode will no longer build the project. The errors it gives me seem somewhat unrelated, such as Type '_' has no member 'id' on the ForEach line, or if I remove , id: \.id from the ForEach (which I don't really need thanks to Identifiable), I'll get Type of expression is ambiguous without more context on the Text(cook.protein) line.
If I use a hard-coded array it builds and can navigate perfectly well. The issue only arises when I'm trying to use Core Data.
My CookDetailView looks like this:
struct CookDetailView: View {
var cook: Cook
var body: some View {
VStack {
Text("\(cook.protein!)")
}
}
}
And the model for the Cook object itself looks like this:
class CookViewModel: ObservableObject, Identifiable {
var protein: String = ""
var type: String = ""
var id: UUID = UUID()
init(cook: Cook){
self.protein = cook.protein!
self.type = cook.type!
self.id = UUID()
}
}
It's also been set up thru the .xcdatamodeld file.
I'm more than happy to add in any additional/omitted code, such as how I'm writing to/reading from core data, if that would be helpful.
I can totally identify with your frustration with Xcode's error messages. The actual error is often nowhere near the error message. [Sug: use source control and commit after each clean compile ;-) ] The error that you are getting in your List view is because you specified a wrong type in your detail view's argument list. Try the following:
struct CookDetailView: View {
var cook: CookViewModel
var body: some View {
VStack {
Text("\(cook.name)")
}
}
}
By the way, since you are fetching into an array, you will not observe changes in that array unless you manually publish them. I gave up on trying to get my NSManagedObject subclasses to publish their own changes. There are two ways to workaround that. You can call objectWillChange from NSFetchedResultsController's controllerDidChangeContent delegate or you can do the same while observing NSManagedObjectContextDidSave notifications when not using a fetched results controller.
This answer is applicable to Beta 5. Things will certainly change in future betas.

Resources