Skip to content

Commit

Permalink
Persistent data using core data.
Browse files Browse the repository at this point in the history
  • Loading branch information
Goku132 committed Mar 5, 2018
1 parent f24da11 commit 32e0204
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 60 deletions.
25 changes: 21 additions & 4 deletions ToDoLIst.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
044AC0C72046BFA100145BEA /* item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044AC0C62046BFA100145BEA /* item.swift */; };
0464A2B22046F0AB0056BC72 /* DataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 0464A2B02046F0AB0056BC72 /* DataModel.xcdatamodeld */; };
04AF0126204D64440032EB73 /* CategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AF0125204D64440032EB73 /* CategoryViewController.swift */; };
04FBF6ED20408B20003A32A0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04FBF6EC20408B20003A32A0 /* AppDelegate.swift */; };
04FBF6EF20408B20003A32A0 /* ToDoList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04FBF6EE20408B20003A32A0 /* ToDoList.swift */; };
04FBF6F220408B20003A32A0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 04FBF6F020408B20003A32A0 /* Main.storyboard */; };
Expand All @@ -16,7 +17,8 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
044AC0C62046BFA100145BEA /* item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = item.swift; sourceTree = "<group>"; };
0464A2B12046F0AB0056BC72 /* DataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DataModel.xcdatamodel; sourceTree = "<group>"; };
04AF0125204D64440032EB73 /* CategoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryViewController.swift; sourceTree = "<group>"; };
04FBF6E920408B20003A32A0 /* ToDoLIst.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ToDoLIst.app; sourceTree = BUILT_PRODUCTS_DIR; };
04FBF6EC20408B20003A32A0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
04FBF6EE20408B20003A32A0 /* ToDoList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoList.swift; sourceTree = "<group>"; };
Expand All @@ -40,14 +42,15 @@
044AC0C82046BFB700145BEA /* Data Model */ = {
isa = PBXGroup;
children = (
044AC0C62046BFA100145BEA /* item.swift */,
0464A2B02046F0AB0056BC72 /* DataModel.xcdatamodeld */,
);
path = "Data Model";
sourceTree = "<group>";
};
044AC0C92046BFD800145BEA /* Controllers */ = {
isa = PBXGroup;
children = (
04AF0125204D64440032EB73 /* CategoryViewController.swift */,
04FBF6EE20408B20003A32A0 /* ToDoList.swift */,
);
path = Controllers;
Expand Down Expand Up @@ -171,8 +174,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
04AF0126204D64440032EB73 /* CategoryViewController.swift in Sources */,
04FBF6EF20408B20003A32A0 /* ToDoList.swift in Sources */,
044AC0C72046BFA100145BEA /* item.swift in Sources */,
0464A2B22046F0AB0056BC72 /* DataModel.xcdatamodeld in Sources */,
04FBF6ED20408B20003A32A0 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -358,6 +362,19 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCVersionGroup section */
0464A2B02046F0AB0056BC72 /* DataModel.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
0464A2B12046F0AB0056BC72 /* DataModel.xcdatamodel */,
);
currentVersion = 0464A2B12046F0AB0056BC72 /* DataModel.xcdatamodel */;
path = DataModel.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = 04FBF6E120408B1F003A32A0 /* Project object */;
}
49 changes: 30 additions & 19 deletions ToDoLIst/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
Expand All @@ -20,26 +21,36 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true
}

func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}

func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}

func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.

self.saveContext()
}

// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = {

let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()

// MARK: - Core Data Saving support

func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}


Expand Down
91 changes: 91 additions & 0 deletions ToDoLIst/Controllers/CategoryViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// CategoryViewController.swift
// ToDoLIst
//
// Created by Micheal Griffin on 05/03/2018.
// Copyright © 2018 Micheal Griffin. All rights reserved.
//

import UIKit
import CoreData

class CategoryViewController: UITableViewController {

var categories = [Category]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

override func viewDidLoad() {
super.viewDidLoad()
loadCategories()

}

//MARK -- Table View DataSource Methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell", for: indexPath)
cell.textLabel?.text = categories[indexPath.row].name
return cell
}

//MARK -- Table View Delegats Methods
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToItems", sender: self)

}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! ToDoListViewController
if let indexPath = tableView.indexPathForSelectedRow {
destinationVC.selectedCategory = categories[indexPath.row]
}
}

//MARK -- Data Manipulation Methods
func saveCategories(){
do{
try context.save()
}catch{
print("Error Saving Category \(error)")
}
tableView.reloadData()
}

func loadCategories(with request : NSFetchRequest<Item> = Item.fetchRequest()){
let request : NSFetchRequest<Category> = Category.fetchRequest()
do{
categories = try context.fetch(request)
}catch{
print("Error Loading Categories \(error)")
}
tableView.reloadData()
}

//MARK -- Add new Categories
@IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Add new Category", message: "", preferredStyle: .alert)
let action = UIAlertAction(title: "Add", style: .default) { (action) in
//what will happen when user clicks add item
let newCategory = Category(context: self.context)
newCategory.name = textField.text!
self.categories.append(newCategory)
self.saveCategories()
}

alert.addAction(action)
alert.addTextField { (field) in
textField = field
textField.placeholder = "Add a new Category"

}

present(alert,animated: true, completion: nil)
}



}
75 changes: 58 additions & 17 deletions ToDoLIst/Controllers/ToDoList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,22 @@
//

import UIKit
import CoreData

class ToDoListViewController: UITableViewController {

var itemArray = [item]()
let dataFilePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("Items.plist")
var itemArray = [Item]()
var selectedCategory : Category? {
didSet{
loadItems()
}
}
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

override func viewDidLoad() {
super.viewDidLoad()
loadItems()
print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask))

}

//MARK - TableView DataSource Methods
Expand All @@ -38,6 +45,12 @@ class ToDoListViewController: UITableViewController {
//MARK - TableView Delegates
//Find selected row.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Remove
//1
// context.delete(itemArray[indexPath.row])
// //2
// itemArray.remove(at: indexPath.row)

itemArray[indexPath.row].done = !itemArray[indexPath.row].done
saveItems()
tableView.deselectRow(at: indexPath, animated: true)
Expand All @@ -50,9 +63,12 @@ class ToDoListViewController: UITableViewController {
let alert = UIAlertController(title: "Add new To-do Item", message: "", preferredStyle: .alert)
let action = UIAlertAction(title: "Add Item", style: .default) { (action) in
//what will happen when user clicks add item
let newItem = item()
let newItem = Item(context : self.context)
newItem.title = textField.text!
newItem.done = false
newItem.parentCategory = self.selectedCategory
self.itemArray.append(newItem)

self.saveItems()
}
alert.addTextField { (alertTextField) in
Expand All @@ -65,27 +81,52 @@ class ToDoListViewController: UITableViewController {

//MARK -- Model manupulation methods
func saveItems(){
let encoder = PropertyListEncoder()
do{
let data = try encoder.encode(itemArray)
try data.write(to: dataFilePath!)
try context.save()
}catch{
print("Error encoding item ARRAY, \(error)")
print("Error Saving Context \(error)")
}
self.tableView.reloadData()
}
func loadItems(){
if let data = try? Data(contentsOf: dataFilePath!){
let decoder = PropertyListDecoder()
do{
itemArray = try decoder.decode([item].self, from: data)
}catch{
print("Error decoding item array \(error)")
}


func loadItems(with request : NSFetchRequest<Item> = Item.fetchRequest(), predicate : NSPredicate? = nil){
let categoryPredicate = NSPredicate(format: "parentCategory.name MATCHES %@", selectedCategory!.name!)

if let additionalPredicate = predicate{
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [categoryPredicate, additionalPredicate])
}else{
request.predicate = categoryPredicate
}

do{
itemArray = try context.fetch(request)
}catch{
print("Error Fetching Data from Context \(error)")
}

}


}

//MARK -- Search Bar Methods
extension ToDoListViewController: UISearchBarDelegate {

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
let request: NSFetchRequest<Item> = Item.fetchRequest()
let predicate = NSPredicate(format: "title CONTAINS[cd] %@", searchBar.text!)
request.sortDescriptors = [NSSortDescriptor(key: "title ", ascending: true)]
loadItems(with: request, predicate: predicate)

}

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text?.count == 0{
loadItems()
DispatchQueue.main.async {
searchBar.resignFirstResponder()
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13772" systemVersion="17D102" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Category" representedClassName="Category" syncable="YES" codeGenerationType="class">
<attribute name="name" attributeType="String" syncable="YES"/>
<relationship name="items" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Item" inverseName="parentCategory" inverseEntity="Item" syncable="YES"/>
</entity>
<entity name="Item" representedClassName=".Item" syncable="YES" codeGenerationType="class">
<attribute name="done" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<attribute name="title" attributeType="String" syncable="YES"/>
<relationship name="parentCategory" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Category" inverseName="items" inverseEntity="Category" syncable="YES"/>
</entity>
<elements>
<element name="Item" positionX="-63" positionY="-18" width="128" height="88"/>
<element name="Category" positionX="-65" positionY="-115" width="128" height="73"/>
</elements>
</model>
14 changes: 0 additions & 14 deletions ToDoLIst/Data Model/item.swift

This file was deleted.

Loading

0 comments on commit 32e0204

Please sign in to comment.