Object isn't updating in tableview with new Realm data - ios

Whenever I click on a row in my tableview and change the text in the fields to something else, it always crashes and doesn't actually update the data. I'm saving all my data in a "Realm" database, which im fairly new at using.
I'm doing the updating in the "unwindToNoteList" function. Can someone help me know why it's adding a new row and crashing instead of updating the current row?
import UIKit
import Realm
class NoteTableViewController: UITableViewController {
// MARK: Properties
var notes = Note.allObjects()
override func viewDidLoad() {
super.viewDidLoad()
// Get Realm Database location
println(RLMRealm.defaultRealm().path)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return Int(notes.count)
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "NoteTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! NoteTableViewCell
let object = notes[UInt(indexPath.row)] as! Note
// Configure the cell...
cell.titleLabel.text = object.title
cell.bodyLabel.text = object.body
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete from database
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
realm.deleteObject(notes[UInt(indexPath.row)] as! RLMObject)
realm.commitWriteTransaction()
// Delete row from table view
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
// 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?) {
if segue.identifier == "ShowDetail" { // Clicked on a cell
let noteDetailViewController = segue.destinationViewController as! NoteViewController
// Get the cell that generated this segue.
if let selectedNoteCell = sender as? NoteTableViewCell {
let indexPath = tableView.indexPathForCell(selectedNoteCell)!
let selectedNote = notes[UInt(indexPath.row)] as! Note
noteDetailViewController.note = selectedNote
}
}
else if segue.identifier == "AddItem" { // Clicked add button
println("Adding new note")
}
}
#IBAction func unwindToNoteList(sender: UIStoryboardSegue) {
if let selectedIndexPath = tableView.indexPathForSelectedRow() { // User clicked on a row
// Update an existing note.
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
Note.createOrUpdateInRealm(realm, withValue: ["title": note.title, "body": note.body, "id": note.id])
realm.commitWriteTransaction()
println("Yes")
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
}
else {
// Add a new note.
let newIndexPath = NSIndexPath(forRow: Int(notes.count), inSection: 0)
// Persist in database
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
Note.createInRealm(realm, withValue: ["title": note.title, "body": note.body, "id": uuid])
realm.commitWriteTransaction()
println("no")
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
}
}
}
Finally solved this, by not updating the object while in the tableview but while in the view controller that holds the text fields
Here's my NoteViewController
import UIKit
import Realm
class NoteViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// MARK: Properties
#IBOutlet var saveButton: UIBarButtonItem!
#IBOutlet var titleTextField: UITextField!
#IBOutlet var bodyTextField: UITextField!
/*
This value is either passed by `NoteTableViewController` in `prepareForSegue(_:sender:)`
or constructed as part of adding a new note.
*/
var note = Note?()
// MARK: Navigation
#IBAction func cancel(sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
let isPresentingInAddNoteMode = presentingViewController is UINavigationController
if isPresentingInAddNoteMode {
dismissViewControllerAnimated(true, completion: nil)
} else {
navigationController!.popViewControllerAnimated(true)
}
}
// This method lets you configure a view controller before it's presented.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if saveButton === sender {
let title = titleTextField.text ?? ""
let body = bodyTextField.text ?? ""
/* The operator ?? unwraps the optional string in titleTextField.text since it may or may not have text in the field, and returns the value if it's a valid string. If nil though, it returns and empty string ("") instead
*/
// Update the database
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
note?.title = titleTextField.text
note?.body = bodyTextField.text
realm.commitWriteTransaction()
// Set the note to be passed to NoteTableViewController after the unwind segue.
// AKA pass "note" to NoteTableViewController with whatever the text fields have in them
note = Note(title: title, body: body)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Handle the text field’s user input through delegate callbacks.
titleTextField.delegate = self
// Set up views if editing an existing Note.
if let note = note {
navigationItem.title = note.title
titleTextField.text = note.title
bodyTextField.text = note.body
}
// Enable the Save button only if the text field has a valid Note name.
checkValidNoteName()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UITextFieldDelegate
func textFieldDidBeginEditing(textField: UITextField) {
// Disable the Save button while editing.
saveButton.enabled = false
}
func checkValidNoteName() {
// Disable the Save button if the text field is empty.
let text = titleTextField.text ?? ""
saveButton.enabled = !text.isEmpty
}
func textFieldDidEndEditing(textField: UITextField) {
checkValidNoteName()
navigationItem.title = textField.text
}
}
And here's the original, NoteTableViewController. Updated a bit as well.
import UIKit
import Realm
class NoteTableViewController: UITableViewController {
// MARK: Properties
var notes = Note.allObjects()
override func viewDidLoad() {
super.viewDidLoad()
// Get Realm Database location
println(RLMRealm.defaultRealm().path)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return Int(notes.count)
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "NoteTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! NoteTableViewCell
let object = notes[UInt(indexPath.row)] as! Note
// Configure the cell...
cell.titleLabel.text = object.title
cell.bodyLabel.text = object.body
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete from database
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
realm.deleteObject(notes[UInt(indexPath.row)] as! RLMObject)
realm.commitWriteTransaction()
// Delete row from table view
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
// 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?) {
if segue.identifier == "ShowDetail" { // Clicked on a cell
let noteDetailViewController = segue.destinationViewController as! NoteViewController
// Get the cell that generated this segue.
if let selectedNoteCell = sender as? NoteTableViewCell {
let indexPath = tableView.indexPathForCell(selectedNoteCell)!
let selectedNote = notes[UInt(indexPath.row)] as! Note
noteDetailViewController.note = selectedNote
}
}
else if segue.identifier == "AddItem" { // Clicked add button
println("Adding new note")
}
}
#IBAction func unwindToNoteList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? NoteViewController, note = sourceViewController.note {
let uuid = NSUUID().UUIDString // Needed for primary key. see below
var unwindedNote = Note()
if let selectedIndexPath = tableView.indexPathForSelectedRow() { // User clicked on a row
// Updating of the note is done in NoteViewController
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
}
else {
// Add a new note.
let newIndexPath = NSIndexPath(forRow: Int(notes.count), inSection: 0)
// Persist in database
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
unwindedNote.title = note.title
unwindedNote.body = note.body
unwindedNote.id = uuid // This is done for the primary key that Realm needs, unique for each object created.
realm.addObjects([unwindedNote])
realm.commitWriteTransaction()
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
}
}
}

Looking at the code you provided there, I was going to suggest that putting your Realm writing logic in the view controller segue-controlled methods might be a little too late.
I would normally recommend that you should place any logic involving Realm writes in a method that is explicitly called from a user interacting with it. For example, in the tableView(_ tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath) method when they tap on a table cell, and then once the write has completed, perform the segue (or segue unwinding) then.
In any case, good to hear you solved it!

Related

Swift 3 UITableView not updating

I was assigned with converting an older Objective C app to Swift.
I had another project come up but when I came back to it and to my somewhat working Swift 2 version, upon updating to Swift 3 the UITableView does not seem to update.
It is built in Interface Builder (IB). The data source and delegate functions are linked in IB.
I made a sample project where I want to reload a different array on a button press. On a button press the main array is set equal to a different array. then self.tableView.reloadData() is called. The array in debugging has a value of 4 and is not empty so numberOfRowsInSection returns a number greater than 0. The table height and width are what you would expect and are visible. The table just does not refresh. The cells populate the first time the array loads.
I have also tried downloading tutorials where they add a new cell to a table but it does not appear to work either. I have also tried manually assigning the app delegate and datasource in MasterViewController.swift. I also tried wrapping the reloadData() call in DispatchQueue.main.async but that did not seem to help either.
Hopefully I'm just missing something very basic here. Below is my MasterViewController file. Thanks for any help.
Current version of Xcode: 8.2.1
Version of swift: 3.0.2
OSX: Sierra 10.12.2
import UIKit
class MasterViewController: UITableViewController {
var detailViewController: DetailViewController? = nil
var objects = [Any]()
var list1 = ["Eggs", "Milk", "Bread", "Bacon"];
var list2 = ["France", "Italy", "England", "Spain"];
var currentArray = [String]();
var setVar = false;
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem
if(currentArray.count <= 0){
currentArray = list2;
}else{
currentArray = list1;
}
//self.tableView.dataSource = self;
// self.tableView.delegate = self;
//self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell");
//self.tableView.reloadData();
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
self.navigationItem.rightBarButtonItem = addButton
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
override func viewWillAppear(_ animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func insertNewObject(_ sender: Any) {
objects.insert(NSDate(), at: 0)
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.insertRows(at: [indexPath], with: .automatic)
}
// MARK: - Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let object = objects[indexPath.row] as! NSDate
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return currentArray.count;
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel!.text = currentArray[indexPath.row];
return cell
}
func refreshUI(){
self.tableView.reloadData();
}
func changeVar(){
if(setVar){
currentArray.removeAll();
self.currentArray = self.list1;
setVar = false;
}else{
currentArray.removeAll();
self.tableView.reloadData();
list2.append("Italy");
self.currentArray = self.list2;
setVar = true;
}
self.refreshUI();
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
objects.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
}
class DetailViewController: UIViewController {
#IBOutlet weak var detailDescriptionLabel: UILabel!
#IBOutlet weak var test2: UIButton!
#IBOutlet weak var test1: UIButton!
var mc = MasterViewController();
func configureView() {
// Update the user interface for the detail item.
if let detail = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.configureView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var detailItem: NSDate? {
didSet {
// Update the view.
self.configureView()
}
}
#IBAction func button1(){
NSLog("here is the button1");
mc.changeVar();
}
#IBAction func button2(){
NSLog("here is the button2");
mc.changeVar();
}
}
Your tableView is using the data of currentArray in cellForRowAt function.
The only place that you assign to currentArray is in viewDidLoad.
Currently, in the code the add button will cause a new string of the current timestamp to the objects array, As the table is pulling data from currentArray and not objects, it will never change.
I do not see changeVar() function being called anywhere in the code.
Change the target of the add button to call changeVar function and see if that updates the data in the table. If not, you'll have to provide the exact code that your saying is not working, as the current code I wouldn't expect it to change anything.
EDIT:
In your detail view controller you are trying to update a value from the master view controller... But.. It is not the same instance.
var mc = MasterViewController();
^^ This code creates a NEW instance of MasterViewController so calling that code wont change anything on your tableView.
change this line to
weak var mc: MasterViewController?
Then when you create the detailviewcontroller you can do:
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.mc = self
Then your DetailViewController has a reference to the master controller and can call the function as expected.

How to perform a different segue when in Edit mode (in UITableView)?

I am creating a simple iPhone app using UITableView. I am using the default MasterDetail application template. Right now (in Edit mode) when I press any of the table cells nothing happens. However, when I am in normal mode the detail segue is initiated. How to override the Edit mode so that I initiate a custom segue to go to a different UIViewController.
P.S.: I still want to preserve the inherit delete functionality.
This is my code in my MasterViewController:
class MasterViewController: UITableViewController {
let kFileName: String = "/resolutionData.plist"
var resolutions = [Dictionary<String,String>]()
var achievedResolutions = [Dictionary<String,String>]()
// TO DO create a class to get this array
let iconArray = ["Fish","Fly","Heart","HelpingHand","Melon","Star","Tentacles","Volunteering"]
override func awakeFromNib() {
super.awakeFromNib()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem()
let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
self.navigationItem.rightBarButtonItem = addButton
//extract the path
NSLog(dataFilePath())
/**
* Check where is the sandbox of the application
* and if there is read from the data file and story it to "objects" array
*/
if NSFileManager.defaultManager().fileExistsAtPath(dataFilePath()){
var temp = NSArray(contentsOfFile: dataFilePath()) as! [Dictionary<String,String>]
for res in temp{
if res["isAchieved"] == "Y"{
achievedResolutions.append(res)
}else{
resolutions.append(res)
}
}
//... if there is not - create it
} else {
let data = [["name":"Resolution name test","startingDate":"24-11-15","achievingDate":"01-01-2016","icon":iconArray[0],"isAchieved":"N"] as NSDictionary] as NSArray
//if the file does not exist...
if !NSFileManager.defaultManager().fileExistsAtPath(dataFilePath()){
//... create it
NSFileManager.defaultManager().createFileAtPath(dataFilePath(), contents: nil, attributes: nil)
}
//write to it
data.writeToFile(dataFilePath(), atomically: true)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func insertNewObject(sender: AnyObject) {
performSegueWithIdentifier("editDetails", sender: sender)
}
func saveDateToFile(){
let data = resolutions as NSArray
data.writeToFile(dataFilePath(), atomically: true)
}
func notifyTableViewForNewInsertion(){
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
// MARK: - Segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = resolutions[indexPath.row] as Dictionary
(segue.destinationViewController as! DetailViewController).detailItem = object
}
}
}
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Active"
}else{
return "Achieved"
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return resolutions.count
}else{
// TODO replace that with an actual array count
return achievedResolutions.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
if indexPath.section == 0{
let object: AnyObject? = resolutions[indexPath.row] ["name"]
cell.textLabel!.text = object as? String
cell.detailTextLabel!.text = resolutions[indexPath.row]["achievingDate"]
cell.imageView!.image = UIImage(named: resolutions[indexPath.row]["icon"]!)
} else {
let object: AnyObject? = achievedResolutions[indexPath.row] ["name"]
cell.textLabel!.text = object as? String
cell.detailTextLabel!.text = resolutions[indexPath.row]["achievingDate"]
cell.imageView!.image = UIImage(named: resolutions[indexPath.row]["icon"]!)
}
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
resolutions.removeAtIndex(indexPath.row)
saveDateToFile()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
func dataFilePath() -> String{
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
//get the first path and convert it to str
let docDicrectyory: String = paths[0] as! String
return "\(docDicrectyory)\(kFileName)"
}
}
Instead of performing your segue directly from the storyboard, add a UITableViewDelegate method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if tableView.editing {
performSegueWithIdentifier("id_Segue_Editing_VC", sender: tableView.cellForRowAtIndexPath(indexPath))
} else {
performSegueWithIdentifier("id_Segue_Standard_VC", sender: tableView.cellForRowAtIndexPath(indexPath))
}
}
Set the sender to the selected cell -- this matches the default UIKit behavior.
Then in your prepareForSegue method you can add custom value to your view controllers according to the segue identifier.
Override willBeginEditingRowAtIndexPath: function. this willbe call before start editing. there you can initialize a global variable.
#property(nonatomic, String) BOOL editing
and in the
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if (editing) {
if([[segue identifier] isEqualToString:#"identifierOne"]){
}else if([[segue identifier] isEqualToString:#"identifierTwo"]){
}
}
}
There is a table view property for that:
self.tableView.allowsSelectionDuringEditing = YES;

Data not being transferred between view controllers Swift

When a EventTableViewController proceeds to UserEventViewController or EventViewController the data is not transferred as the labels etc. are not updated to the transferred information. I have tried fix it but it still doesn't work. Please Help.
Any help would be great thanks.
Link to project:
https://www.dropbox.com/s/1d4d8opuxzpcuk4/TicketekApp.zip?dl=0
Code:
// EventTableViewController.swift
import UIKit
class EventTableViewController: UITableViewController {
// MARK: Properties
var events = [Event]()
var isAdmin = true
override func viewDidLoad() {
super.viewDidLoad()
// Use the edit button item provided by the table view controller.
navigationItem.leftBarButtonItem = editButtonItem()
// Load any saved events, otherwise load sample data.
if let savedEvents = loadEvents() {
events += savedEvents
} else {
// Load the sample data.
loadSampleEvents()
}
}
func loadSampleEvents() {
let photo1 = UIImage(named: "event1")!
let event1 = Event(name: "ACDC", photo: photo1, rating: 4, price: 500.0, eventDescription: "Album", album: "Album1")!
let photo2 = UIImage(named: "event2")!
let event2 = Event(name: "Cold Play", photo: photo2, rating: 5, price: 500.0, eventDescription: "Album", album: "Album1")!
let photo3 = UIImage(named: "event3")!
let event3 = Event(name: "One Direction", photo: photo3, rating: 3, price: 500.0, eventDescription: "Album", album: "Album1")!
events += [event1, event2, event3]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return events.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "EventTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! EventTableViewCell
// Fetches the appropriate event for the data source layout.
let event = events[indexPath.row]
cell.nameLabel.text = event.name
cell.photoImageView.image = event.photo
cell.ratingControl.rating = event.rating
cell.priceLabel.text = event.album
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let index = self.tableView.indexPathForSelectedRow?.row
//use the index to know which cell you selected
//check for your condition here something like
if isAdmin {
performSegueWithIdentifier("eventViewControllerSegue", sender: self)
} else {
performSegueWithIdentifier("userEventTableViewControllerSegue", sender: self)
}
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
events.removeAtIndex(indexPath.row)
saveEvents()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create new instance of class, add to the array, and add a new row to the table
}
}
/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
// MARK: - Navigation
// preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "eventViewControllerSegue" {
let eventDetailViewController = segue.destinationViewController as! EventViewController
// Get the cell that generated this segue.
if let selectedEventCell = sender as? EventTableViewCell {
let indexPath = tableView.indexPathForCell(selectedEventCell)!
let selectedEvent = events[indexPath.row]
eventDetailViewController.event = selectedEvent
}
} else if segue.identifier == "userEventTableViewControllerSegue" {
let eventDetailViewController = segue.destinationViewController as! UserEventViewController
// Get the cell that generated this segue.
if let selectedEventCell = sender as? EventTableViewCell {
let indexPath = tableView.indexPathForCell(selectedEventCell)!
let selectedEvent = events[indexPath.row]
eventDetailViewController.event = selectedEvent
}
}
else if segue.identifier == "AddItem" {
print("Adding new event.")
}
}
#IBAction func unwindToMealList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? EventViewController, event = sourceViewController.event {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing event.
events[selectedIndexPath.row] = event
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
} else {
// Add a new event.
let newIndexPath = NSIndexPath(forRow: events.count, inSection: 0)
events.append(event)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
// Save the events.
saveEvents()
}
}
// MARK: NSCoding
func saveEvents() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(events, toFile: Event.ArchiveURL.path!)
if !isSuccessfulSave {
print("Failed to save events...")
}
}
func loadEvents() -> [Event]? {
return NSKeyedUnarchiver.unarchiveObjectWithFile(Event.ArchiveURL.path!) as? [Event]
}
}
Shripada's answer is correct, no doubt. But, I would like to explain why your code ain't working.
You are calling performSegueWithIdentifier("eventViewControllerSegue", sender: self) from tableView:didSelectRowAtIndexPath method. Just note here that sender: self which is the ViewController and not the tableCell. Now, when this fires prepareForSegue, you are trying to access the selected cell using let selectedEventCell = sender as? EventTableViewCell . But, the sender is not the EventTableCell. Thats why you get a nil value and your if condition fails.
A simple fix would be to get the selected tableViewCell using tableView.indexPathForSelectedRow! in prepareForSegue and pass the appropriate data to destinationViewController.
let eventDetailViewController = segue.destinationViewController as! EventViewController
// Get the indexPath of selected cell
let indexPath = self.tableView.indexPathForSelectedRow!
let selectedEvent = events[indexPath.row]
eventDetailViewController.event = selectedEvent
You typically want to find the event related to the row selected. So, you will need to record which row is selected. Introduce a variable called as:
var currentlySelectedIndex = 0
to your EventTableViewController
Then change the implementation of didSelectRow delegate method in the same class to-
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//Record the row selected
currentlySelectedIndex = indexPath.row
//check for your condition here something like
if isAdmin {
performSegueWithIdentifier("eventViewControllerSegue", sender: self)
} else {
performSegueWithIdentifier("userEventTableViewControllerSegue", sender: self)
}
}
And thirdly, you will need to utilise this selected row index to fetch right event object:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "eventViewControllerSegue" {
let eventDetailViewController = segue.destinationViewController as! EventViewController
//Get the associated event
eventDetailViewController.event = events[currentlySelectedIndex]
} else if segue.identifier == "userEventTableViewControllerSegue" {
let eventDetailViewController = segue.destinationViewController as! UserEventViewController
//Get the associated event
eventDetailViewController.event = events[currentlySelectedIndex]
}
else if segue.identifier == "AddItem" {
print("Adding new event.")
}
}
This will resolve your issues.

Proceed to different view controllers based on boolean value

When a table cell is selected, I am trying to proceed to a view controller if the value of a boolean is true, else (if it is false), proceed to a different view controller. Any help would be great!
(The boolean is isAdmin and if true proceed to eventViewController, else proceed to UserEventTableViewController)
link to project:
https://www.dropbox.com/s/1d4d8opuxzpcuk4/TicketekApp.zip?dl=0
Code:
import UIKit
class EventTableViewController: UITableViewController {
// MARK: Properties
var events = [Event]()
var isAdmin = false
override func viewDidLoad() {
super.viewDidLoad()
// Use the edit button item provided by the table view controller.
navigationItem.leftBarButtonItem = editButtonItem()
// Load any saved events, otherwise load sample data.
if let savedEvents = loadEvents() {
events += savedEvents
} else {
// Load the sample data.
loadSampleEvents()
}
}
func loadSampleEvents() {
let photo1 = UIImage(named: "event1")!
let event1 = Event(name: "ACDC", photo: photo1, rating: 4, price: 500.0, eventDescription: "Album", album: "Album1")!
let photo2 = UIImage(named: "event2")!
let event2 = Event(name: "Cold Play", photo: photo2, rating: 5, price: 500.0, eventDescription: "Album", album: "Album1")!
let photo3 = UIImage(named: "event3")!
let event3 = Event(name: "One Direction", photo: photo3, rating: 3, price: 500.0, eventDescription: "Album", album: "Album1")!
events += [event1, event2, event3]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return events.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "EventTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! EventTableViewCell
// Fetches the appropriate event for the data source layout.
let event = events[indexPath.row]
cell.nameLabel.text = event.name
cell.photoImageView.image = event.photo
cell.ratingControl.rating = event.rating
cell.priceLabel.text = event.album
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
events.removeAtIndex(indexPath.row)
saveEvents()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create new instance of class, add to the array, and add a new row to the table
}
}
/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
// MARK: - Navigation
// preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowDetail" {
let eventDetailViewController = segue.destinationViewController as! EventViewController
// Get the cell that generated this segue.
if let selectedEventCell = sender as? EventTableViewCell {
let indexPath = tableView.indexPathForCell(selectedEventCell)!
let selectedEvent = events[indexPath.row]
eventDetailViewController.event = selectedEvent
print(selectedEventCell)
}
}
else if segue.identifier == "AddItem" {
print("Adding new event.")
}
}
#IBAction func unwindToMealList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? EventViewController, event = sourceViewController.event {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing event.
events[selectedIndexPath.row] = event
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
} else {
// Add a new event.
let newIndexPath = NSIndexPath(forRow: events.count, inSection: 0)
events.append(event)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
// Save the events.
saveEvents()
}
}
// MARK: NSCoding
func saveEvents() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(events, toFile: Event.ArchiveURL.path!)
if !isSuccessfulSave {
print("Failed to save events...")
}
}
func loadEvents() -> [Event]? {
return NSKeyedUnarchiver.unarchiveObjectWithFile(Event.ArchiveURL.path!) as? [Event]
}
}
You have to implement the tableView delegate method didSelectRowAtIndexPath.
example as follows:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let index = self.tableView.indexPathForSelectedRow?.row
//use the index to know which cell you selected
//check for your condition here something like
if isAdmin {
performSegueWithIdentifier("eventViewControllerSegue", sender: self)
} else {
performSegueWithIdentifier("userEventTableViewControllerSegue", sender: self)
}
}
note the following:
In your story board, draw segues to the different view controllers and assign the segue ID respectively.
In your code you are not changing the value of isAdmin boolean. Check that too.
Regards.
Image on how to segue from ViewController for your reference
I believe you can accomplish what you are seeking by using tableView:didSelectRowAtIndexPath: from the UITableViewDelegate protocol.
Add this to your view controller
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
and handle your logic within that function.
When you are ready to go to the next view controller, use
performSegueWithIdentifier(identifier: String, sender: AnyObject?)
Here is an example:
if isAdmin {
performSegueWithIdentifier(identifier: "segueToAdminView", sender: self)
} else {
performSegueWithIdentifier(identifier: "segueToUserEventView", sender: self)
}

Data won't load into my Table View

So I have a UITableViewController class for my notes app where everything is working fine, adding, deleting, and editing of the table view. But now I'm trying to load the data using "Realm" Database. I'm fairly new to iOS (Swift) and especially Realm, so I'm kind of confused. I got it to save into the database perfectly, and it shows up when I click the "Save" button in my navigation bar. But when I restart the app, the table view is completely empty. I've been stuck on this for a while now, tried everything I know so far, but just can not get it to show up whatsoever. Can someone help me out, and maybe tell me what I'm doing wrong, or not doing at all? Thank You very much.
Here is my class as well
import UIKit
import Realm
class NoteTableViewController: UITableViewController {
// MARK: Properties
var notes = [Note]() // Initialized with a default value (an empty array of Note objects).
override func viewDidLoad() {
super.viewDidLoad()
// Get Realm Database location
println(RLMRealm.defaultRealm().path)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return notes.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "NoteTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! NoteTableViewCell
// Fetches the appropriate note for the data source layout in the notes array
let note = notes[indexPath.row]
// Configure the cell...
cell.titleLabel.text = note.title
cell.bodyLabel.text = note.body
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
notes.removeAtIndex(indexPath.row)
// Get the default Realm (Will do this part later)
/*
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
realm.deleteObject(notes[Int(indexPath.row)])
realm.commitWriteTransaction()
*/
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
// 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?) {
if segue.identifier == "ShowDetail" { // Clicked on a cell
let noteDetailViewController = segue.destinationViewController as! NoteViewController
// Get the cell that generated this segue.
if let selectedNoteCell = sender as? NoteTableViewCell {
let indexPath = tableView.indexPathForCell(selectedNoteCell)!
let selectedNote = notes[indexPath.row]
noteDetailViewController.note = selectedNote
}
}
else if segue.identifier == "AddItem" { // Clicked add button
println("Adding new note")
}
}
#IBAction func unwindToNoteList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? NoteViewController, note = sourceViewController.note {
if let selectedIndexPath = tableView.indexPathForSelectedRow() { // User clicked on a row
// Update an existing note.
notes[selectedIndexPath.row] = note
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
}
else {
// Add a new note.
let newIndexPath = NSIndexPath(forRow: notes.count, inSection: 0)
notes.append(note)
// Persist in database
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
Note.createInRealm(realm, withValue: notes[newIndexPath.row])
realm.commitWriteTransaction()
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
}
}
}
And this one is the Note Object class
import UIKit // Automatically imports Foundation
import Realm
class Note: RLMObject {
// MARK: Properties
dynamic var title: String = ""
dynamic var body: String = ""
// MARK: Initialization
init?(title: String, body: String) { // Failable initializer
// Initialize stored properties.
self.title = title
self.body = body
super.init()
// Initialization should fail if there is no title
if title.isEmpty {
return nil
}
}
// Must have for Realm to work
override init() {
super.init()
}
}
Solved it after a good 2 more days...Here is my updated class
Did as Swinny89 said, and started with a new notes object, of all objects, instead of initializing an "empty" notes array.
import UIKit
import Realm
class NoteTableViewController: UITableViewController {
// MARK: Properties
var notes = Note.allObjects()
override func viewDidLoad() {
super.viewDidLoad()
// Get Realm Database location
println(RLMRealm.defaultRealm().path)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return Int(notes.count)
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "NoteTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! NoteTableViewCell
let object = notes[UInt(indexPath.row)] as! Note
// Configure the cell...
cell.titleLabel.text = object.title
cell.bodyLabel.text = object.body
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete from database
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
realm.deleteObject(notes[UInt(indexPath.row)] as! RLMObject)
realm.commitWriteTransaction()
// Delete row from table view
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
// 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?) {
if segue.identifier == "ShowDetail" { // Clicked on a cell
let noteDetailViewController = segue.destinationViewController as! NoteViewController
// Get the cell that generated this segue.
if let selectedNoteCell = sender as? NoteTableViewCell {
let indexPath = tableView.indexPathForCell(selectedNoteCell)!
let selectedNote = notes[UInt(indexPath.row)] as! Note
noteDetailViewController.note = selectedNote
}
}
else if segue.identifier == "AddItem" { // Clicked add button
println("Adding new note")
}
}
#IBAction func unwindToNoteList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? NoteViewController, note = sourceViewController.note {
let uuid = NSUUID().UUIDString // Needed for primary key. see below
var unwindedNote = Note()
if let selectedIndexPath = tableView.indexPathForSelectedRow() { // User clicked on a row
// Updating of the note is done in NoteViewController
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
}
else {
// Add a new note.
let newIndexPath = NSIndexPath(forRow: Int(notes.count), inSection: 0)
// Persist in database
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
unwindedNote.title = note.title
unwindedNote.body = note.body
unwindedNote.id = uuid // This is done for the primary key that Realm needs, unique for each object created.
realm.addObjects([unwindedNote])
realm.commitWriteTransaction()
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
}
}
}
This is because your Note array is initialised as an empty array and that's what you are using to tell the tableView how many rows there are, which is 0.
Once your Note array has been set and has some data you can then call tableView.reloadData() to reload the array with the data.
Also, looking at the code above, your class is inheriting from UITableViewController rather than implementing UITableViewControllerDelegate and UITableViewControllerDataSource. Once your class implements these you need to make sure that you set the viewController as the datasource and delegate for the tableViewController either in the storyboard or through code.

Resources