I'm presently taking an iOS development course. As part of an assignment, I'm tasked with creating a UISearchController in a note tracking project using Core Data in Swift.
Every example I've found is in Objective-C or is filtering a static array. Apple's "sample" code, updated in December 2014 doesn't compile in Xcode 6.3.
To add a UISearchController, I've got 3 primary tasks to do:
1) Create a view controller to present search results. I'm using a TableViewController.
2) Create a UISearchController, and pass it my search results view controller.
What's "stumping" me is now to get a hold of the objects in the managedObjectsContext. Prior to attempting to add a UISearchController, my app works fine. I can add, edit, and delete items. I'm using the "canned" Core Data code in Xcode 6.3 with the stack in AppDelegate.
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate
var searchController: UISearchController? = nil
func addSearchBar() {
var resultsController = SearchResultsTableViewController()
resultsController.notes = // stumped what to call the notes. Trying to call an array from the Notes class below
resultsController.delegate = self
searchController = UISearchController(searchResultsController: resultsController)
searchController!.searchResultsUpdater = resultsController
searchController!.searchBar.frame = CGRect(
x: searchController!.searchBar.frame.origin.x,
y: searchController!.searchBar.frame.origin.y, width: searchController!.searchBar.frame.size.width, height: 44.0)
tableView.tableHeaderView = searchController!.searchBar
self.definesPresentationContext = true
}
3) The UISearchController will notify its searchResultsUpdater (a class that conforms to UISearchResultsUpdating) when the search text changes. I'm using my search results view controller implement this protocol so I can update the filtered results.
Below is my Note: NSManagedObject class:
import Foundation
import CoreData
class Note: NSManagedObject {
#NSManaged var dateCreated: NSDate
#NSManaged var dateEdited: NSDate
#NSManaged var noteTitle: String
#NSManaged var noteBody: String
// TODO: adding this to make it easier to handle names
class func notes() -> NSArray {
let whereMyNotesAreStored = // Need syntax for where my MyManagedObjectContext is located
let dataArray = NSArray(contentsOfFile: whereMyNotesAreStored!)
var notesArray = NSMutableArray()
for dictionary in dataArray {
var note = Note()
note.noteTitle = dictionary["noteTitle"] as! String
note.noteBody = dictionary["noteBody"] as! String
note.dateCreated = dictionary["dateCreated"] as! String
note.dateEdited = dictionary["dateEdited"] as! String
notesArray.addObject(note)
}
return NSArray(array: notesArray as! [AnyObject])
}
}
There are two approaches to setting the context:
Calling back to the App Delegate:, like this
let appDelegate : AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context = appDelegate.managedObjectContext!
or passing the context forward from the App Delegate to the Master View Controller, which then passes it on to any subsequent view controllers, etc. Each view controller will need a property defined for the context; when a new VC is instantiated, the context is set before the VC is presented/pushed, eg:
class CustomViewController : UIViewController {
var managedObjectContext : NSManagedObjectContext
...
and, when loading a new view controller,
let newVC = CustomViewController()
newVC.managedObjectContext = self.managedObjectContext
...
To access the objects, use either a NSFetchRequest or NSFetchedResultsController to create an array of results, which you can then pass to the resultsController. eg. for a fetch request:
let fetch = NSFetchRequest(entityName: "Notes")
var error : NSError? = nil
let fetchedResults = managedObjectContext?.executeFetchRequest(fetch, error: &error)
(Your notes() function is on the wrong track - you would not use NSArray(contentsOfFile:) to access CoreData objects. Also, you must use the designated initialiser for NSManagedObject subclasses: so not var note = Notes() but var note = Notes(entity: NSEntityDescription, insertIntoManagedObjectContext: NSManagedObjectContext?)
Related
I want to share some data (an array of custom objects)
from different ViewController, when tab changed.
1 = TabController
2 = ViewController
3 = ViewController
4 = SplitViewController
5 = MapView
6 = ViewController
7 = TableViewController
I want to share data between:
7 to 3, 7 to 2
What is the best way to do this?
You could do something like this:
class DataSource {
static let sharedInstance = DataSource()
var data: [AnyObject] = []
}
Usage:
DataSource.sharedInstance.data
Another simple solution is creating a view bag to hold data to be shared between VC:
import Foundation
class ViewBag
{
internal static var internalDictionary = Dictionary<String, AnyObject>()
class func get(key: String) -> AnyObject?
{
return internalDictionary[key]
}
class func add(key: String, data: AnyObject)
{
internalDictionary[key] = data
}
}
class MyClass
{
}
// Example
let myClassArray = [MyClass(),MyClass(),MyClass(),MyClass()]
ViewBag.add("myKey", data: myClassArray)
ViewBag.get("myKey")?.count // You must do a proper casting here
What's the data?A string?NSNotification is best.A few data?Save to NSUserDefaults.A lot of data?Save to file and read it.
Here is example code for find vc along view controller chain:
let vc7 = UIViewController()
let tabBarVC = vc7.splitViewController?.tabBarController
let vc2 = tabBarVC?.viewControllers?[1]
let vc3 = tabBarVC?.viewControllers?[2]
I have a static table that is bound to some Core Data values, I'm not sure how I would use NSFetchedResultsController in this instance, though I have seen discussions about how much more recommended it is.
I grab my Core Data object which is passed via Segue.
I also have a model that is setup to contain questions, with one of the properties containing the Core Data value (this is why I don't think I can use NSFetchedResultsController, as even though my Core Data entity contains some of the values I need, I'm not sure I would need a full data set)
self.surveyQuestion.append(SurveyQuestion(question: "Does the customer have a 'Proof of ownership'?", answer: coreDataEntity.isProofOfOwnership as Bool))
The questions are Survey related such as "Is your property X?" with a UiSwitch which is mapped to a Core Data value:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let cell : SurveyQuestionTableViewCell = tableView.dequeueReusableCellWithIdentifier("SurveyQuestionCell") as! SurveyQuestionTableViewCell
cell.lblQuestion.textColor = UIColor.grayColor()
let surveyQuestion = self.surveyQuestion[indexPath.row]
cell.lblQuestion.text = surveyQuestion.question
cell.toggQuestion.on = surveyQuestion.answer
if cell.toggQuestion.on {
cell.lblQuestion.textColor = UIColor.blackColor()
cell.accessoryType = .DetailDisclosureButton
}
return cell
}
Now, when I tap on the UISwitch I need it to update the Core Data value, and reload the table, its hooked up to a CustomTableViewCell like so:
*edit - Nearly got this thing working! heres my UITableViewCell class
class SurveyQuestionTableViewCell: UITableViewCell {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
#IBOutlet weak var lblQuestion: UILabel!
#IBOutlet weak var toggQuestion: UISwitch!
var surveyQuestionReference : SurveyQuestion?
var tableViewReference : UITableView?
#IBAction func toggledQuestion(sender: AnyObject) {
let tempContext: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
tempContext.parentContext = self.managedObjectContext
tempContext.performBlock({
let entityName = "CoreDataEntity"
let request = NSFetchRequest(entityName: entityName)
request.predicate = NSPredicate(format: "id = %#", self.surveyQuestionReference!.id)
do {
let results = try tempContext.executeFetchRequest(request) as? [NSManagedObject]
if results!.count > 0{
if let moc = self.managedObjectContext{
moc.performBlockAndWait({
for result in results!{
result.setValue(self.toggQuestion.on, forKey: (self.surveyQuestionReference?.property)!)
}
})
}
}
do {
try tempContext.save()
//completion(finished: true)
} catch let error {
print(error)
}
}catch{
print("error")
}
})
print(sender)
dispatch_async(dispatch_get_main_queue()) {
self.tableViewReference!.reloadData()
}
}
I can obviously access the bit where the toggle is triggered, but this class doesn't know anything about the Core Data bit, I was thinking about using notifications but that just seems kind of messy...
when you create your cell, pass in a reference to the coredata object, and the tableView itself and store them as attributes of SurveyQuestionTableViewCell, then you can do everything you need to in setSelected()
in your custom cell class, add an attribute for the question
class SurveyQuestionTableViewCell: UITableViewCell {
#IBOutlet weak var lblQuestion: UILabel!
#IBOutlet weak var toggQuestion: UISwitch!
var surveyQuestionReference : SurveyQuestionType
vat tableViewReference : UITableView
...
and then in cellForRowAtIndexPath after you create the cell
cell.surveyQuestionReference = surveyQuestion
cell.tableViewReference = tableView
where SurveyQuestionType is whatever you have previously defined
in setSelected, you can use those stored attributes
surveyQuestionReference = self.toggQuestion.on
tableViewReference.reloadData()
Here's another option, using a shared Instance
import Foundation
import MapKit
import CoreData
class DataModelInstance : NSObject, NSCoding
{
var appDelegate : AppDelegate?
var managedContext : NSManagedObjectContext?
var persistentStoreCoordinator : NSPersistentStoreCoordinator?
// plus whatever else you need
class var sharedInstance : DataModelInstance
{
struct Singleton
{
static let instance = DataModelInstance()
}
return Singleton.instance
}
and then in any class which needs access to this data model
var dataModel = DataModelInstance.sharedInstance
I know there are those who just won't ever use singletons, but it can be a much more elegant solution to making these attributes available where they are needed
With a shared data model, you can simply move all of your data attributes out of the class they are currently in, and reference them through the data model - then if you have the same data model in your custom cell class, you can do whatever you can do in the main view. To keep your GUI and processing logic separate, you can put everything in the data model
dataModel.refreshTable()
and then define a function in the data model that takes care of your table view - you could save all current edits to the data, and reload, without having to put any of that logic in individual cell classes
for updating any record in core data try to use this code:
let managedObjectContext:NSManagedObjectContext=(UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
let req=NSFetchRequest(entityName: "Entity Name")
req.returnsObjectsAsFaults=false
let result:NSArray=try! managedObjectContext.executeFetchRequest(req)
if result.count>0{
let res=result[Int(indexPath.row!]as! NSManagedObject
res.setValue("The Value", forKey: "Key Name")
do {
try managedObjectContext.save()
} catch _ { print("Update Unsuccessful") }
You must use [unowned self] in within the closure. See Apple's docs. This is how it's done. See also CoreDataKit, a 28-star github repo Core Data stack. It's available on cocoapods and honestly, why not just drop something like this into your app and not worry about "unowned selves" and other philosophical brain twisters, eh?
if let moc = self.managedObjectContext{
moc.performBlockAndWait({
/* In here we are in a closure (Swift version of a block), so "self" is problematic. Use unowned self instead (in objective c you'd have to do weak self). */
[unowned self] in
for result in results!{
result.setValue(self.toggQuestion.on,
forKey: (self.surveyQuestionReference?.property)!)
}
})
}
Can I access a struct inside of an array that is defined in the application delegate from a ViewController?
I get the error:
'Any' does not have a member named 'title' in XCode 6.2
What is the syntax for accessing a structure inside of the array?
//AppDelegate.swift
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
struct TodoItem {
var title: String
}
var todoItem = TodoItem(
title: "Get Milk")
var myArray: [Any] = []
And then in the ViewController
//
// ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
//here I'm adding the struct to the array
let myTodoItem = delegate.myArray.append(delegate.todoItem)
//how do I access the items in the struct?
println(delegate.myArray[0].title)
You can access structures in array same as you would classes.
Your issue is that you explicitly tell that the array contains Any Objects. Type the array to be of type MyStruct and it works fine:
var myArray: [MyStructure] = [];
alternatively, if you can't modify the array declaration, cast:
myValueIWannaRead = (myArray[0] as MyStruct).myValueIWannaRead
You can define the array to store the type you desire :
var myArray: [TodoItem] = [];
Then you can access the instances properly.
The Any type denotes an object of an unknown type (class/primitive type ) and during compilation time there is no way to know which type the instance accessed will be of.
In Swift be always as specific as possible.
If the array contains only items of type TodoItem, declare it respectively.
var myArray: [TodoItem] = []
Any is a kind of placeholder, the compiler has no idea what dynamic type it is.
Why do you have to add the struct TodoItem to AppDelegate? in my point of view, it would be better to create it in the same file that you have your view controller or - even better - create a new Swift file named TodoItem.swift holding the struct.
Then after you have moved your struct to a new file or inside your View Controller file, but outside your ViewController class. You can just call:
let myTodoItem = TodoItem(title: "Get Milk") // declaring TodoItem
var myTodoItemArray : [TodoItem] = [] // declaring TodoItem array
myTodoItemArray.append(myTodoItem) // Appending to the array
// then you can set it by calling the array only member
let todoItem = myTodoItemArray[0] as! TodoItem
let title = todoItem.title
// Or you can just call the toDo Item itself
let title = myTodoItem.title
If you want to comunicate this data between two different classes, I would suggest use Delegation by creating a protocol or Notifications with NSNotifications.
I hopw this helps, happy coding.
EDIT: Fixed some minor error in the code
Solution1: Your struct is defined under AppDelegate class so, you will have to parse it like this;
//** Swift 1.2, xCode 6.3.1**//
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
//here I'm adding the struct to the array
delegate.myArray.append(delegate.todoItem)
//how do I access the items in the struct?
println((delegate.myArray[0] as! AppDelegate.TodoItem).title)
Solution2: Change the data type of your array Any to TodoItem
var myArray: [TodoItem] = []
and then, it would work;
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
//here I'm adding the struct to the array
delegate.myArray.append(delegate.todoItem)
//how do I access the items in the struct?
println(delegate.myArray[0].title)
I have a test app with two entities, folder with a relationship of To Many with another object called List.
In my storyboard I have a tableview controller with the list of created folders. When tapping on a folder I segue to another TableView passing on the selectedFolder which should display the List ordered set saved to the selectedFolder. I have a modal that appears to add a item to the List.
Unfortunately I have not been able to save a List to the selectedFolder ordered set. I receive an error when executing the save function unrecognized selector sent to instance this error is because of the following line:
selectedFolder.list = list.copy() as! NSOrderedSet
I am not sure what I am doing wrong with the save function and was wondering if anyone could help, it would be much appreciated.
Folder Subclass:
class Folder: NSManagedObject {
#NSManaged var title: String
#NSManaged var details: String
#NSManaged var date: NSDate
#NSManaged var list: NSOrderedSet
}
List Subclass
class List: NSManagedObject {
#NSManaged var item: String
#NSManaged var folder: Event
}
Modal View to add List to selected Folder ordered set.
class PopoverViewController: UIViewController {
//selectedFolder passed from segue. Works fine displays title of folder
var selectedFolder: Folder!
#IBOutlet weak var popoverTextField: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
self.popoverTextField.becomeFirstResponder()
// Do any additional setup after loading the view.
}
#IBAction func addListItem(sender: AnyObject) {
//Get the context
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
//get entity details
let entity = NSEntityDescription.entityForName("List", inManagedObjectContext: moc!)
//Create the managed object to be inserted
let list = List(entity: entity!, insertIntoManagedObjectContext: moc!)
// Add Text
list.item = popoverTextField.text
//Insert the new checklist into the folder set
var folder = selectedFolder.list.mutableCopy() as! NSMutableOrderedSet
folder.addObject(list)
selectedFolder.list = list.copy() as! NSOrderedSet
//Error check & Save
var error: NSError?
if moc!.save(&error){
println("Could not save: \(error)")
}
}
var folder = selectedFolder.list.mutableCopy() as! NSMutableOrderedSet
folder.addObject(list)
selectedFolder.list = list.copy() as! NSOrderedSet
The last assignment makes no sense because list is a List object and not an (ordered) set, and btw. this computes a folder variable
which is then ignored.
What you probably meant (and this should work) is
var folder = selectedFolder.list.mutableCopy() as! NSMutableOrderedSet
folder.addObject(list)
selectedFolder.list = folder
As already mentioned in a comment, adding to an ordered relationship
is a bit tricky, but can be done with Key-Value coding as
let mutableChecklist = selectedFolder.mutableOrderedSetValueForKey("list")
mutableChecklist.addObject(list)
(Compare Exception thrown in NSOrderedSet generated accessors and Setting an NSManagedObject relationship in Swift.)
In the case of a one-to-many relationship however, the easiest way
is to set the other direction of the relationship:
list.folder = selectedFolder
Recently, I'm learning about CoreData in Swift. My purpose is about to send the value of one object "editContact" in class "AllContactTableViewController" as code below.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let appDelegate:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedObjectContext:NSManagedObjectContext = appDelegate.managedObjectContext!
let entity = NSEntityDescription.entityForName("Contact", inManagedObjectContext: managedObjectContext)
var request = NSFetchRequest(entityName: "Contact")
request.returnsObjectsAsFaults = false
var results: NSArray = managedObjectContext.executeFetchRequest(request, error: nil)!
let editContactView : EditContactTableViewController = EditContactTableViewController()
var editContact : Contact = results.objectAtIndex(indexPath.row) as Contact
editContactView.editContact = editContact
println("\(editContactView.editContact)")
}
to another viewcontroller called "EditContactTableViewController" (as code below)
class EditContactTableViewController: UITableViewController {
var editContact : Contact!
override func viewDidLoad() {
super.viewDidLoad()
firstNameField.text = self.editContact.firstName
lastNameField.text = self.editContact.lastName
phoneField.text = self.editContact.phone
emailField.text = self.editContact.email
companyField.text = self.editContact.company
addressField.text = self.editContact.address
}
}
then it caused the error as "fatal error: unexpectedly found nil while unwrapping an Optional value"
in the log. It seems the value of object called "editContact" in this class has changed to nil.
Do you have idea how to fix this problem?
First, use a NSFetchedResultsController. There are many advantages, and you also will safely get the object you need with
self.fetchedResultsController.objectAtIndexPath(indexPath)
If your contact is displayed in the cell you must already have fetched it. So it does not make any sense to fetch it again. Also you have an unused variable (entity) in your very verbose but unnecessary code.
The best way is to use a segue in storyboard from the cell to the edit controller. You can then set up the object in prepareForSegue.
One nice setup is to subclass the table view cell and give it a property of type Contact. The displaying of the attributes in the cell can be handled by the subclass, helping you to uncluttered the table view controller. You can then easily retrieve the object in prepareForSegue from the sender parameter.