Realm Array and child relationships - swift

I am trying to make a List in relation to my realm array. I don't know if it is possible to take a hard coded realm array and give each string its own list. Currently I have my array in a table view and when a row is selected it segues to its own viewController. I am trying to get each selected row to contain its own list. Here's the code
Data Model 1
import Foundation
import RealmSwift
class DateChange: Object {
#objc dynamic var itemId : String = UUID().uuidString
override static func primaryKey() -> String? {
return "itemId"
}
let dates = List<String>()
let selection = List<Home>()
convenience init(tag: String) {
self.init()
}
}
Data Model 2
class Home: Object {
#objc dynamic var itemId : String = UUID().uuidString
override static func primaryKey() -> String? {
return "itemId"
}
var parentCategory = LinkingObjects(fromType: Home.self, property: "selection")
View Controller 1
class WeekOfViewController: NSViewController {
let post = DateChange(tag: "")
post.dates.append("December 30th - January 5th")
post.dates.append("January 13th - January 19th")
}
func numberOfRows(in tableView: NSTableView) -> Int {
return 2
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = tableView.makeView(withIdentifier:
NSUserInterfaceItemIdentifier(rawValue: "dateCell") , owner: self) as! NSTableCellView?
cell?.textField?.stringValue = post.dates[row]
return cell
}
func tableViewSelectionDidChange(_ notification: Notification) {
self.performSegue(withIdentifier: "selectedDate", sender: self)
}
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
// Unwrap the segue's identifier.
guard let identifier = segue.identifier else { return }
// Make sure this is the segue we care about.
if identifier == "selectedDate" {
let secondVC = segue.destinationController as! ViewController2
// Figure out which row was selected.
if let selectedRow = dateTableView?.selectedRow {
secondVC.selectedDate = post.dates[selectedRow]
}
View Controller 2
class ViewController2: NSViewController {
#IBAction func saveData(_ sender: NSButton) {
if let appendDate = selectedDate {
do {
try realm?.write {
let homeData = Home()
homeData.done = false
appendDate.dates.append()
}
} catch {
print("There was an error saving")
}
}
}

Yes. You can make multiple dimensional arrays.
Example:
1. Year object has a list of months
2. Month object has a list of days
3. Day object has a list of hours
4. etc, etc
When you app launches, you create a loop which initializes a Year, then initializes lots of months and appends them into the Year.Month_Array. Do the same for days_array in each month.
To save in Realm, call:
try! realm.write {
realm.add(Year)
}
Now you can read out you multi-level Realm object anytime you wish.
If my answer isn't clear, please let me know

The exact use case is a bit unclear but it seems that the app is a Master->Detail configuration where the master page contains a list of dates and then when a date is tapped, it segues to a detail page with further info.
Here's an example of code to handle the objects. You know how to populate a tableView with it's delegate methods so I'm omitting that part.
Suppose we want a list of events on the master page and then the activities of each event on the detail page. Start with two managed Realm objects; the event object which has a start and end date, the event title and the activities list within that event. Then there's the activity object.
class EventClass: Object {
#objc dynamic var start_date: Date?
#objc dynamic var end_date: Date?
#objc dynamic var event_title = ""
let activities = List<ActivityClass>()
}
class ActivityClass: Object {
#objc dynamic var title = ""
}
write an event with some activities to Realm
let formatter = DateFormatter()
formatter.dateFormat = "yyyymmdd"
let e0 = EventClass()
e0.event_title = "Workshop"
e0.start_date = formatter.date(from: "20180113")
e0.end_date = formatter.date(from: "20180119")
let a0e0 = ActivityClass()
a0e0.title = "Some activity"
let a0e1 = ActivityClass()
a0e1.title = "Another activity"
let a0e2 = ActivityClass()
a0e2.title = "Activity continues"
e0.activities.append(a0e0)
e0.activities.append(a0e1)
e0.activities.append(a0e2)
// write event 0 (e0) to realm which will create the event and activities
We are assuming both the master and detail views have tableViews, so load the events into the master tableView dataSource which is a Realm results class - it behaves like an array.
class ViewController: NSViewController {
var eventResults: Results<EventClass>? = nil
and then whenever it's time to populate the dataSource:
self.eventResults = realm.objects(EventClass.self)
and then the tableView delegate methods can get each row for display. It would look something like this on the master page
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = tableView.makeView(withIdentifier:
NSUserInterfaceItemIdentifier(rawValue: "dateCell") , owner: self) as! NSTableCellView?
//cell?.textField?.stringValue = post.dates[row]
let event = self.eventResults[row]
cell.textField?.stringValue = "Event: \(title) from: \(start) - \(end)"
return cell
}
so then the tableView would look something like this
Event: Workshop from: 2018-12-30 - 2018-01-05
Event: Workshop from: 2018-01-13 - 2018-01-19
etc
when a row is tapped, get that event object from the master tableView datasource, self.eventResults
let tappedEvent = self.eventResults[tappedRowIndex]
and then get the activity list
let activityList = tappedEvent.activities
that activityList can be passed via the segue (or 1000 other ways) to the detail viewController and it becomes the dataSource for the detail tableView. The detail page follows the same pattern except the dataSource is the activities.
Does that help?

Related

how to implement json in realm swift

I have a dictionary in json format. I want to show it with a table in my app(about 1000 cells) and also save it to realm database. I am new to database can anyone please tell me how to implement this? Should I try to convert the format outside the app or load the json when it is used?
My dictionary looks like this..
[
{
"id":0,
"book":1,
"lesson":1,
"kanji":"\u4e2d\u56fd\u4eba"
} {
"id":1,
"book":1,
"lesson":1,
"kanji":"\u65e5\u672c\u4eba"
},
...
]
First of all, you will need to install 2 pods:
pod 'SwiftyJSON' #great for handling JSON
pod 'RealmSwift' #Realm database
You will need to create the object that will be able to be saved in Realm. I suppose your object is some type of course or something similar. You can rename it per your needs:
import Foundation
import RealmSwift
import SwiftyJSON
class Course: Object {
#objc dynamic var id = 0
#objc dynamic var book = 0
#objc dynamic var lesson = 0
#objc dynamic var kanji = ""
override static func primaryKey() -> String? { //you need this in case you will want to update this object in Realm
return "id"
}
convenience init(courseJson: JSON) {
self.id = courseJson["id"].intValue
self.book = courseJson["book"].intValue
self.lesson = courseJson["lesson"].intValue
self.kanji = courseJson["kanji"].stringValue
}
}
Now, at the place where you get your json from the server, you need to do this:
var courses = [Course]()
let jsonArray = JSON(dataFromServer).arrayValue
for courseJson in jsonArray {
let course = Course(courseJson: courseJson)
courses.append(course)
}
//Now, you need to save these objects in Realm:
let realm = try! Realm()
try! realm.write {
realm.add(courses, update: true)
}
In the viewDidLoad method of the view controller where you want to show the data, you need to fetch all your Course objects from Realm, add them to an array and show data in the tableView/collectionView:
import UIKit
import RealmSwift
class PresentingCoursesVC: UIViewController {
//MARK: IBOutlets
#IBOutlet weak var tableView: UITableView!
//MARK: Properties
var courses = Results<Course>?
//MARK: Lifecycles
override func viewDidLoad() {
super.viewDidLoad()
getCourses()
}
//MARK: Private functions
private func getCourses() {
let realm = try! Realm()
courses = realm.objects(Course.self).sorted(byKeyPath: "id", ascending: true)
tableView.reloadData()
}
}
//MARK: TableView Datasource
extension PresentingCoursesVC: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return courses?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCustomCell") as! MyCustomCell
let course = courses[indexPath.row]
cell.setupCell(course: course)
return cell
}
}
I hope my answer was helpful!
class Book: Object {
#objc dynamic var id = 0
#objc dynamic var book = 0
#objc dynamic var lesson = 0
#objc dynamic var kanji = ""
}
class MyDic: Object {
var yourKeyOfDic = List<Book>()
}
Now map your dic with MyDic class object and then add that object in realm by following code,
let realm = try! Realm()
try! realm.write {
realm.add(yourDicObject)
}

Using type select on only one column with NSTableView in Swift

I'm working in XCode 9.3, Swift developing an application for MacOS.
I've created a table that currently has two columns. I want to use type select that only acts on one column so that when the user types the first few letters, it only selects within the first column and not in the 2nd column.
The Apple documentation has an example in Objective-C here: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TableView/RowSelection/RowSelection.html#//apple_ref/doc/uid/10000026i-CH6-SW1
but I can't seem to translate that into Swift. I've included the table code below. Any help at all would be greatly appreciated.
extension ViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return directoryItems?.count ?? 0
}
// Sorting.
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
guard let sortDescriptor = tableView.sortDescriptors.first else {
return
}
if let order = Directory.FileOrder(rawValue: sortDescriptor.key!) {
sortOrder = order
sortAscending = sortDescriptor.ascending
reloadFileList()
}
}
}
extension ViewController: NSTableViewDelegate {
fileprivate enum CellIdentifiers {
static let NameCell = "NameCellID"
static let TimeCell = "TimeCellID"
static let SizeCell = "SizeCellID"
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var image: NSImage?
var text: String = ""
var cellIdentifier: String = ""
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .long
guard let item = directoryItems?[row] else {
return nil
}
if tableColumn == tableView.tableColumns[0] {
image = item.icon
text = item.name
cellIdentifier = CellIdentifiers.NameCell
} else if tableColumn == tableView.tableColumns[1] {
let asset = AVAsset(url: item.url as URL)
let audioTime = asset.duration
text = audioTime.durationText
cellIdentifier = CellIdentifiers.TimeCell
} else if tableColumn == tableView.tableColumns[2] {
text = item.isFolder ? "--" : sizeFormatter.string(fromByteCount: item.size)
cellIdentifier = CellIdentifiers.SizeCell
}
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: nil) as? NSTableCellView {
cell.textField?.stringValue = text
cell.imageView?.image = image ?? nil
return cell
}
return nil
}
}
Your problem is twofold:
First, you need to set an Identifier for each of your table columns. You can do this by selecting the column in Interface Builder, going to the Identity Inspector, and setting it to some string value:
Once you've done that, create some static properties to refer to these identifiers (this isn't strictly necessary, since you could of course just use rawValue to do a plain string comparison the Objective-C way, but the new Swift 4 identifiers are type-safe, and thus preferred). Do that like this:
extension ViewController: NSTableViewDelegate {
private struct TableColumns {
static let foo = NSUserInterfaceItemIdentifier("Foo")
static let bar = NSUserInterfaceItemIdentifier("Bar")
}
...
}
Now, you can use these identifiers to refer to your columns, using tableColumn(withIdentifier:) (or column(withIdentifier:) if you just want the column number). I recommend doing this everywhere you refer to them, including in your tableView(:viewFor:row:) method, since the way you're doing it now with tableView.tableColumns[0] and so forth depends on the order of the columns, and if the user reorders them, it may cause unexpected behavior. Using the identifier will make sure you're always looking at the column you think you're looking at.
Anyway, once you've got your identifiers set up, you can address the second problem: You're using the wrong delegate method. Instead of tableView(:shouldTypeSelectFor:searchString:), which is meant for catching these things at the event level (i.e., the user just pressed a key. Should I invoke the type select system at all?), use tableView(:typeSelectStringFor:row:). This method lets you return the string given to the type selection mechanism for each row/column combination in your table. For the ones you want type-select to ignore, just return nil; otherwise, return the string that you'd expect to have to type to select that particular row. So, something like:
func tableView(_ tableView: NSTableView, typeSelectStringFor tableColumn: NSTableColumn?, row: Int) -> String? {
if tableColumn?.identifier == TableColumns.foo {
return directoryItems?[row].name
} else {
return nil
}
}

Fatal Error: Index out of range - Swift 4 and Firebase

I am creating an iOS application using XCode 8 and Swift 4 that keeps track of events. I am using Firebase as my database tool for this app. I am running into an "index out of range" error in the code below. I am using a for loop to read the Event start and end times for each event in the Firebase Database. The code is able to return the event names, but not the times underneath. Why is this the case? Is there something wrong with my code, or is there something wrong with my database structure? The database structure used is pasted below the code here.
Code here:
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
class Event_List_Controller: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var eventList = [String]()
var startTimeArray = [String]()
var endTimeArray = [String]()
var ref: DatabaseReference?
var handle:DatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
endTimeArray.removeAll()
tableView.delegate = self
tableView.dataSource = self
let emailfinal = UserDefaults.standard.string(forKey: "splicedEmailStandard")
ref = Database.database().reference()
handle = ref?.child(emailfinal!).child("Event Data").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String {
self.eventList.append(item)
self.tableView.reloadData()
}
})
// FOR LOOP THAT POPULATES THE START AND END TIMES
for individualEvents in eventList {
ref = Database.database().reference()
handle = ref?.child(emailfinal!).child("Event Data").child(individualEvents).child("Start Time").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String {
self.startTimeArray.append("\(item)")
}
})
handle = ref?.child(emailfinal!).child("Event Data").child(individualEvents).child("End Time").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String {
self.endTimeArray.append("\(item)")
}
})
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "customClassCell") as! CustomTableViewCell
cell.classLabel.text = eventList[indexPath.row]
// FATAL ERROR: INDEX OUT OF RANGE HERE.
cell.timeLabel.text = "\(startTimeArray[indexPath.row]) - \(endTimeArray[indexPath.row])"
return cell
}
}
Database Structure:
email#email (User)
-> User Information
----> (Information)
---> Event Information
------> Event Name
----------> Event Start Time
----------> Event End Time
----------> Event Location
email#email (User 2)
----> ...
It looks like you're calling reloadData() before you're actually getting the start and end times for the events, so the startTimeArray and endTimeArray will not have objects at the index of the event you just got from firebase.
Also consider that with Firebase you're setting up observing rather than getting all of your data at once. Perhaps some simple class like Event:
class Event {
var name: String
var startTime: String
var endTime: String
init(name: String) {
self.name = name
self.startTime = "" //or some other sensible default before loading
self.endTime = "" //or some other default
}
}
Then have an array of Event object that your tableview is using instead of the array of strings, and update the Event objects and refresh the table view whenever you get new data in from Firebase

FetchedResultsController Swift 3 API Misuse: Attempt to serialize store access on non-owning coordinator

I'm attempting to use a fetchedResultsController to handle the results in my UITable.
It works initially when the program starts up. Then when I switch back to the inventory tab where my table is (for the viewToAppear again), this is when it crashes.
I'm getting a runtime crash error in my viewWillAppear() method of the window which has the table.
In particular it crashes on the Inventory+CoredataProperties.swift file on this line let characters = name!.characters.map { String($0) }, but I suspect the error is somewhere else as this works initially so why not now on the 2nd reload?
Here is the function.
override func viewWillAppear(_ animated: Bool) {
print("view appearing")
//When the view appears its important that the table is updated.
//Trigger Event on SearchBar in case returning from BarCode Scanner
// self.searchBar:SearchBar textDidChange:recentlySearchedWord;
//searchBar.performSelector(":textDidChange")
//Perform another fetch again to get correct data~
do {
//fetchedResultsController. //this will force setter code to run again.
print("attempting fetch again, reset to use lazy init")
fetchedResultsController = setFetchedResultsController() //sets it again so its correct.
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
inventoryTable.reloadData()//this is important to update correctly for changes that might have been made
}
The error occurs on the try fetchedResultsController.performFetch() statement. I'm getting a lot of errors before the actual crash occurs saying "API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x170265300, store PSC = 0x0). I've been refactoring my code to work with the new swift 3 standards I have a feeling I did something wrong or maybe something changed with how the fetched results controller works.
Any help is appreciated as to what could be the cause?
If you think I'm missing a file you need to see, just let me know and I'll add it to the relevant source code below.
POSSIBLE RELEVANT SOURCE CODE BELOW:
InventoryController.swift (Entire File)
import UIKit
import CoreData
import Foundation
class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
#available(iOS 2.0, *)
//Create fetchedResultsController to handle Inventory Core Data Operations
lazy var fetchedResultsController: NSFetchedResultsController<Inventory> = {
return self.setFetchedResultsController()
}()
//Reference to search text for filtering
var m_searchText = ""
func setFetchedResultsController() -> NSFetchedResultsController<Inventory>{
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let inventoryFetchRequest : NSFetchRequest<Inventory> = Inventory.fetchRequest()
var primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)//by default assume name.
print("primarySortDescriptor...")
if(g_appSettings[0].indextype=="numberfront"){
primarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
}else if(g_appSettings[0].indextype=="numberback"){
primarySortDescriptor = NSSortDescriptor(key: "barcodeReverse", ascending: true)
}else if(g_appSettings[0].indextype=="numberfourth"){
primarySortDescriptor = NSSortDescriptor(key: "barcodeFourth", ascending: true, selector: #selector(NSString.localizedCompare(_:)))
}
print("set primarySortDescriptor")
//let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
inventoryFetchRequest.sortDescriptors = [primarySortDescriptor]
print("set sort descriptors to fetch request")
var storefilter : Store? = nil
var predicate: NSPredicate
//Store should never be set to nil, the first store should always be selected by default. For fringe cases just in case ... support added so doesn't break
if(g_appSettings[0].selectedStore != nil){
storefilter = g_appSettings[0].selectedStore
predicate = NSPredicate(format: "store = %#", storefilter!) //default predicate assuming store is selected
//However if search text is present then modify predicate
if(m_searchText != ""){
predicate = NSPredicate(format: "store = %# AND name contains[cd] %# OR store = %# AND barcode contains[cd] %#", storefilter!,m_searchText,storefilter!,m_searchText)
}
//This will ensure correct data relating to store is showing (and if any filters present, them as well)
inventoryFetchRequest.predicate = predicate
}else{
if(m_searchText != ""){
predicate = NSPredicate(format: "name contains[cd] %# OR barcode contains[cd] %#",m_searchText,m_searchText)
inventoryFetchRequest.predicate = predicate
//This will ensure correct data relating to store is showing
}
}
//default assume letter section
var frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: "lettersection",
cacheName: nil)
if(g_appSettings[0].indextype=="numberfront"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: "numbersection",
cacheName: nil)
}else if(g_appSettings[0].indextype=="numberback"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: "numberendsection",
cacheName: nil)
}else if(g_appSettings[0].indextype=="numberfourth"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: "numberfourthsection",
cacheName: nil)
}
print("set the frc")
frc.delegate = self
return frc
}
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var inventoryTable: UITableView!
// Start DEMO Related Code
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 previousNumber = -1 //used so we show A,A, B,B, C,C etc for proper testing of sections
func createInventoryDummyData(number: Int) -> Inventory{
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let tempInventory = NSEntityDescription.insertNewObject(forEntityName: "Inventory", into: moc) as! Inventory
if(number-1 == previousNumber){
tempInventory.name = "\(letterIndex[number-2])-Test Item # \(number)"
previousNumber = -1//reset it again
}else{
tempInventory.name = "\(letterIndex[number-1])-Test Item # \(number)"
previousNumber = number //set previous letter accordingly
}
tempInventory.barcode = "00000\(number+1)00\(number)"
//special exception to demo barcode reader
if(number==5){
tempInventory.barcode = "0051111407592"
}
if(number==6){
tempInventory.barcode = "0036000291452"
}
tempInventory.barcodeReverse = String(tempInventory.barcode!.characters.reversed())
//Convert barcode into array of characters and take note if its size for indexing
let bcArraySize = Int(tempInventory.barcode!.characters.count) - 1//for correct indexing
var bcArray = tempInventory.barcode!.characters.map { String($0) }
print(bcArray)
print(bcArraySize)
//Take the digits from the 4th one at a time and convert to strings concatenating as you go.
let fourth = "\(bcArray[bcArraySize-3])"+"\(bcArray[bcArraySize-2])"+"\(bcArray[bcArraySize-1])"+"\(bcArray[bcArraySize])"
print(fourth)
//Finally convert that into a number again and set to barcodeFourth
tempInventory.barcodeFourth = fourth
print(tempInventory.barcodeFourth!)
//tempInventory.barcodeFourth =
//print(tempInventory.barcodeReverse)
tempInventory.currentCount = 0
tempInventory.id = number as NSNumber?
tempInventory.imageLargePath = "http://distribution.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.imageSmallPath = "http://distribution.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 lo = 0;
let hi = 2;
let aRandomInt = Int.random(range:lo...hi)
tempInventory.setValue(g_storeList[aRandomInt], forKey: "store") //assigns inventory to one of the stores we created.
return tempInventory
}
func createStoreDummyData(number:Int) -> Store{
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let tempStore = NSEntityDescription.insertNewObject(forEntityName: "Store", into: moc) as! Store
tempStore.address = "100\(number) lane, Miami, FL"
tempStore.email = "store\(number)#centraltire.com"
tempStore.id = number as NSNumber?
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()
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
print("InventoryController -> ViewDidLoad -> ... starting inits")
// // 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<Inventory>(entityName: "Inventory")
//let storeFetchRequest = NSFetchRequest(entityName: "Store")
do {
let inventoryRecords = try moc.fetch(inventoryFetchRequest)
//Maybe sort descriptor here? But how to organize into sectioned array?
if(inventoryRecords.count<=0){
g_demoMode = true
print("No entities found for inventory. Demo mode = True. Creating default entities & store...")
//Reset the Stores
g_storeList = [Store]()
var store : Store //define variable as Store type
for index in 1...3 {
store = createStoreDummyData(number: index)
g_storeList.append(store)
}
//save changes for inventory we added
do {
try moc.save()
print("saved to entity")
}catch{
fatalError("Failure to save context: \(error)")
}
var entity : Inventory //define variable as Inventory type
for index in 1...52 {
let indexFloat = Float(index/2)+1
let realIndex = Int(round(indexFloat))
entity = createInventoryDummyData(number: realIndex)
g_inventoryItems.append(entity)
}
//Save the changes
(UIApplication.shared.delegate as! AppDelegate).saveContext()
print("finished creating entities")
}
}catch{
fatalError("bad things happened \(error)")
}
// //perform fetch we need to do.
// do {
// try fetchedResultsController.performFetch()
// } catch {
// print("An error occurred")
// }
print("InventoryController -> viewDidload -> ... finished inits!")
}
override func viewWillAppear(_ animated: Bool) {
print("view appearing")
//When the view appears its important that the table is updated.
//Trigger Event on SearchBar in case returning from BarCode Scanner
// self.searchBar:SearchBar textDidChange:recentlySearchedWord;
//searchBar.performSelector(":textDidChange")
//Perform another fetch again to get correct data~
do {
//fetchedResultsController. //this will force setter code to run again.
print("attempting fetch again, reset to use lazy init")
fetchedResultsController = setFetchedResultsController() //sets it again so its correct.
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
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 prepare(for segue: UIStoryboardSegue, sender: Any?) {
// 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.destination as! InventoryItemController
vc.hidesBottomBarWhenPushed = true //hide the tab bar. This prevents crashing error from being on this page then syncing & returning.
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 sectionIndexTitles(for tableView: UITableView) -> [String]? {
//This is smart and takes the first letter of known sections to create the Index Titles
return self.fetchedResultsController.sectionIndexTitles
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InventoryTableCell", for: indexPath as IndexPath) as! InventoryTableViewCell
print("IndexPath=")
print(indexPath)
let inventory : Inventory = fetchedResultsController.object(at: indexPath as IndexPath)
cell.inventoryItem = inventory
cell.drawCell() //uses passed inventoryItem to draw it's self accordingly.
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
func numberOfSections(in tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
//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, cellForRowAt: indexPath) as? InventoryTableViewCell
self.performSegue(withIdentifier: "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) {
self.barcodeTextDidChange(searchText: searchText)
}
func barcodeTextDidChange(searchText: String){
print("text is changing")
//Code to change NSFetchRequest Here~ & Reload Table
m_searchText = searchText //sets the local variable to this class so the setFetchedResultsController() will update accordingly
//Perform another fetch again to get correct data~
do {
//fetchedResultsController. //this will force setter code to run again.
print("attempting fetch again, reset to use lazy init")
fetchedResultsController = setFetchedResultsController() //sets it again so its correct.
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
inventoryTable.reloadData()//refreshes the data~
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
print("ended by cancel")
searchBar.text = ""
m_searchText = "" //set the search text accordingly back to nothing.
//Perform another fetch again to get correct data~
do {
//fetchedResultsController. //this will force setter code to run again.
print("attempting fetch again, reset to use lazy init")
fetchedResultsController = setFetchedResultsController() //sets it again so its correct.
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
inventoryTable.reloadData()//refreshes the data~
searchBar.resignFirstResponder()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
print("ended by search")
searchBar.resignFirstResponder()
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
print("ended by end editing")
searchBar.resignFirstResponder()
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
print("DidBeginEditing")
//searchBar.keyboardType = UIKeyboardType.NamePhonePad
}
#IBAction func unwindBackToInventory(segue: UIStoryboardSegue) {
print("unwind attempt")
let barcode = (segue.source as? ScannerViewController)?.barcode
searchBar.text = barcode!
barcodeTextDidChange(searchText: searchBar.text!)//force it to re-run function manually.
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: ClosedRange<Int> ) -> Int
{
var offset = 0
if range.lowerBound < 0 // allow negative ranges
{
offset = abs(range.lowerBound)
}
let mini = UInt32(range.lowerBound + offset)
let maxi = UInt32(range.upperBound + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}
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
Inventory+CoreDataProperties.swift
import Foundation
import CoreData
extension Inventory {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Inventory> {
return NSFetchRequest<Inventory>(entityName: "Inventory");
}
#NSManaged var addCount: NSNumber?
#NSManaged var barcode: String?
#NSManaged var barcodeReverse: String?
#NSManaged var barcodeFourth: 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?
//This is used for A,B,C ordering...
var lettersection: String {
let characters = name!.characters.map { String($0) }
return (characters.first?.uppercased())!
}
//This is used for 1,2,3 ordering... (using front of barcode and using barcodeReverse)
var numbersection: String {
let characters = barcode!.characters.map { String($0) }
return (characters.first?.uppercased())!
}
//This is used for 0000000123 ordering...(uses back number of barcode)
var numberendsection: String {
let characters = barcodeReverse!.characters.map { String($0) }
return (characters.first?.uppercased())!
}
//This is used for 0000000 -> 0123 ordering...(uses back 4th number of barcode)
var numberfourthsection: String {
let characters = barcodeFourth!.characters.map { String($0) }
//print("characters")
//print(characters)
return (characters.first?.uppercased())!
}
}
Inventory.Swift
import Foundation
import CoreData
class Inventory: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
Screenshots of Errors
I have reviewed your all comments and contents posted here.
You have not shared one file here, but the problem is occurring you are creating invalid managed objects in the context.
And then whenever you call viewWillAppear() function in InventoryViewController, it saves the context.
Finally, it synced empty records into your database.
During parsing those invalid objects, it tried to parse nil value, so crashed.
Please never set default value for managed objects you are defining as properties.
I hope this will clarify your issue.
I was running into similar issue and i moved to the new CoreData api introduced in ios10.
This uses the NSPersistentContainer class to create the stack and create associated contexts.
This eliminates the need to manually call save or order the creation of fetch results controller.
Good blog post to read: https://useyourloaf.com/blog/easier-core-data-setup-with-persistent-containers/
My setup is a follows
create a store NSPersistentContainer
let persistentContainer = NSPersistentContainer(name: "ModelFileName");
configure settings
let url = NSPersistentContainer.defaultDirectoryURL()
let path = url.appendingPathComponent(persistentContainer.name);
description.shouldAddStoreAsynchronously = true; //write to disk should happen on background thread
self.persistentContainer.persistentStoreDescriptions = [description];
load the store
persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
fatalError("Unresolved error \(error), \(error.localizedDescription)")
}
//configure context for main view to automatically merge changes
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true;
});
in the view controller you can access the view context by calling
persistentContainer.viewContext
if you need to make changes you can call
persistentContainer.performBackgroundTask({ (context) in ... });
or you can get a background context
let context = persistentContainer.newBackgroundContext()
context.perform({ ... })
In case this helps anyone else who gets the "API Misuse: Attempt to serialize store access on non-owning coordinator" error - I was getting the error because I accessed an object in a singleton that had not been destroyed and was still using the old NSManagedObjectContext after I reset the NSPersistentStore and NSManagedObjectContext.

Reload a NSWindow Xcode Swift2

I'm working on an NSOutlineView that uses NSView subclasses to generate custom cells in the outline. This I've gotten to work, BUT after the Outline sucks in the data from the model class and displays it correctly, the Outline is released(?) from memory / goes to nil and I haven't figured out a way to get it back.
Here is the MainViewController class
class MainWindowController: NSWindowController, ShareInfoDelegate, NSOutlineViewDelegate, NSOutlineViewDataSource {
override var windowNibName: String {
return "MainWindowController"
}
#IBOutlet var daOutline: NSOutlineView!
// The NSoutline I'm trying to get back to
Some stuff related to the test data (Omitted)
leading us to the NSOutlineViewDataSource stuff
//MARK: - NSOutlineViewDataSource
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
if let item: AnyObject = item {
switch item {
case let work as Work:
return work.movements[index]
case let movement as Movement:
return movement.tracks[index]
default:
let track = item as! Track
return track.credits[index]
}
} else {
if allWorks.count > 0 {
return allWorks[index]
}
}
let q = "patience"
return q
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
switch item {
case let work as Work:
return (work.movements.count > 0) ? true : false
case let movement as Movement:
return (movement.tracks.count > 0) ? true : false
case let track as Track:
return (track.credits.count > 0) ? true: false
default:
return false
}
}
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
if let item: AnyObject = item {
switch item {
case let work as Work:
return work.movements.count
case let movement as Movement:
return movement.tracks.count
case let track as Track:
return track.credits.count
default:
return 0
}
} else {
return allWorks.count
}
}
func outlineView(daOutline: NSOutlineView, viewForTableColumn theColumn: NSTableColumn?, item: AnyObject) -> NSView? {
switch item {
case let worked as Work:
let cell = daOutline.makeViewWithIdentifier("newTry", owner:self) as! newTry
cell.fourthLabel.stringValue = worked.composer
cell.fourthCell.stringValue = worked.title
return cell
case let moved as Movement:
let cell2 = daOutline.makeViewWithIdentifier("SecondTry", owner:self) as! SecondTry
cell2.roman.stringValue = moved.name!
cell2.details.stringValue = moved.sections!
cell2.track.stringValue = "0"
return cell2
default:
print("probably not")
}
print("not again")
return nil
}
func outlineView(daOutline: NSOutlineView, heightOfRowByItem item: AnyObject) -> CGFloat {
switch item {
case let worked as Work:
return 40
default:
return 24
}
}
And the stuff in WindowDidLoad
override func windowDidLoad() {
super.windowDidLoad()
let nib = NSNib(nibNamed: "newTry", bundle: NSBundle.mainBundle())
daOutline.registerNib(nib!, forIdentifier: "newTry")
let nib2 = NSNib(nibNamed: "SecondTry", bundle: NSBundle.mainBundle())
daOutline.registerNib(nib2!, forIdentifier: "SecondTry")
//give Sender it's Receiver
mailItOut.delegate = receiver
allWorks.append(work1)
allWorks.append(work2)
work1.movements.append(move1)
work1.movements.append(move2)
work1.movements.append(move3)
work1.movements.append(move4)
work2.movements.append(move5)
work2.movements.append(move6)
work2.movements.append(move7)
daOutline.reloadData()
daOutline?.expandItem(work1, expandChildren: false)
daOutline?.expandItem(work2, expandChildren: false)
}
}
And Finally what the newTry NSView class looks like
class newTry: NSView {
var delegate: ShareInfoDelegate?
#IBOutlet weak var fourthCell: NSTextField!
#IBOutlet weak var fourthLabel: NSTextField!
#IBAction func cellAdd(sender: NSTextField) {
var catchIt: String = String()
catchIt = sender.stringValue
if catchIt != "" {
tryAgain = catchIt
whichField = "title"
//Trigger the sender to send message to it's Receiver
mailItOut.sendMessage()
}
}
The cellAdd Action is used to try and get user input from the text cells back into the model. To do this I (AFAIK) need to access the NSOutline (daOutline) and get which row I'm at and put the data from the sender into the appropriate part of the Model class. Which is something that I've managed to get to work in a standard (1 cell / 1 data value) outline. But in this prototype, as far as I can tell, the MainWindowController has released all of its contents and daOutline is nil (bad).
How do I get XCode to bring / reload the completed outline (or never release it) and get daOutline to a non nil state?
For those who come after there appeared to be two problems that led to the NSOutline outlet becoming nil. The first one was that in implementing the delegate protocol "shareInfoDelegate" I was creating a new instance of the MainWindowController, not the one with the data in it. This new instance did NOT have the IBOutlets connected (or much of anything useful about it).
Once I scrapped the Delegate and moved to using NSNotification to update information about the NSView textFields my NSOutline came "back".
The second, more minor, problem was that in the NSView nib file I placed and NSBox to mimic the behavior of a group row (e.g. a gray background). As a side effect the NSBox was inhibiting the normal row select behavior of the outline. Which made it very hard to determine which row was selected. When I deleted the NSBox, row selection became much more easy to determine.
in particular this Question and the answer by Chuck were helpful in sniffing this out.
Why is my NSOutlineView datasource nil?
Thanks Indeed(!)