I'm using Realm for my Note-taking-app.
I used core data before and I am now migrating core data to realm, but i got trouble! reordering objects like this causes error.
do {
let realm = try Realm()
let source = myNote[sour!.row]
try realm.write() {
myNote.remove(objectAtIndex: sourceIndexPath!.row)
myNote.insert(source, at: destensionIndexPath!.row)
}
}
catch {
print("handle error")
}
So I added
orderPosition property to my object
dynamic var orderPosition: Int = 0
and changed tableView moveRowAtIndexPath to this
ReorderingRealmResultsInTableView.swift
but it didn't helped a lot.
so How can I reorder objects in realm?
I'd encourage you to store ordered items in a List rather than sorting based on an orderPosition property.
Storing the index manually will be much less performant when moving an item, because all objects between the "old index" and "new index" will need to be mutated to account for the change.
You can then use List.move(from:to:) to move an object from one index to another, which should correspond directly to the indices in the table view you're reordering.
Here's a tutorial you can follow guides you through building a task management app, including support for reordering tasks: https://realm.io/docs/realm-mobile-platform/example-app/cocoa/
A List is certainly efficient and clean, though I wasn't sure how I would sync it through a server. So in my case I'm using orderPosition: Double, and the value is calculated as the middle of the two existing orderPositions that the object is inserted between. Also keep in mind that you can perform the write without updating the tableView from the notification: try! list.realm?.commitWrite(withoutNotifying: [notificationToken!]).
As others suggested, List is the solution. Here is the example to implement the approach on Swift 5:
import UIKit
import RealmSwift
// The master list of `Item`s stored in realm
class Items: Object {
#objc dynamic var id: Int = 0
let items = List<Item>()
override static func primaryKey() -> String? {
return "id"
}
}
class Item: Object {
#objc dynamic var id: String = UUID().uuidString
#objc dynamic var name = ""
}
class ViewController: UITableViewController {
let realm = try! Realm()
var items = RealmSwift.List<Item>()
override func viewDidLoad() {
super.viewDidLoad()
// initialize database
var itemsData = realm.object(ofType: Items.self, forPrimaryKey: 0)
if itemsData == nil {
itemsData = try! realm.write { realm.create(Items.self, value: []) }
}
items = itemsData!.items
// temporarily add new items
let newItem1 = Item()
newItem1.name = "Item 1"
let newItem2 = Item()
newItem2.name = "Item 2"
try! realm.write {
items.append(newItem1)
items.append(newItem2)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
...
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
try! items.realm?.write {
items.move(from: sourceIndexPath.row, to: destinationIndexPath.row)
}
}
}
You can find example applications for both iOS and macOS in GitHub Repo (https://github.com/realm/realm-cocoa) under examples/, demonstrating how to use many features of Realm like migrations, how to use it with UITableViewControllers, encryption, command-line tools and much more.
Related
I would like to understand why fetchBatchSize is not working correctly with NSFetchRequest.
On initial fetch, after fault array loaded, the data array are loaded in a loop by batchSize until all data is loaded rather than until just the data required is loaded. using coreData instruments or editing run scheme clearly shows all items data loaded in loops of batchSize number of items until all data loaded instead of only loading data of those rows appearing in tableView.
Expected result:
Expect all items in fault array to be loaded followed by only first batch as data to be loaded.
Actual result:
All items loaded in fault array and then all data loaded in looped batches of batch size prior to any scrolling.
Here is the sample project viewController I have created to demonstrate this:
import UIKit
import CoreData
class BatchTestTableViewController: UITableViewController {
var context: NSManagedObjectContext!
var items: [Item]?
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "BatchTableViewCell", bundle: nil), forCellReuseIdentifier: "BatchTableViewCell")
let appDelegate = UIApplication.shared.delegate as! AppDelegate
self.context = appDelegate.persistentContainer.viewContext
self.initializeItems()
if items != nil {
if items!.count == 0 {
for i in 0..<10000 {
let entityDescription = NSEntityDescription.entity(forEntityName: "Item", in: context)
let item = Item(entity: entityDescription!, insertInto: self.context)
item.objectId = UUID().uuidString
item.itemId = UUID().uuidString
item.itemName = "\(i)"
}
try? context.save()
self.initializeItems()
}
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if items != nil {
return items!.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BatchTableViewCell", for: indexPath) as! BatchTableViewCell
let item = self.items![indexPath.row]
cell.itemNameLabel.text = item.itemName
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
// MARK: - Helper Methods
func initializeItems() {
let request = NSFetchRequest<Item>(entityName: "Item")
let messageSort = NSSortDescriptor(key: "itemName", ascending: true)
request.fetchBatchSize = 20
request.sortDescriptors = [messageSort]
do {
self.items = try context.fetch(request)
} catch {
print("Could not fetch \(error)")
}
}
}
And here is the read out from CoreData Instruments:
From the above image of coredata instrument it can be seen that all data is loaded in 20 batch size loop until all 10,000 items are loaded prior to any scrolling of the tableView.
fetchBatchSize doesn't limit your data fetching. It enables fetchRequest to fetch data in a batch of 20 in your case. You can use this feature to restrict the working set of data in your application. In combination with fetchLimit, you can create a subrange of an arbitrary result set. You should use fetchLimit together
request.fetchLimit = 20
From Apple doc
When the fetch is executed, the entire request is evaluated and the identities of all matching objects recorded, but only data for objects up to the batchSize will be fetched from the persistent store at a time. The array returned from executing the request is a proxy object that transparently faults batches on demand. (In database terms, this is an in-memory cursor.)
I am using Realm 3 and Swift 4 and still new to Realm and Swift. Need a guidance here :)
Given this realm model
class Person: Object, Mappable {
let dog = List<Dog>()
required convenience init?(map: Map) {
self.init()
}
}
How can I get the dog count of each person?
What i want to achieve is there are multiple sections on my table view and for each person there will be dog list for the respective person.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let owner = realm.objects(Person.self)
return owner.dog.count // This is not working. What should I change it to ?
}
I have been searching through the web but couldn't find any guide on such problem.
Any help given is highly appreciated. Thanks!
I would probably rename the var dog to var dogs to indicate it may contain more than one dog.
Here's a straightforward solution that prints the owners name and dog count.
do {
let realm = try Realm()
let people = realm.objects(Person.self)
for person in people {
let name = person.personName
print(name)
let dogs = person.dogs
print(dogs.count)
}
} catch let error as NSError {
print(error.localizedDescription)
}
A Realm List has Array functionality so it can be iterated over, count is available and can also be filtered.
And the code in your question
let owners = realm.objects(Person.self)
will assign all Person objects in Realm to the owners var (an array of Person) so it's not one person it would be all of them which is why it doesn't have a .dog property.
You will need to establish which person is supposed to be in each section of the tableView, query Realm for that person and then return person.dogs.count.
The code to determine how you determine which person goes in which section isn't shown in the original question but lets assume you want person 0 in section 0 (there may be an ordering issue so this is just conceptual)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let owners = realm.objects(Person.self).sorted('name')
let thisOwner = owners[section]
let dogCount = thisOwner.dogs.count
return dogCount
}
Have you tried query logic.
realm.objects(Person).filter("dogs.#count > 0")
or
realm.objects(Person).filter("ANY dogs.#count > 0")
Is there a way to insert new items at the 0 index to the Realm container? I don't see an insert method in the Realm class.
Do I need to use Lists? If the answer is yes, how can I restructure the following code to be able to use Lists and keep the List in constant sync with the Realm container. In other words I'm having a hard time coming up with a good way to keep the Realm container and the List with the same items when adding and removing.
In the following code new items are entered at the last index. How can I restructure it to be able to insert items at the 0 index?
Model class
import RealmSwift
class Item:Object {
dynamic var productName = ""
}
Main ViewController
let realm = try! Realm()
var items : Results<Item>?
var item:Item?
override func viewDidLoad() {
super.viewDidLoad()
self.items = realm.objects(Item.self)
}
func addNewItem(){
item = Item(value: ["productName": productNameField.text!])
try! realm.write {
realm.add(item!)
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath)
let data = self.items![indexPath.row]
cell.textLabel?.text = data.productName
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete{
if let item = items?[indexPath.row] {
try! realm.write {
realm.delete(item)
}
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
}
}
Ideally this is what I would like to be able to do when inserting new items in the addNewItem() method...
item = Item(value: ["productName": inputItem.text!])
try! realm.write {
realm.insert(item!, at:0)
}
Adding a sortedIndex integer property that lets you manually control the ordering of objects is definitely one of the more popular ways to order objects in Realm, however it's quite inefficient. In order to insert an object at 0, you'll then need to loop through every other object and increment its ordering number by 1, meaning you'll end up needing to touch every object of that type in your database in order to do it.
The best practice for this sort of implementation is to create another Object model subclass that contains a List property, keep one instance of it in Realm, and to then add every object to that. List properties behave like normal arrays, so it's possible to very quick and efficient to arrange objects that way:
import RealmSwift
class ItemList: Object {
let items = List<Item>()
}
class Item: Object {
dynamic var productName = ""
}
let realm = try! Realm()
// Get the list object
let itemList = realm.objects(ItemList.self).first!
// Add a new item to it
let newItem = Item()
newItem.productName = "Item Name"
try! realm.write {
itemList.items.insert(newItem, at: 0)
}
You can then use the ItemList.items object directly as the data source for your table view.
They're at least two ways to do this:
you can manually add priority as number in your class for.ex:
class Task: Item {
dynamic var something = ""
dynamic var priority = 0
}
add to realm:
//you can make priority 2,3,4 etc - 0 will be at the top
let task = Task(something: "Something", priority: 0)
and after retrieve objects:
var tasks: Results<Task>!
viewdidLoad(){
let realm = try! Realm()
tasks = realm.objects(Task.self).sorted(byKeyPath: "priority")
}
or you can make date like this:
class Task: Item {
dynamic var something = ""
dynamic var created = Date()
override class func indexedPriority() -> [String]{
return ["created"]
}
}
add to realm:
let task = Task(something: "Something")
and after retrieve objects:
var tasks: Results<Task>!
viewdidLoad(){
let realm = try! Realm()
tasks = realm.objects(Task.self).sorted(byKeyPath: "created")
}
first one you must update yourself to assign priority, second one will be automatically updated
I am a newbie to swift and firebase, I am trying to populate my tabelview with firebase data. When I run the program, nothing shows up in tableview. Any help would be gladly appreciated. This is what I got do far, tried to read the documents, but its not helping.
import UIKit
import Firebase
import FirebaseUI
class ChurchTableViewController: UITableViewController {
let firebase = Firebase(url:"https://.....com/")
var items = [NSDictionary]()
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
//self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func viewDidAppear(animated: Bool) {
//MARK: Load data from firebsr
firebase.observeEventType(.Value, withBlock: { snapshot in
print(snapshot.value)
}, withCancelBlock: { error in
print(error.description)
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let dict = items[indexPath.row]
cell.textLabel?.text = dict["ChurchName"] as? String
return cell
}
You've created the observer for when some value changes in your Firebase DB, but in your closure you need to add the new items and of course reload your UITableView to synchronize the data in your app, see the following code to see a sample of how to do it with a sample data type too:
var items = [GroceryItem]()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
firebase.observeEventType(.Value, withBlock: { snapshot in
var newItems = [GroceryItem]()
for item in snapshot.children {
let itemType = GroceryItem(snapshot: item as! FDataSnapshot)
newItems.append(itemType)
}
// update your item with the new ones retrieved
self.items = newItems
// reload the data
self.tableView.reloadData()
})
}
In the below struct you can see a sample of how you can create your data type from the data returned from Firebase
GroceryItem
struct GroceryItem {
let key: String!
let name: String!
let addedByUser: String!
let ref: Firebase?
var completed: Bool!
// Initialize from arbitrary data
init(name: String, addedByUser: String, completed: Bool, key: String = "") {
self.key = key
self.name = name
self.addedByUser = addedByUser
self.completed = completed
self.ref = nil
}
init(snapshot: FDataSnapshot) {
key = snapshot.key
name = snapshot.value["name"] as! String
addedByUser = snapshot.value["addedByUser"] as! String
completed = snapshot.value["completed"] as! Bool
ref = snapshot.ref
}
}
For a deeper knowledge about how to use Firebase you can read this very good tutorial:
Firebase Tutorial: Getting Started
I hope this help you.
Check that you have set your Tableview's delegate and datasource properly, to do this, go to interface builder, cmd + right click on your tableview and drag over to the yellow heading icon in interface builder.
You should see two options, 'datasource' and 'delegate', make sure that they are both checked and then rerun your app, you should see the table populate with whatever data you've loaded
You've got three issues
1) Your not populating a datasource for your tableview. This is typically an array that is stored in the class and because it's by .value you will need to iterate over those values to get to each child nodes data
2) You are observing by .value. This will return everything in the node, all children, their children etc so you won't be able to directly read it as a string value unless that's all the node contains, as in a single key:value pair, otherwise all of they key:value pairs will be read.
3) Firebase is asynchronous so within the observe block, you need to populate the array, and then re-load the tableview
Here's the solution:
Given a structure
users
user_id_0
name: "Biff"
user_id_1
name: "Buffy"
user_id_2
name: "Skip
here's the associated code to read in each name and populate a namesArray
var namesArray: [String] = []
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
for child in snapshot.children {
let name = child.value["name"] as! String
namesArray.append(name)
}
self.myTableView.reloadData()
})
substitute your items array for the namesArray.
They key is to let Firebase load the data asynchronously before telling the tableView to refresh itself, and when using .Value, ensure you iterate over all of the children in that node with snapshot.children
This is happened because there is no data in your items array. So first inside your viewDidAppear method you need to append your Firebase data dictionaries into items array and then call tableView.reloadData().
Also check your Firebase database url is correct and you need to fetch and store data in proper format while appending to items array.
Attached at very bottom of this question is my inventory controller file. My problem is I'm getting duplicate results in all the sections. I narrowed down the reason to
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
My code in that function does not account for how many rows there are in each section. As such I'm just printing out the same duplicate results every section.
The actual question is listed after the images below...
Refer to images below:
I also have the ability to change the index from my settings menu so it can index by numbers, like 0-9. Refer to image below:
That said, I currently load the data from Core Data. Attached is reference image of the entities I use and there relationships.
The Question:
My question is, how can I get the results from coreData to be sorted into the A,B,C type sections or 1,2,3 sections so that navigating the table will be simple.
My hunch is the line that says let inventoryRecords = try moc.executeFetchRequest(inventoryFetchRequest) as? [Inventory] needs a sort descriptor to sort based on how I like, but how I then take the data and put into the correct array structure to split into the sections I need...I have no idea.
globals.swift
import Foundation
import CoreData
//Array of Inventory & Store Core Data Managed Objects
var g_inventoryItems = [Inventory]()
var g_storeList = [Store]()
var g_appSettings = [AppSettings]()
var g_demoMode = false
InventoryController.swift
import UIKit
import CoreData
class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var inventoryTable: UITableView!
var numberIndex = ["0","1","2","3","4","5","6","7","8","9"]
var letterIndex = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
var moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext //convinience variable to access managed object context
// Start DEMO Related Code
func createInventoryDummyData(number: Int) -> Inventory{
let tempInventory = NSEntityDescription.insertNewObjectForEntityForName("Inventory", inManagedObjectContext: moc) as! Inventory
tempInventory.name = "Test Item # \(number)"
tempInventory.barcode = "00000000\(number)"
tempInventory.currentCount = 0
tempInventory.id = number
tempInventory.imageLargePath = "http://website.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.imageSmallPath = "http://website.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.addCount = 0
tempInventory.negativeCount = 0
tempInventory.newCount = 0
tempInventory.store_id = 1 //belongs to same store for now
//Select a random store to belong to 0 through 2 since array starts at 0
let aRandomInt = Int.random(0...2)
tempInventory.setValue(g_storeList[aRandomInt], forKey: "store") //assigns inventory to one of the stores we created.
return tempInventory
}
func createStoreDummyData(number:Int) -> Store{
let tempStore = NSEntityDescription.insertNewObjectForEntityForName("Store", inManagedObjectContext: moc) as! Store
tempStore.address = "100\(number) lane, Miami, FL"
tempStore.email = "store\(number)#centraltire.com"
tempStore.id = number
tempStore.lat = 1.00000007
tempStore.lng = 1.00000008
tempStore.name = "Store #\(number)"
tempStore.phone = "123000000\(number)"
return tempStore
}
// End DEMO Related Code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
print("InventoryController -> ViewDidLoad -> ... starting inits")
//First check to see if we have entities already. There MUST be entities, even if its DEMO data.
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
let storeFetchRequest = NSFetchRequest(entityName: "Store")
do {
let storeRecords = try moc.executeFetchRequest(storeFetchRequest) as? [Store]
if(storeRecords!.count<=0){
g_demoMode = true
print("No store entities found. Demo mode = True. Creating default store entities...")
var store : Store //define variable as Store type
for index in 1...3 {
store = createStoreDummyData(index)
g_storeList.append(store)
}
}
let inventoryRecords = try moc.executeFetchRequest(inventoryFetchRequest) as? [Inventory]
if(inventoryRecords!.count<=0){
g_demoMode = true
print("No entities found for inventory. Demo mode = True. Creating default entities...")
var entity : Inventory //define variable as Inventory type
for index in 1...20 {
entity = createInventoryDummyData(index)
g_inventoryItems.append(entity)
}
print("finished creating entities")
}
}catch{
fatalError("bad things happened \(error)")
}
print("InventoryController -> viewDidload -> ... finished inits!")
}
override func viewWillAppear(animated: Bool) {
print("view appearing")
//When the view appears its important that the table is updated.
//Look at the selected Store & Use the LIST of Inventory Under it.
inventoryTable.reloadData()//this is important to update correctly for changes that might have been made
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
print("inventoryItemControllerPrepareForSegueCalled")
if segue.identifier == "inventoryInfoSegue" {
let vc = segue.destinationViewController as! InventoryItemController
if let cell = sender as? InventoryTableViewCell{
vc.inventoryItem = cell.inventoryItem! //sets the inventory item accordingly, passing its reference along.
}else{
print("sender was something else")
}
}
}
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
//This scrolls to correct section based on title of what was pressed.
return letterIndex.indexOf(title)!
}
func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
//Use correct index on the side based on settings desired.
if(g_appSettings[0].indextype=="letter"){
return letterIndex
}else{
return numberIndex
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//TODO: Need to figure out how many rows for ...column A,B,C or 1,2,3 based on indexType using~
//To do this we need to organize the inventory results into a section'ed array.
if(g_appSettings[0].selectedStore != nil){
return (g_appSettings[0].selectedStore?.inventories!.count)! //number of rows is equal to the selected stores inventories count
}else{
return g_inventoryItems.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("InventoryTableCell", forIndexPath: indexPath) as! InventoryTableViewCell
if(g_appSettings[0].selectedStore != nil){
//Get the current Inventory Item & Set to the cell for reference.
cell.inventoryItem = g_appSettings[0].selectedStore?.inventories?.allObjects[indexPath.row] as! Inventory
}else{
//This only happens for DEMO mode or first time.
cell.inventoryItem = g_inventoryItems[indexPath.row]//create reference to particular inventoryItem this represents.
}
cell.drawCell() //uses passed inventoryItem to draw it's self accordingly.
return cell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if(g_appSettings[0].indextype == "letter"){
return letterIndex[section]
}else{
return numberIndex[section]
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if(g_appSettings[0].selectedStore != nil){
if(g_appSettings[0].indextype=="letter"){
return letterIndex.count
}else{
return numberIndex.count
}
}else{
return 1//only one section for DEMO mode.
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//dispatch_async(dispatch_get_main_queue()) {
//[unowned self] in
print("didSelectRowAtIndexPath")//does not recognize first time pressed item for some reason?
let selectedCell = self.tableView(tableView, cellForRowAtIndexPath: indexPath) as? InventoryTableViewCell
self.performSegueWithIdentifier("inventoryInfoSegue", sender: selectedCell)
//}
}
#IBAction func BarcodeScanBarItemAction(sender: UIBarButtonItem) {
print("test of baritem")
}
#IBAction func SetStoreBarItemAction(sender: UIBarButtonItem) {
print("change store interface")
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
print("text is changing")
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
print("ended by cancel")
searchBar.text = ""
searchBar.resignFirstResponder()
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
print("ended by search")
searchBar.resignFirstResponder()
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
print("ended by end editing")
searchBar.resignFirstResponder()
}
#IBAction func unwindBackToInventory(segue: UIStoryboardSegue) {
print("unwind attempt")
let barcode = (segue.sourceViewController as? ScannerViewController)?.barcode
searchBar.text = barcode!
print("barcode="+barcode!)
inventoryTable.reloadData()//reload the data to be safe.
}
}
//Extention to INT to create random number in range.
extension Int
{
static func random(range: Range<Int> ) -> Int
{
var offset = 0
if range.startIndex < 0 // allow negative ranges
{
offset = abs(range.startIndex)
}
let mini = UInt32(range.startIndex + offset)
let maxi = UInt32(range.endIndex + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}
Update:: **
So I was looking around and found this article (I implemented it).
https://www.andrewcbancroft.com/2015/03/05/displaying-data-with-nsfetchedresultscontroller-and-swift/
I'm really close now to figuring it out. Only problem is I can get it to auto create the sections, but only on another field, like for example store.name, I can't get it to section it into A,B,C sections or 1,2,3.
This is my code for the fetchedResultsController using the methods described in that article.
//Create fetchedResultsController to handle Inventory Core Data Operations
lazy var fetchedResultsController: NSFetchedResultsController = {
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
let primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)
let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
inventoryFetchRequest.sortDescriptors = [primarySortDescriptor, secondarySortDescriptor]
let frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "store.name",
cacheName: nil)
frc.delegate = self
return frc
}()
Question is what to put for sectionNameKeyPath: now that will make it section it on A B C and I got this !
Found a stackoverflow post very similar to my issue, but need swift answer.
A-Z Index from NSFetchedResultsController with individual section headers within each letter?
Here is another similar article but all objective-c answers.
NSFetchedResultsController with sections created by first letter of a string
Update::
Found another article I think with my exact issue (How to have a A-Z index with a NSFetchedResultsController)
Ok I figured it out, phew was this confusing and took a lot of research.
Okay, so first thing you have to do is create a transient property on the data model. In my case I called it lettersection. To do this in the entity just create a new attribute and call it lettersection and in graph mode if you select it (double click it), you will see option in inspector for 'transient'. This means it won't be saved to the database and is used more for internal reasons.
You then need to manually set up the variable in the extension area of the model definition. Here is how it looks for me.
import Foundation
import CoreData
extension Inventory {
#NSManaged var addCount: NSNumber?
#NSManaged var barcode: String?
#NSManaged var currentCount: NSNumber?
#NSManaged var id: NSNumber?
#NSManaged var imageLargePath: String?
#NSManaged var imageSmallPath: String?
#NSManaged var name: String?
#NSManaged var negativeCount: NSNumber?
#NSManaged var newCount: NSNumber?
#NSManaged var store_id: NSNumber?
#NSManaged var store: Store?
var lettersection: String? {
let characters = name!.characters.map { String($0) }
return characters[0].uppercaseString
}
}
Once you do this, you simply call this new 'lettersection' with the fetchedResultsController like so...
let frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "lettersection",
cacheName: nil)
and everything will work! It sorts by the name of my inventory items, but groups them by the first letters, for a nice A,B,C type list!
"My question is, how can I get the results from coreData to be sorted into the A,B,C type sections or 1,2,3 sections so that navigating the table will be simple."
Using "Store" as your entity and property "name" to be what you want to sort the records by.
override func viewDidLoad() { super.viewDidLoad()
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Store", inManagedObjectContext: managedObjectContext)
fetchRequest.entity = entity
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
let foundObjects = try managedObjectContext.executeFetchRequest(fetchRequest)
locations = foundObjects as! [Location]
} catch {
fatalCoreDataError(error) }
}
You are going to use this function to set the number of sections:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return letterindex.count // if that is how you want to construct sections
}
I learned this from the Ray Wenderlich e-book "iOS Apprentice". From Lesson 3 - MyLocations. Highly recommend this and their e-book book on CoreData.