How do I use NSManagedObject and get all the data back? - ios

I'm having trouble getting my NSManagedObject to work.
In one file I have:
import UIKit
import CoreData
#objc(Location)
class Location: NSManagedObject {
#NSManaged var title:String
#NSManaged var lat:NSDecimalNumber
#NSManaged var lon:NSDecimalNumber
#NSManaged var course:NSDecimalNumber
#NSManaged var alt:NSDecimalNumber
}
In a TableView class I have:
...
var locations:NSArray = [Location]()
override func viewDidLoad() {
super.viewDidLoad()
locations = self.getAllLocations()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var logCell:TableViewCell = self.tableView.dequeueReusableCellWithIdentifier("loggedCell") as TableViewCell
logCell.loggedTitle.text = locations[indexPath.row].title
if let lat = locations[indexPath.row].lat {
println(lat)
}
return logCell
}
func getAllLocations() -> [Location] {
let appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Locations")
request.returnsObjectsAsFaults = false
if let results = context.executeFetchRequest(request, error: nil) {
return results as [Location]
} else {
// Failed, return empty error. Alternatively, report error.
return []
}
}
...
I am able to retrieve the values from CoreData. If I println( locations[indexPath.row] ) it's all there. However in func tableView I am only able to get the title. lat, lon, everything but title errors out with 'AnyObject' does not have a member named 'lat', as in the lines:
if let lat = locations[indexPath.row].lat {
println(lat)
}
I'm sure it's something basic as I am new to Swift and iOS development. I would appreciate a point in the right direction please.

It's partly about Swift's strict typing and partly about the interplay between Swift types and Cocoa (Objective-C) types.
The problem here is that you have typed locations as an NSArray. That's a Cocoa collection, not a Swift collection. Swift doesn't know what kind of thing is inside an NSArray.
You may know (or believe) that what's at locations[indexPath.row] is a Location, therefore, but Swift doesn't know that. You have to tell it, by casting (with as).
Alternatively, if there is no reason not to do so, type locations as a Swift array of Location, a [Location]. Now Swift knows what's in it. I should also point out that as things stand, your initializer is pointless:
var locations:NSArray = [Location]()
You start by making a Swift array of Location, yes, but then by typing locations as an NSArray and assigning into it, you throw away all knowledge that this thing is supposed to contain only Location objects.

Related

How to retrieve data from Realm's Results instance?

I just start to learn Realm data persistence, I start it from a iOS test project.
The realm object is declared like this:
class AchievementRecord: Object {
dynamic var dateID:String = "1111-00-00"
dynamic var date:String = "0000-00-00"
dynamic var apple:Int = Int(0)
override static func primaryKey() -> String? {
return "dateID"
}
}
I initialise the object in View Controller's viewDidLoad() method as this:
class AchievementRecord: Object {
dynamic var dateID:String = "1111-00-00"
dynamic var date:String = "0000-00-00"
dynamic var apple:Int = Int(0)
override static func primaryKey() -> String? {
return "dateID"
}
}
then I declare another function to obtain the save data as:
let appleOn_05 = defaultRealm.objects(AchievementRecord.self).filter("dateID = '05-06-2017'")
print(appleOn_05)
In the console, Xcode says:
Because I need to retrieve the apple's number, which is 22 in the console. How can I retrieve the apple's number to demo it on the screen, how can I do it? Thanks in advance.
Results works like native Swift collections in many ways. If you are fetching a single object, you can just access it with Results.first let appleOn_05 = defaultRealm.objects(AchievementRecord.self).filter("dateID = '05-06-2017'").first
Subclasses of Object work like any other native class instance in Swift, so you can access their properties using the dot syntax.
let apple = appleOn_05.apple
Combining the two:
if let appleOn_05 = defaultRealm.objects(AchievementRecord.self).filter("dateID = '05-06-2017'").first {
let apple = appleOn_05.apple
}

Update Core Data from a UISwitch inside a TableViewCell

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)!)
}
})
}

Swift Custom NSMutableArray Sorting

I have a custom NSMutableArray and i want to sort it; I've looked over the internet but nothing solve my problem. what I have is the following Locations.swift
import UIKit
class Location: NSObject {
var id:String = String()
var name:String = String()
var distance:Float = Float()
}
then I create a Mutable Array from this class in my ViewController.swift
class CustomViewController: UIViewController{
var locations: NSMutableArray!
override func viewDidLoad() {
super.viewDidLoad()
locations = NSMutableArray()
locations = SqLite.getInstnace().getLocations()//get values from DB، objects of Location
}
}
how I can sort locations by the distance value??
I would make location an array of Location objects, and then sorting becomes easy with sortInPlace.
class CustomViewController: UIViewController{
var locations = [Location]()
override func viewDidLoad() {
super.viewDidLoad()
locations = SqLite.getInstnace().getLocations().flatMap { $0 as? Location }
}
}
Now you can sort locations like this:
locations.sortInPlace { $0.distance < $1.distance }
You could always use the built in sort function:
let locations = SqLite.getInstnace().getLocations().sort({
return ($0 as! Location).distance < ($1 as! Location).distance
})
if SqLite.getInstnace().getLocations() return an array as [Location] and not a NSArray then you could just do this since it will already know the elements types:
let locations = SqLite.getInstnace().getLocations().sort({
return $0.distance < $1.distance
})
This should return a sorted array in ascending order if you want descending just use > instead.
Also, there is really no need to use NSMutableArrays in Swift. Just use [Location].
Edit:
NSMutableArray can be used in swift but it considered objective-c like where as using [OBJECT_TYPE] is conventional for swift.
Take a look at the Swift 2 book array segment. None of the examples use NSArray or NSMutable array.
By declaring something with var in swift makes it mutable where as let makes something immutable

Memory leak in Core Data fetch request with Swift

I've come across a memory leak when I make a Core Data fetch request using Swift. However, I make an almost identical fetch request in a different part of the app, but it doesn't cause a leak. In both cases, the fetch requests are made in viewDidLoad of a view controller, and the results of the fetch request are assigned to an optional property of the view controller.
Here's the method for the fetch request that does not cause any leaks:
class LocationFilter {
//Lots of other code...
class func getAllPlacesOfRegionType<T: Region>(regionType: RegionType) -> [T] {
let fetchRequest = NSFetchRequest(entityName: regionType.rawValue)
var places: [T]
do {
places = try CoreDataStack.sharedInstance.context.executeFetchRequest(
fetchRequest) as! [T]
} catch let error as NSError {
NSLog("Fetch request failed: %#", error.localizedDescription)
places = [T]()
}
places.sortInPlace({ (firstPlace, nextPlace) -> Bool in
//Ingenious sorting code...
})
return places
}
}
This method is called in viewDidLoad of a viewController, and the result is assigned to the property var allRegions: [Region]? without any leaks. Here's the code:
class PlacesTableViewController: UITableViewController {
var allRegions: [Region]?
#IBOutlet weak var segmentedRegions: UISegmentedControl!
#IBAction func selectRegionSegment(sender: UISegmentedControl) {
// When the segmented control is tapped, the appropriate list will be loaded.
switch sender.selectedSegmentIndex {
case 0: //Country Segment
allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.Country)
case 1: //States segment
allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.Province)
case 2: //Cities segment
allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.City)
case 3: //Addresses segment
allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.Address)
default:
break
}
// Then reload the cells with animations.
let index = NSIndexSet(index: 0)
tableView.reloadSections(index, withRowAnimation: UITableViewRowAnimation.Automatic)
}
override func viewDidLoad() {
super.viewDidLoad()
selectRegionSegment(segmentedRegions)
}
}
The following method is called in viewDidLoad of a different viewController to set the property var allDays: [Day]!.
class DateFilter {
//Lots of other code in the class...
class func getAllDays() -> [Day] {
let fetchRequest = NSFetchRequest(entityName: "Day")
let days: [Day]
do {
days = try CoreDataStack.sharedInstance.context.executeFetchRequest(
fetchRequest) as! [Day]
} catch let error as NSError {
NSLog("Fetch request failed: %#", error.localizedDescription)
days = [Day]()
}
return days
}
}
This is where it is called:
class SearchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var allDays: [Day]!
override func viewDidLoad() {
super.viewDidLoad()
allDays = DateFilter.getAllDays()
let backgroundView = UIView(frame: CGRectZero)
tableView.tableFooterView = backgroundView
tableView.backgroundColor = UIColor.groupTableViewBackgroundColor()
}
}
Xcode instruments detect a memory leak when this is called. Supposedly the responsible library is libswiftFoundation.dylib, and the responsible frame is static Array<A>._forceBridgeFromObjectiveC<A>(NSArray, result:inout [A]?) -> (). When I look at Cycles & Roots, it shows an NSArray at the root, with +16 list: (null) and +24 [no ivar]: (null) branching off.
Am I doing something wrong with how I store the results of my fetch request? Or is this a bug in how Swift interacts with Core Data?
Edit: Tidied up code in accordance with Mundi's suggestion.
Edit 2: Added code that calls the fetch request functions.
After trying lots of things, I'm pretty sure it's a bug in how Core Data converts NSArray to Swift Arrays when fetching my Day entities. Perhaps it has to do with the relationships or attributes of the Day entities. I'll continue to look into it.
For now, I found a work around. Instruments kept pointing back to the libswiftFoundation method for converting NSArray to Array, and the Cycles & Roots kept showing an NSArray with no ivar. Based on my research this has to do with the initialization of the NSArray created by the fetch request, which is converted behind the scenes to a Swift array. Since I can't change this, I made a new Array from the fetch results:
override func viewDidLoad() {
super.viewDidLoad()
let fetchResults: [Day] = DateFilter.getAllDays()
allDays = fetchResults.map({$0})
let backgroundView = UIView(frame: CGRectZero)
tableView.tableFooterView = backgroundView
tableView.backgroundColor = UIColor.groupTableViewBackgroundColor()
}
And magically, the memory leak is gone! I'm not entirely sure why the fetchResults array is leaky, but that seems to be the source of the problem.
Just experienced this exact same issue. I went around adding weak self to every capture list, to no avail. Turned out to be a name space collision where the system failed to infer which array (or maybe its type) I was talking about (because they shared the same name). I renamed the below 'foundCategories' from 'categories' which was a property of my view controller.
func fetchCategoriesAndNotes() {
categories = []
fetchEntity("Category", predicates: nil) { [weak self] (found) -> Void in
guard let foundCategories = found as? [Category] else { return }
for category in foundCategories {} ... } }
Memory leak is gone.
I think your fetch code is overly verbose. In particular, I think that assigning the fetch result to another variable causes some sort of conversion from Objective-C class NSArray (which is the result type of a fetch request) which in some way causes your leak. (I also do not fully understand why but I think it also has to do with the fact that this is a class function defining variables.)
I would suggest simplifying your code.
let request = NSFetchRequest(entityName: "Day")
do { return try context.executeFetchRequest(request) as! [Day]) }
catch { return [Day]() }

Error: Could not find overload for 'title' that accepts the supplied arguments

Currently I have a subclass of NSManaged object called Folder with property called item that is of type NSSet. I have a function to return a NSMutableArray and I am trying to display the data in a tableview (and then rearrange the order displayed in the table).
class Folder: NSManagedObject {
#NSManaged var title: String
#NSManaged var date: NSDate
#NSManaged var item: NSSet
func itemMutableArray() -> NSMutableArray {
return NSMutableArray(array: (item.allObjects as! [Checklist]).sorted{ $0.date.compare($1.date) == NSComparisonResult.OrderedAscending } )
}
TableViewController:
var itemMutableArray: NSMutableArray!
itemMutableArray = folder.itemMutableArray()
cell.textLabel?.text = itemMutableArray[indexPath.row].title //Error
Unfortunately this returns an error in my tableview when using this function.
Error could not find overload for 'title' that accepts the supplied arguments
Ultimately what I am trying to achieve is to display the data and move the cells around to change the order of the NSSet.
table.didMoveCellFromIndexPathToIndexPathBlock = {(fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) -> Void in
let delegate:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = delegate.managedObjectContext!
context.deleteObject(self.itemArray[indexPath!.row] as NSManagedObject )
self.itemArray.exchangeObjectAtIndex(toIndexPath.row, withObjectAtIndex: fromIndexPath.row)
}
PS: Originally I had a function to return an array of the object but I was unable to modify the order of the NSSet as they are not ordered.
func itemArray() -> [Item] {
let sortDescriptor = NSSortDescriptor(key: "date", ascending: true)
return item.sortedArrayUsingDescriptors([sortDescriptor]) as! [Item]
}
Does anybody have any suggestions with where I am currently going wrong ?
The problem with the expression itemMutableArray[indexPath.row].title is that, because you have typed itemMutableArray as an NSMutableArray, itemMutableArray[indexPath.row] is an AnyObject. Thus, Swift has no reason to believe that this thing has a title property. You need to cast it to something that does have a title property (though it would be much better to avoid the use of NSMutableArray completely, if possible, since a Swift array is mutable).

Resources