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)
}
Related
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;
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.
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!
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.
I can fetch the image from a url and able to display the image in a UIImageView in my DetailViewController. I would like to display the same image from this URL as a thumbnail in my tableviewcell.
My viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
self.UrlField.text = contact.Imageurl
let urlString = UrlField.text
loadImageView(urlString)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
let urlString = UrlField.text
loadImageView(urlString)
textField.resignFirstResponder()
return true
}
In this function I try to fetch the image from the URL
func loadImageView(url : String){
self.contact.Imageurl = url
self.contact.data = nil
let DatatoImage: (NSData?) -> Void = {
if let d = $0 {
let image = UIImage(data: d)
self.imageView.image = image
}else{
self.imageView.image = nil
}
}
if let d = contact.data {
DatatoImage(d)
} else {
contact.loadImage(DatatoImage)
}
}
In my cellForRowAtIndexPath I managed to load an image that I have hardcoded in my folder and named it as "images1.jpeg"
cell.imageView?.image = UIImage (named: "images1.jpeg", inBundle: nil, compatibleWithTraitCollection: nil)
MainViewController
import UIKit
class MasterTableViewController: UITableViewController, ContactDetailTableViewControllerDelegate {
var contacts: [ContactListEntry] = []
var currentContact: ContactListEntry!
var detailObject: ContactDetailTableViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
#IBAction func addContact(sender: UIBarButtonItem) {
currentContact = ContactListEntry(firstName: "", lastName: "", address: "", Imageurl: "")
contacts.append(currentContact)
performSegueWithIdentifier("showDetail", sender: self)
}
/* override func viewWillAppear(animated: Bool) {
if self.currentContact == nil || self.currentContact.isEmpty()
{
if count(contacts) > 0
{
contacts.removeLast()
}
}
tableView.reloadData()
}*/
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
/*override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}*/
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return contacts.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("contactcell", forIndexPath: indexPath) as! UITableViewCell
let contact = contacts[indexPath.row]
cell.textLabel?.text = "\(contact.firstName) \(contact.lastName)"
cell.imageView?.image = UIImage (named: "images1.jpeg", inBundle: nil, compatibleWithTraitCollection: nil)
let url = NSURL(string: "<# place your URL here #>")
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
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
}
}
/*
// 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 NO if you do not want the item to be re-orderable.
return true
}
*/
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let cell = sender as? UITableViewCell
{
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
if (segue.identifier == "showDetail")
{
if let dvc = segue.destinationViewController as? ContactDetailTableViewController{
let indexPath = tableView.indexPathForCell(cell)!
currentContact = contacts[indexPath.row]
dvc.contact = currentContact
}}}
if let dvc = segue.destinationViewController as? ContactDetailTableViewController
{
dvc.contact = currentContact
dvc.delegate = self
}
}
func masterViewController(dvc: ContactDetailTableViewController, didUpdate contact: Person)
{
dvc.contact = currentContact
dvc.delegate = self
dvc.dismissViewControllerAnimated(true, completion: nil)
navigationController?.popToViewController(self, animated: true)
tableView.reloadData()
}
override func viewDidAppear(animated: Bool) {
tableView.reloadData()
}
}
/* Create our configuration first In view did load */
let configuration =
NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = 15.0
configuration.URLCache=NSURLCache(memoryCapacity: 4*1024*1024, diskCapacity: 20*1024*1024, diskPath: "CustomName"))
configuration.requestCachePolicy=NSURLRequestUseProtocolCachePolicy;
/* Now create our session which will allow us to create the tasks */
var session: NSURLSession!
session = NSURLSession(configuration: configuration,
delegate: self,
delegateQueue: nil)
/* Now attempt to download the contents of the URL in tableView cellForRowAtIndexpath */
let url = NSURL(string: "<# place your URL here #>")
task=session.dataTaskWithRequest(url!, completionHandler: { (NSData!, NSURLResponse!, NSError!) -> Void in
cell.imageView.image=UIImage(data: NSData))
})
task.start()
You can also set the cache,so that next tym when the request is fired then it is called from cache