Skip to content

Commit

Permalink
Add example app (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
spnkr authored Dec 14, 2022
1 parent 76aa6ad commit 9c40a0e
Show file tree
Hide file tree
Showing 15 changed files with 796 additions and 0 deletions.
407 changes: 407 additions & 0 deletions Example App/Example App.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions Example App/Example App/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
10 changes: 10 additions & 0 deletions Example App/Example App/Author.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation
import CoreData
import CoreDataPlus

extension Author: ManagedObjectCountable,
ManagedObjectSearchable,
ManagedObjectDeletable,
ManagedObjectFindOrCreateBy {

}
10 changes: 10 additions & 0 deletions Example App/Example App/Book.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation
import CoreData
import CoreDataPlus

extension Book: ManagedObjectCountable,
ManagedObjectSearchable,
ManagedObjectDeletable,
ManagedObjectFindOrCreateBy {

}
61 changes: 61 additions & 0 deletions Example App/Example App/BookDetailView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//

import Foundation
import SwiftUI
import CoreData
import CoreDataPlus

struct BookDetailView: View {
@Environment(\.managedObjectContext) private var viewContext
@ObservedObject var book: Book

public var body: some View {
ScrollView {
VStack {
Text(book.title ?? "No title")
.font(.title)

ForEach((book.authors?.allObjects as? [Author] ?? []), id: \.self) { author in
HStack(spacing: 5) {
Text(author.name ?? "No name")
Button("", action: {
book.removeFromAuthors(author)
})
}
}


VStack(spacing: 100) {
Button("Add author\nHaruki Murakami", action: {
let murakami = Author.findOrCreate(column: "name", value: "Haruki Murakami", context: viewContext)
book.addToAuthors(murakami)
})
.buttonStyle(.bordered)

Button("Add author\nMurakami, J.K., or Millet", action: {
let names = [
"Haruki Murakami",
"J. K. Rowling",
"Lydia Millet"
]

book.addToAuthors(Author.findOrCreate(column: "name", value: names.randomElement()!, context: viewContext))
})
.buttonStyle(.bordered)

Button("Add new fictional author", action: {
book.addToAuthors(Author.findOrCreate(column: "name", value: "Author \(Int.random(in: 1...1000))", context: viewContext))
})
.buttonStyle(.bordered)

Button("Add new fictional author", action: {
book.addToAuthors(Author.findOrCreate(column: "name", value: "Author \(Int.random(in: 1...1000))", context: viewContext))
})
.buttonStyle(.bordered)
}


}
}
}
}
156 changes: 156 additions & 0 deletions Example App/Example App/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//
// ContentView.swift
// Example App
//
// Created by Will Jessop on 10/26/22.
//

import SwiftUI
import CoreData
import CoreDataPlus

struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext

@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Book.title, ascending: true)],
animation: .default)
private var books: FetchedResults<Book>

var body: some View {
NavigationView {
List {
Section(footer: VStack(alignment: .leading) {
Spacer()
HStack {
Image(systemName: "plus")
Text("Add test data").fontWeight(.bold)
}
.foregroundColor(.blue)
.onTapGesture {
self.addTestData()
}
Spacer(minLength: 20)

Text("Saving").underline()
Text("Core Data is saved to disk when the app goes into the background.\n\nOr you can click the \"Save all\" button.")
Spacer(minLength: 20)

Text("New Book object with id = 100").underline()
Text("Demonstrates what happens when you create a variable using findOrCreate(id:)")
Spacer(minLength: 20)

}) {
ForEach(books) { book in
NavigationLink {
BookDetailView(book: book)
} label: {
Text(book.title!)
Text("\((book.authors?.allObjects as! [Author]).map { author in author.name! }.joined())")
.font(.caption)
}
}
.onDelete(perform: deleteItems)
}



}
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {


Button(action: addBook) {
Label("Add Book", systemImage: "plus")
}

Button(action: deleteAllBooksAndAuthors) {
Label("Delete all books and authors", systemImage: "trash")
}

}

ToolbarItemGroup(placement: .navigationBarLeading) {
Button("New Book object with id = 100", action: {
let book = Book.findOrCreate(id: "100", context: viewContext)
book.title = "Book(id=100) last touched \(Date().formatted(date: .abbreviated, time: .complete))"
})
}

ToolbarItemGroup(placement: .bottomBar) {

Button("Save all", action: {
try! viewContext.save()
})
.buttonStyle(.borderedProminent)

Spacer()
Button("Delete authors", action: {
Author.destroyAll(context: viewContext)
})
.foregroundColor(.red)

Button("Delete books", action: {
Book.destroyAll(context: viewContext)
})
.foregroundColor(.red)




}

}
Text("Select an item")
}
.navigationViewStyle(.stack)
}

private func addTestData() {
// create some authors
// using the name field as the unique identifier
// TODO: add better docs on how to handle id/identifiable with core data (not library-specific)
let murakami = Author.findOrCreate(column: "name", value: "Haruki Murakami", context: viewContext)
let jk = Author.findOrCreate(column: "name", value: "J. K. Rowling", context: viewContext)

// using id as the unique identifier
let lydia = Author.findOrCreate(id: "1234", context: viewContext)
lydia.name = "Lydia Millet"

var authors = [murakami, jk, lydia]

for _ in 0..<10 {
let newItem = Book.findOrCreate(id: UUID().uuidString, context: viewContext)
newItem.title = "Harry Potter Vol. \(Int.random(in: 1...1000))"

newItem.addToAuthors(authors.randomElement()!)

// add a 2nd author to some books
if Int.random(in: 1...100) > 50 {
newItem.addToAuthors(authors.randomElement()!)
}
}
}
private func deleteAllBooks() {
Book.destroyAll(context: viewContext)
}
private func deleteAllAuthors() {
Author.destroyAll(context: viewContext)
}
private func deleteAllBooksAndAuthors() {
Author.destroyAll(context: viewContext)
Book.destroyAll(context: viewContext)
}

private func addBook() {
let newBook = Book.findOrCreate(id: UUID().uuidString, context: viewContext)
newBook.title = "Book \(Int.random(in: 1...1000))"
}

private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map { books[$0] }.forEach(viewContext.delete)
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Example_App.xcdatamodel</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21279" systemVersion="21G72" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Author" representedClassName="Author" syncable="YES" codeGenerationType="class">
<attribute name="id" optional="YES" attributeType="String"/>
<attribute name="name" attributeType="String" defaultValueString=""/>
<relationship name="books" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Book" inverseName="authors" inverseEntity="Book"/>
</entity>
<entity name="Book" representedClassName="Book" syncable="YES" codeGenerationType="class">
<attribute name="id" attributeType="String"/>
<attribute name="title" attributeType="String" defaultValueString=""/>
<relationship name="authors" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Author" inverseName="books" inverseEntity="Author"/>
</entity>
</model>
30 changes: 30 additions & 0 deletions Example App/Example App/Example_AppApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// Example_AppApp.swift
// Example App
//
// Created by Will Jessop on 10/26/22.
//

import SwiftUI

@main
struct Example_AppApp: App {
let persistenceController = PersistenceController.shared
@Environment(\.scenePhase) private var phase

var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
.onChange(of: phase) { newPhase in
switch newPhase {
case .background:
try! persistenceController.container.viewContext.save()
default:
break
}
}
}

}
50 changes: 50 additions & 0 deletions Example App/Example App/Persistence.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// Persistence.swift
// Example App
//
// Created by Will Jessop on 10/26/22.
//

import CoreData
import CoreDataPlus

struct PersistenceController {
static let shared = PersistenceController()

static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext



try! viewContext.save()

return result
}()

let container: NSPersistentContainer

init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Example_App")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
Loading

1 comment on commit 9c40a0e

@spnkr
Copy link
Owner Author

@spnkr spnkr commented on 9c40a0e Dec 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Please sign in to comment.