I have the next code in my application but only print the next image,i don't know why.Someone who can help me solve this error.I think that the error is in the Dictionary.
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var viewTable: NSTableView!
func getDataArray () -> NSArray{
var dataArray:[NSDictionary] = [["FirstName": "Debasis", "LastName": "Das"],
["FirstName": "Nishant", "LastName": "Singh"],
["FirstName": "John", "LastName": "Doe"],
["FirstName": "Jane", "LastName": "Doe"],
["FirstName": "Mary", "LastName": "Jane"]];
return dataArray;
}
func numberOfRowsInTableView(aTableView: NSTableView!) -> Int
{
let numberOfRows:Int = getDataArray().count
return numberOfRows
}
func tableView(tableView: NSTableView!, objectValueForTableColumn tableColumn: NSTableColumn!, row: Int) -> AnyObject!
{
var newString: (AnyObject?) = getDataArray().objectAtIndex(row).objectForKey(tableColumn.identifier)
println(newString)
return newString;
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
}
}
}
Maybe it's silly but it's good to know how to do, I hope it will be useful.Finally my code works, the only thing he needed was set as data source.
Have you set the Identifier for the cells in the storyboard? If so then just get the row and with the identifier you can get the "FirstName" or "LastName", wich you return
func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn?, row: Int) -> AnyObject?
{
var columnIdentifier = tableColumn!.identifier
var newString: (AnyObject?)
newString = getDataArray().objectAtIndex(row).valueForKey(columnIdentifier)
println(newString)
return newString
}
If you have further questions how to work with cell based tableViews: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TableView/PopulatingCellTables/PopulatingCellTables.html
Edit:
you should set the tableView as Cell Based.
And i think you forget to set ViewController as dataSource and delegete for the tableview in the Storyboard
Related
When a cell is deleted, the item at the end of the list takes the place of the item that just got deleted. This only happens when there are more than 3 items in the list.
In the gif below I delete numbers 3 and 4 which leaves me with numbers 1,2,5 in the simulator. HOWEVER in the Realm file I have numbers 1,2,4. I have no clue why it does this?
Data Model
import Foundation
import RealmSwift
class Item: Object {
#objc dynamic var name = ""
}
View Controller
import UIKit
import RealmSwift
class ListViewController: UITableViewController {
let realm = try! Realm()
var itemArray : Results<Item>?
var item:Item?
override func viewDidLoad() {
super.viewDidLoad()
self.itemArray = realm.objects(Item.self)
}
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Add New Item", message: "", preferredStyle: .alert)
alert.view.tintColor = UIColor.red
let action = UIAlertAction(title: "Add Item", style: .default) { (action) in
let newItem = Item()
newItem.name = textField.text!
try! self.realm.write {
self.realm.add(newItem)
}
self.tableView.reloadData()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.itemArray!.count//Size of the Array
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath)//Asigns the Protocol Cell
let data = self.itemArray![indexPath.row]
cell.textLabel?.text = data.name
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if let item = itemArray?[indexPath.row] {
try! self.realm.write {
self.realm.delete(item)
}
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
}
You are retrieving an unsorted result set from Realm, which as per documentation does not necessarily retain insertion order after deletions (basically when you remove 3, then 5 is shifted in its place):
Note that the order of Results is only guaranteed to stay consistent when the query is sorted. For performance reasons, insertion order is not guaranteed to be preserved.
So there are two things you can do:
1.) sort the result set
2.) instead of assuming you're only deleting a single object and otherwise have no movements of any sort, you can rely on Realm's own diffing + change set evaluation with a notification token so that you receive a change set for any possible change that happens to the result set.
// see https://realm.io/docs/swift/latest/#collection-notifications
class ViewController: UITableViewController {
var notificationToken: NotificationToken? = nil
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
let results = realm.objects(Person.self).filter("age > 5")
// Observe Results Notifications
notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
tableView.beginUpdates()
tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.endUpdates()
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
}
}
deinit {
notificationToken?.invalidate()
}
}
I am using cell-based tableview to store custom class data, for example
class Person {
var name:String = ""
var age: Int = 0
}
And then use a NSMUtableArray to collect person1, person2 ... datas, call it dataArray.
Present Person name on column1 and age on column2, works fine and editable.
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
if tableColumn?.identifier == "Name"
{
return (self.dataArray[row] as! Person).name
}
else if tableColumn?.identifier == "Age"
{
return (self.dataArray[row] as! Person).age
}
}
How can I sort each column with respected sortDescriptor?
I follow that fefernce but "sortDescriptorWithKey:tableColumn.identifier" always fail in my code, it can be passed if set the tableColumn.identifier to "self", it also confused me.
Below are my other code fragments.
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
let newDescriptors = tableView.sortDescriptors
dataArray.sort(using: newDescriptors)
tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// Sort
for tableColumn in tableView.tableColumns {
let sortDescrptor = NSSortDescriptor.init(key: tableColumn.identifier, ascending: true, selector: #selector(NSString.caseInsensitiveCompare))
tableColumn.sortDescriptorPrototype = sortDescrptor
}
}
I have table view like this (in a mac cocoa application):
In the leftmost panel you can see that I have set the identifier of the Table Cell View to "1". That's fine if you just have 2 columns, once the number goes up, this approach will become cumbersome. Can I do this programmatically?
Here is an example:
import Cocoa
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
private var dataModel = DataModel()
private var answer = 0
private var keyData: (Int, [Int]) = (0, []) {
didSet {
tbl.reloadData()
}
}
#IBOutlet weak var questionIndex: NSTextField!
#IBOutlet weak var tbl: NSTableView!
#IBAction func replay(_ sender: Any) {
dataModel = DataModel()
questionIndex.stringValue = "0:"
answer = 0
updateModel()
}
#IBAction func forward(_ sender: NSButton) {
if sender.tag == 1 {
answer += keyData.0
}
updateModel()
}
func updateModel() {
let group = dataModel.nextGroup()
if let g = group {
self.keyData = g
let s = questionIndex.stringValue
questionIndex.stringValue = String(Int(String(s.characters.dropLast()))! + 1) + ":"
return
}
let alert = NSAlert()
alert.messageText = "You did have \(answer) on your mind, didn't you?"
alert.runModal()
}
override func viewDidLoad() {
super.viewDidLoad()
for (n, col) in tbl.tableColumns.enumerated() {
col.identifier = String(n)
}
updateModel()
}
func numberOfRows(in tableView: NSTableView) -> Int {
return keyData.1.count / 8 + 1
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let colId = tableColumn!.identifier
let colIndex = Int(colId)!
let index = (row * 8) + colIndex
let cell = tbl.make(withIdentifier: colId, owner: self) as! NSTableCellView
if 0 <= index && index < keyData.1.count {
cell.textField!.integerValue = keyData.1[index]
} else {
cell.textField!.stringValue = ""
}
return cell
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
I have assigned the cell identifiers by hand, and made them identical the corresponding column index, so as to creating a mapping between the cell id and the 2D array (which is the underlying data model) column index. The app is running fine, I just don't like assigning these IDs by click-and-point.
The full project can be found here: https://github.com/kindlychung/MysteriousNum
Create custom cell and add init to it using following lines.
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
and register this cell class as.
self.tableView.register(CustomCell.self, forCellReuseIdentifier: "customCell")
also dequeueReusableCell using same cell like:
tableView.dequeueReusableCell(withIdentifier: "customCell",for: indexPath) as! CustomCell
Goals
I'm trying to update a few records and delete them from NSTableView after that. http://take.ms/6r3IV
Expected Results
I expect that my records will be updated and NSTableView will be reloaded to reflect my changes.
Actual Results
My application crashes. http://take.ms/q6SWw
Steps to Reproduce
What are steps we can follow to reproduce this issue?
Code Sample
https://gist.github.com/msamoylov/27e1b6c9255b254f033f44d6de115d20
Version of Realm and Tooling
Realm version: 2.0.0 (installed as the Dynamic Framework)
Xcode version: 8.0 (8A218a)
macOS version: 10.12
It seems that I wasn't handling notifications properly. So, I ended up rewriting my code in this way:
import Cocoa
import RealmSwift
class ExerciseListViewController: NSViewController {
#IBOutlet weak var exerciseTableView: NSTableView!
let realm = try! Realm()
var exercises = try! Realm().objects(Exercise.self).filter("preferred = false").sorted(byProperty: "priority")
var notificationToken: NotificationToken? = nil
override func viewDidLoad() {
super.viewDidLoad()
exerciseTableView.dataSource = self
exerciseTableView.delegate = self
exerciseTableView.doubleAction = #selector(self.addPreferredExercises(_:))
notificationToken = exercises.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
guard let exerciseTableView = self?.exerciseTableView else { return }
switch changes {
case .initial:
exerciseTableView.reloadData()
break
case .update(_, let deletions, let insertions, _):
exerciseTableView.beginUpdates()
exerciseTableView.insertRows(at: IndexSet(insertions), withAnimation: .slideDown)
exerciseTableView.removeRows(at: IndexSet(deletions), withAnimation: .effectFade)
exerciseTableView.endUpdates()
if self?.exercises.count == 0 {
self?.dismiss(nil)
}
break
case .error(let error):
fatalError("\(error)")
break
}
}
}
deinit {
notificationToken?.stop()
}
#IBAction func addPreferredExercises(_ sender: AnyObject) {
if exerciseTableView.selectedRowIndexes.isEmpty {
let alert = NSAlert()
alert.messageText = "Hint"
alert.informativeText = "Please select at least one exercise from the list."
alert.alertStyle = .informational
alert.beginSheetModal(for: view.window!, completionHandler: nil)
}
try! realm.write {
for index in exerciseTableView.selectedRowIndexes {
if exercises.indices.contains(index) {
let exercise = exercises[index]
exercise.preferred = true
}
}
}
}
}
extension ExerciseListViewController: NSTableViewDataSource, NSTableViewDelegate {
func numberOfRows(in tableView: NSTableView) -> Int {
return exercises.count
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return exercises.indices.contains(row) ? exercises[row].name : nil
}
func tableView(_ tableView: NSTableView, toolTipFor cell: NSCell, rect: NSRectPointer, tableColumn: NSTableColumn?, row: Int, mouseLocation: NSPoint) -> String {
let exercise = exercises[row]
let comment = exercise.comment as String!
return comment!
}
}
It still doesn't work 100% correct with multiple selections, but at least it's not crashing the app anymore. For some reason exerciseTableView.selectedRowIndexes contains non-existing indexes.
I have implemented a basic example of an ios app using Realm.io
I'd like to be able to reorder table rows in my iOS app and save the order back to Realm.
Realm model contains a property called position for this purpose.
P.S: Sorry for so much code.
import UIKit
import Realm
class Cell: UITableViewCell {
var position: Int!
init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: .Subtitle, reuseIdentifier: reuseIdentifier)
}
}
class Language: RLMObject {
var title = ""
var position = Int()
}
class ManagerLanguagesController: UITableViewController, UITableViewDelegate, UITableViewDataSource {
var array = RLMArray()
var notificationToken: RLMNotificationToken?
var editButton = UIBarButtonItem()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
notificationToken = RLMRealm.defaultRealm().addNotificationBlock { note, realm in
self.reloadData()
}
reloadData()
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return Int(array.count)
}
func setupUI() {
tableView.registerClass(Cell.self, forCellReuseIdentifier: "cell")
self.title = "Languages"
var addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "add")
editButton = UIBarButtonItem(title: "Edit", style: .Plain, target: self, action: "edit")
var buttons = [addButton, editButton]
self.navigationItem.rightBarButtonItems = buttons
}
func add() {
var addLanguageView:UIViewController = self.storyboard.instantiateViewControllerWithIdentifier("newLanguage") as UIViewController
self.navigationController.presentViewController(addLanguageView, animated: true, completion: nil)
}
func edit () {
if tableView.editing {
/* FROM THIS POINT I'M PROBABLY DOING SOMETHING WRONG.. IT IS NOT WORKING */
var positionArray = NSMutableArray()
let realm = RLMRealm.defaultRealm()
var i = 0
for var row = 0; row < tableView.numberOfRowsInSection(0); row++ {
var cellPath = NSIndexPath(forRow: row, inSection: 0)
var cell:Cell = tableView.cellForRowAtIndexPath(cellPath) as Cell
positionArray.addObject(cell.position)
}
realm.beginWriteTransaction()
for row: RLMObject in array {
row["position"] = positionArray[i]
i++
}
realm.commitWriteTransaction()
/* -- NOT WORKING END -- */
tableView.setEditing(false, animated: true)
editButton.style = UIBarButtonItemStyle.Plain
editButton.title = "Edit"
} else{
tableView.setEditing(true, animated: true)
editButton.title = "Done"
editButton.style = UIBarButtonItemStyle.Done
}
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as Cell
let object = array[UInt(indexPath!.row)] as Language
cell.textLabel.text = object.title
cell.position = object.position // I have implemented this to be able to retain initial positions for each row and maybe use this when reordering..
return cell
}
override func tableView(tableView: UITableView!, canMoveRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
return true
}
override func tableView(tableView: UITableView!, moveRowAtIndexPath sourceIndexPath: NSIndexPath!, toIndexPath destinationIndexPath: NSIndexPath!) {
// println("Old index: \(sourceIndexPath.indexAtPosition(sourceIndexPath.length - 1)+1)")
// println("New index: \(destinationIndexPath.indexAtPosition(sourceIndexPath.length - 1)+1)")
// Maybe something needs to be implemented here instead...
}
func reloadData() {
array = Language.allObjects().arraySortedByProperty("position", ascending: true)
tableView.reloadData()
}
}
Thanks in advance
Instead of using a position property, you could instead keep an ordered array as a property on another object. This way you don't have to keep the position up to date and instead arrange your objects as needed:
class Language: RLMObject {
dynamic var title = ""
}
class LanguageList: RLMObject {
dynamic var languages = RLMArray(objectClassName: "Language")
}
class ManagerLanguagesController: UITableViewController, UITableViewDelegate, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
// create our list
var realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
realm.addObject(LanguageList())
realm.commitWriteTransaction()
...
}
// helper to get the RLMArray of languages in our list
func array() -> RLMArray {
return (LanguageList.allObjects().firstObject() as LanguageList).languages
}
override func tableView(tableView: UITableView!, moveRowAtIndexPath sourceIndexPath: NSIndexPath!, toIndexPath destinationIndexPath: NSIndexPath!) {
var languages = array()
var object = languages.objectAtIndex(UInt(sourceIndexPath.row)) as Language
var realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
languages.removeObjectAtIndex(UInt(sourceIndexPath.row))
languages.insertObject(object, atIndex: UInt(destinationIndexPath.row))
realm.commitWriteTransaction()
}
...
}
this work for me to move rows in tableview using realm with swift 2.2:
func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
let aux = TimesHome.mutableCopy() as! NSMutableArray
let itemToMove = aux[fromIndexPath.row]
let realm = try! Realm()
realm.beginWrite()
aux.removeObjectAtIndex(fromIndexPath.row)
aux.insertObject(itemToMove, atIndex: toIndexPath.row)
try! realm.commitWrite()
TimesHome = aux
let times = realm.objects(ParciaisTimes)
if times.count > 0 {
for tm in times {
for i in 1...aux.count {
if aux[i-1].valueForKey("time_id") as! Int == tm.time_id {
realm.beginWrite()
tm.ordem = i
try! realm.commitWrite()
}
}
}
}
}