Update: Identified the first half of the problem as exerciseNameLabel not rendering the updated text until a app restart. I removed the label element and am using cell.textLabel.text only. Problem #1 solved. Problem #2 is more clean: every time a cell is selected to view details, the indexPath.row is set to the selection and the next added item overwrites the item. Not sure of the mechanics behind indexPath.row, so I'm still searching to figure this one out. Help there is appreciated.
Short Youtube video (0:48) of the below behavior: https://www.youtube.com/watch?v=Zr4lCbzrtLY
I followed Apple's MealTracker walkthrough pretty closely, but my TableView is behaving strangely. Usually the newly created elements will only appear on the TableView after I restart the app. However, the newly created, but invisible items can be clicked. It seems like the label isn't being populated with the name after creation, but the name takes after a restart of the app. (This is shown in the first half of the video above)
Additionally, sometimes there is really unexpected behavior where a newly created item overwrites an old item (~0:32 on the video). When this happens, the label is updated in real time as expected, so the only unexpected part of this is that an old item is getting overwritten.
I have a print statement inside the tableView method which shows the correct index, the right element name, and the right array size.
class MasterTableViewController: UITableViewController {
var exercises = ExerciseProgram(name: "temp", startDate: "temp", program: nil)
override func viewWillAppear(animated: Bool) {
// Load any saved program, otherwise load sample data.
if let savedProgram = loadProgram() {
exercises = savedProgram
} else {
// Load the sample data.
loadSampleProgram()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
}
func loadSampleProgram() {
// snip
}
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 exercises!.getCount()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "ExerciseTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! ExerciseTableViewCell
// Fetches the appropriate exercise for the data source layout
let exercise = exercises?.getExercise(indexPath.row)
cell.exerciseNameLabel.text = exercise!.name
print("in tableView() indexPath.row: \(indexPath.row) exercise name: \(exercise!.name) exercises count: \(exercises!.getCount())")
// Configure the cell...
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 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
exercises!.removeExercise(indexPath.row)
saveProgram()
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?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "ShowDetail" {
let exerciseDetailViewController = segue.destinationViewController as! ExerciseDetailViewController
// Get the cell that generated this segue
if let selectedExerciseCell = sender as? ExerciseTableViewCell {
let indexPath = tableView.indexPathForCell(selectedExerciseCell)!
let selectedExercise = exercises!.getExercise(indexPath.row)
exerciseDetailViewController.exercise = selectedExercise
}
}
}
#IBAction func unwindToExerciseList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? AddExerciseViewController, exercise = sourceViewController.exercise {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing exercise.
exercises!.updateExercise(selectedIndexPath.row, updatedExercise: exercise)
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
} else {
// Add a new exercise.
let newIndexPath = NSIndexPath(forRow: exercises!.getCount(), inSection: 0)
exercises!.addExercise(exercise)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
// Save the program.
saveProgram()
}
}
// MARK: NSCoding
func saveProgram() {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(NSKeyedArchiver.archivedDataWithRootObject(exercises!), forKey: "program")
defaults.synchronize()
}
func loadProgram() -> ExerciseProgram? {
let defaults = NSUserDefaults.standardUserDefaults()
guard let decodedNSData = defaults.objectForKey("program") as? NSData,
let exerciseProgram = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData) as? ExerciseProgram
else {
print("Failed")
return nil
}
return exerciseProgram
}
}
Add Element:
class AddExerciseViewController: UIViewController, UINavigationControllerDelegate, UITextFieldDelegate {
#IBOutlet weak var exerciseNameTextField: UITextField!
#IBOutlet weak var doneButton: UIBarButtonItem!
var exercise: Exercise?
override func viewDidLoad() {
super.viewDidLoad()
doneButton.enabled = false
exerciseNameTextField.delegate = self
// Do any additional setup after loading the view.
}
// MARK: UITextFieldDelegate
func textFieldShouldReturn(textField: UITextField) -> Bool {
// Hide the keyboard.
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
checkValidExerciseName()
}
func textFieldDidBeginEditing(textField: UITextField) {
// Disable the Save button while editing.
doneButton.enabled = false
}
func checkValidExerciseName() {
// Disable the Done button if the text field is empty.
let text = exerciseNameTextField.text ?? ""
doneButton.enabled = !text.isEmpty
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func cancelButtonTapped(sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
let isPresentingInAddExerciseMode = presentingViewController is UINavigationController
if isPresentingInAddExerciseMode {
dismissViewControllerAnimated(true, completion: nil)
} else {
navigationController!.popViewControllerAnimated(true)
}
}
// MARK: - Navigation
// This method lets you configure a view controller before it's presented.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if doneButton === sender {
let name = exerciseNameTextField.text ?? ""
let notes = "test form notes"
let weight = 135
// Set the meal to be passed to MealListTableViewController after the unwind segue.
exercise = Exercise(name: name, notes: notes, workoutLog: nil, weight: weight)
}
}
}
ExeciseProgram:
class ExerciseProgram: NSObject, NSCoding {
var name = "Allpro Auto Regulated"
var startDate = "16-04-20"
var program: [Exercise]? = []
init?(name: String, startDate: String, program: [Exercise]?) {
self.name = name
self.startDate = startDate
if program != nil {
self.program = program
} else {
self.program = []
}
if name.isEmpty || startDate.isEmpty {
return nil
}
}
// MARK: Types
struct PropertyKey {
static let nameKey = "name"
static let startDateKey = "startDate"
static let programKey = "program"
}
// MARK: Methods
func addExercise(newExercise: Exercise) {
program!.append(newExercise)
}
func getExercise(index: Int) -> Exercise {
return program![index]
}
func getCount() -> Int {
return program!.count
}
func updateExercise(index: Int, updatedExercise: Exercise) {
program![index] = updatedExercise
}
func removeExercise(index: Int) {
program!.removeAtIndex(index)
}
// MARK: NSCoder
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: PropertyKey.nameKey)
aCoder.encodeObject(startDate, forKey: PropertyKey.startDateKey)
aCoder.encodeObject(program, forKey: PropertyKey.programKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
let startDate = aDecoder.decodeObjectForKey(PropertyKey.startDateKey) as! String
let program = aDecoder.decodeObjectForKey(PropertyKey.programKey) as! [Exercise]?
// Must call designated initializer.
self.init(name: name, startDate: startDate, program: program)
Exercise:
class Exercise: NSObject, NSCoding {
var name: String
var formNotes: String?
private var workoutLog: [[String:AnyObject]]?
var currentWeights = Weights(heavy: 0)
init?(name: String, notes: String?, workoutLog: [[String:AnyObject]]?, weight: Int?) {
self.name = name
self.formNotes = notes
if (workoutLog != nil) {
self.workoutLog = workoutLog
} else {
self.workoutLog = []
}
self.currentWeights.heavy = weight!
if name.isEmpty {
return nil
}
}
// MARK: Types
// weights of the exercise
struct Weights {
var heavy = 0
var warmup25: Int { return roundToFives(Double(heavy) * 0.25) }
var warmup50: Int { return roundToFives(Double(heavy) * 0.50) }
}
struct PropertyKey {
static let nameKey = "name"
static let formNotesKey = "formNotes"
static let workoutLogKey = "workoutLog"
static let currentWeightsHeavyKey = "currentWeightsHeavy"
}
func recordWorkout(date: String, weight: Int, repsFirstSet: Int, repsSecondSet: Int) {
let newWorkoutLogEntry = ["date": date, "weight": weight, "repsFirstSet": repsFirstSet, "repsSecondSet": repsSecondSet]
workoutLog!.append(newWorkoutLogEntry as! [String : AnyObject])
}
func getLastWorkout() -> [String:AnyObject]? {
return workoutLog?.last
}
func getBarWeightsString(targetWeight: Int) -> String {
if targetWeight < 54 {
return "Bar"
} else {
return calculatePlates(roundToFives(Double(targetWeight)))
}
}
// MARK: NSCoder
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: PropertyKey.nameKey)
aCoder.encodeObject(formNotes, forKey: PropertyKey.formNotesKey)
aCoder.encodeObject(workoutLog, forKey: PropertyKey.workoutLogKey)
aCoder.encodeInteger(currentWeights.heavy, forKey: PropertyKey.currentWeightsHeavyKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
let formNotes = aDecoder.decodeObjectForKey(PropertyKey.formNotesKey) as! String
let workoutLog = aDecoder.decodeObjectForKey(PropertyKey.workoutLogKey) as! [[String : AnyObject]]
let currentWeights = aDecoder.decodeIntegerForKey(PropertyKey.currentWeightsHeavyKey)
// Must call designated initializer.
self.init(name: name, notes: formNotes, workoutLog: workoutLog, weight: currentWeights)
}
}
Problem 1: missing cell labels for newly added exercises until app restart
The cell.exerciseNameLabel.text inside tableView() is not consistently rendering. I don't know why, but changing this to cell.labelText.text works 100% of the time.
Problem 2: newly added exercises overwriting previously selected cells
The copied code from Apple's MealTracker exercise has a case for updating an item, which is essentially overwriting its old entry. I fixed this by removing the conditional inside unwindToExerciseList(). Everything is working now.
#IBAction func unwindToExerciseList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? AddExerciseViewController, exercise = sourceViewController.exercise {
// Add a new exercise.
let newIndexPath = NSIndexPath(forRow: exercises!.getCount(), inSection: 0)
exercises!.addExercise(exercise)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
saveProgram()
}
}
Related
I have a journal application that has an object called Entry. It has its own Swift file called Entry.swift and these journal entries are saved using arrays of dictionaries.
I added a search bar to the UITableViewController and whenever I input a letter the app crashes after tableView.reloadData() is called. I think this has something to do with the filter returning my array of dictionaries named entries incorrectly and when tableView.reloadData() is called, both of the labels on the dequeueReusableCell can't be populated because the information is in the wrong format in the array of dictionaries.
Entry.swift
//
// Entry.swift
// Journal
//
// Created by handje on 6/17/17.
// Copyright © 2017 Rob Hand. All rights reserved.
//
import Foundation
class Entry {
static fileprivate let titleKey = "title"
static fileprivate let bodyTextKey = "bodyText"
static fileprivate let dateKey = "date"
var title: String
var bodyText: String
var date: String
init(title: String, bodyText: String, date: String ) {
self.title = title
self.bodyText = bodyText
self.date = date
}
func dictionaryRepresentation() -> [String: Any] {
return [Entry.titleKey: title, Entry.bodyTextKey: bodyText, Entry.dateKey: date]
}
convenience init?(dictionary: [String: Any]) {
guard let title = dictionary[Entry.titleKey] as? String,
let bodyText = dictionary[Entry.bodyTextKey] as? String, let date = dictionary[Entry.dateKey] as? String else { return nil
}
self.init(title: title, bodyText: bodyText, date: date)
}
}
extension Entry: Equatable {
static func == (lhs:Entry, rhs:Entry) -> Bool {
return
lhs.title == rhs.title &&
lhs.bodyText == rhs.bodyText
}
}
EntryListTableViewController.swift
//
// EntryListTableViewController.swift
// Journal
//
// Created by handje on 6/17/17.
// Copyright © 2017 Rob Hand. All rights reserved.
//
import UIKit
class EntryListTableViewCell: UITableViewCell {
#IBOutlet weak var dreamTitle: UILabel!
#IBOutlet weak var dreamDate: UILabel!
}
extension EntryListTableViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
}
class EntryListTableViewController: UITableViewController {
#IBOutlet weak var searchBar: UISearchBar!
var dreamTitle: UILabel!
let searchController = UISearchController(searchResultsController: nil)
let dreams = EntryController.shared.entries
var filteredDreams = [Entry]()
func filterContentForSearchText(searchText: String, scope: String = "All") {
let filteredDreams = EntryController.shared.entries.filter{ $0.title.contains(searchController.searchBar.text!) }
tableView.reloadData()
print(filteredDreams)
}
override func viewDidLoad() {
//cell setup
super.viewDidLoad()
let backgroundImage = UIImage(named: "DreamPageLucidity.jpg")
let imageView = UIImageView(image: backgroundImage)
imageView.contentMode = .scaleAspectFill
self.tableView.backgroundView = imageView
tableView.separatorInset = .zero
tableView.separatorColor = UIColor.lightGray
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return EntryController.shared.entries.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "entryCell", for: indexPath) as! EntryListTableViewCell
let entry: Entry
if searchController.isActive && searchController.searchBar.text != "" {
entry = filteredDreams[indexPath.row] /////////ERROR HERE///////
} else {
entry = EntryController.shared.entries[indexPath.row]
}
cell.dreamTitle.text = entry.title
cell.dreamDate.text = entry.date
if cell.dreamTitle.text == "" {
cell.dreamTitle.text = "Untitled Dream"
}
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let entry = EntryController.shared.entries[indexPath.row]
EntryController.shared.deleteEntry(entry: entry)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let detailVC = segue.destination as? EntryDetailViewController
guard let indexPath = tableView.indexPathForSelectedRow else { return }
let entry = EntryController.shared.entries[indexPath.row]
detailVC?.entry = entry
}
}
EntryContoller.swift
//
// EntryController.swift
// Journal
//
// Created by handje on 6/17/17.
// Copyright © 2017 Rob Hand. All rights reserved.
//
import Foundation
class EntryController {
var entries = [Entry]()
static fileprivate let entriesKey = "entriesKey"
static let shared = EntryController()
init() {
load()
}
// MARK: - CRUD
func addNewEntryWith(title: String, bodyText: String, date: String) {
let entry = Entry(title: title, bodyText: bodyText, date: date)
entries.append(entry)
save()
}
func updateEntry(entry: Entry, title: String, bodyText: String, date: String) {
entry.title = title
entry.bodyText = bodyText
save()
}
// Set up search bar
func deleteEntry(entry: Entry) {
guard let index = entries.index(of: entry) else { return }
entries.remove(at: index)
save()
}
// MARK: - save/load UserDefaults
private func save() {
let entryDictionaries = entries.map {$0.dictionaryRepresentation()}
UserDefaults.standard.set(entryDictionaries, forKey: EntryController.entriesKey)
}
private func load() {
guard let entryDictionaries = UserDefaults.standard.object(forKey: EntryController.entriesKey) as? [[String: Any]] else { return }
entries = entryDictionaries.flatMap ({ Entry(dictionary: $0) })
}
}
I think there is a problem with
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return EntryController.shared.entries.count
}
You should put a check here that if search controller is active then return count from filteredDreams else return count from EntryController.shared.entries.count, (make code changes as per your exact implementation) something like:
if searchController.isActive && searchController.searchBar.text != "" {
return filterDreams.count
} else {
return EntryController.shared.entries.count
}
You're returning a list that is bigger than the filtered list in the delegate function numberOfRowsInSection
try this, when searching:
func filterContentForSearchText(searchText: String, scope: String = "All") {
// update the list that is a class property, you were creating a new one
if searchText.isEmpty {
filteredDreams = EntryController.shared.entries
} else {
filteredDreams = EntryController.shared.entries.filter{ $0.title.contains(searchController.searchBar.text!) }
}
tableView.reloadData()
}
in numberOfRowsInSection
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// use the filtered list to determine count
return filteredDreams.count
}
for more safety you can return an empty cell instead of crashing:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard indexPath.row < filteredDreams.count else { return UITableViewCell() }
// your code here
}
When I run my app I get an error message that says
fatal error: unexpectedly found nil while unwrapping an Optional value
MealViewController.swift
import UIKit
class MealViewController: UIViewController, UITextFieldDelegate, UINavigationControllerDelegate {
// MARK: Properties
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var saveButton: UIBarButtonItem!
/*
This value is either passed by `MealTableViewController` in `prepareForSegue(_:sender:)`
or constructed as part of adding a new meal.
*/
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if saveButton === sender {
let weightInt: Int? = Int(weightInKilos.text!)
let dehydrationInt: Int? = Int(percentOfDehydration.text!)
let lossesInt: Int? = Int(ongoingLosses.text!)
let factorInt: Int? = Int(Factor.text!)
let name = nameTextField.text ?? ""
let wt = weightInt!
let dn = dehydrationInt!
let ol = lossesInt!
let fr = factorInt!
// Set the meal to be passed to MealListTableViewController after the unwind segue.
meal = Meal(name: name, wt: wt, dn: dn, ol: ol, fr: fr)
}
if calcButton === sender {
if weightInKilos.text == "" && percentOfDehydration.text == "" && ongoingLosses.text == "" && Factor.text == "" {
let alertController = UIAlertController(title: "Fields were left empty.", message:
"You left some fields blank! Please make sure that all fields are filled in before tapping 'Calculate'.", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
else {
let DestViewController: ftrViewController = segue.destinationViewController as! ftrViewController
let weightInt: Int? = Int(weightInKilos.text!)
let dehydrationInt: Int? = Int(percentOfDehydration.text!)
let lossesInt: Int? = Int(ongoingLosses.text!)
let factorInt: Int? = Int(Factor.text!)
let lrs24Int = (30 * weightInt! + 70) * factorInt! + weightInt! * dehydrationInt! * 10 + lossesInt!
let lrsPerHourint = lrs24Int / 24
DestViewController.lrsHr = "\(lrsPerHourint)"
DestViewController.lrs24Hrs = "\(lrs24Int)"
}
}
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
var meal: Meal?
override func viewDidLoad() {
super.viewDidLoad()
// Handle the text field’s user input through delegate callbacks.
nameTextField.delegate = self
// Set up views if editing an existing Meal.
if let meal = meal {
navigationItem.title = meal.name
nameTextField.text = meal.name
var weightInt: Int? = Int(weightInKilos.text!)
var dehydrationInt: Int? = Int(percentOfDehydration.text!)
var lossesInt: Int? = Int(ongoingLosses.text!)
var factorInt: Int? = Int(Factor.text!)
weightInt = meal.wt
dehydrationInt = meal.dn
lossesInt = meal.ol
factorInt = meal.fr
weightInKilos.delegate = self
percentOfDehydration.delegate = self
ongoingLosses.delegate = self
Factor.delegate = self
calcButton.layer.cornerRadius = 4;
resetbutton.layer.cornerRadius = 4;
}
// Enable the Save button only if the text field has a valid Meal name.
checkValidMealName()
}
// MARK: UITextFieldDelegate
func textFieldDidEndEditing(textField: UITextField) {
checkValidMealName()
navigationItem.title = textField.text
}
func textFieldDidBeginEditing(textField: UITextField) {
// Disable the Save button while editing.
saveButton.enabled = false
}
func checkValidMealName() {
// Disable the Save button if the text field is empty.
let text = nameTextField.text ?? ""
saveButton.enabled = !text.isEmpty
}
// 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 isPresentingInAddMealMode = presentingViewController is UINavigationController
if isPresentingInAddMealMode {
dismissViewControllerAnimated(true, completion: nil)
} else {
navigationController!.popViewControllerAnimated(true)
}
}
// Calculation outlets & actions
#IBOutlet weak var resetbutton: UIButton!
#IBOutlet weak var calcButton: UIButton!
#IBOutlet weak var weightInKilos: UITextField!
#IBOutlet weak var percentOfDehydration: UITextField!
#IBOutlet weak var ongoingLosses: UITextField!
#IBOutlet weak var Factor: UITextField!
#IBAction func resetButton(sender: AnyObject) {
if weightInKilos.text == "" && percentOfDehydration.text == "" && ongoingLosses.text == "" && Factor.text == "" {
let alertController = UIAlertController(title: "...", message:
"There is no information to reset. Nice try though!", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
else {
weightInKilos.text=""
percentOfDehydration.text=""
ongoingLosses.text=""
Factor.text=""
}
}
}
class ftrViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var lrs24hours: UILabel!
#IBOutlet weak var lrsPerHour: UILabel!
var lrs24Hrs = String()
var lrsHr = String()
override func viewDidLoad() {
lrs24hours.text = lrs24Hrs
lrsPerHour.text = lrsHr
}
}
Meal.swift
import UIKit
class Meal: NSObject, NSCoding {
// MARK: Properties
var name: String
var wt: Int
var dn: Int
var ol: Int
var fr: Int
// MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("meals")
// MARK: Types
struct PropertyKey {
static let nameKey = "name"
static let wtKey = "weight"
static let dnKey = "dehydration"
static let olKey = "ongoinglosses"
static let frKey = "factor"
}
// MARK: Initialization
init?(name: String, wt: Int, dn: Int, ol: Int, fr: Int) {
// Initialize stored properties.
self.name = name
self.wt = wt
self.dn = dn
self.ol = ol
self.fr = fr
super.init()
// Initialization should fail if there is no name or if the rating is negative.
if name.isEmpty {
return nil
}
}
// MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: PropertyKey.nameKey)
aCoder.encodeObject(wt, forKey: PropertyKey.wtKey)
aCoder.encodeObject(dn, forKey: PropertyKey.dnKey)
aCoder.encodeObject(ol, forKey: PropertyKey.olKey)
aCoder.encodeObject(fr, forKey: PropertyKey.frKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
let wt = aDecoder.decodeObjectForKey(PropertyKey.wtKey) as! Int
let dn = aDecoder.decodeObjectForKey(PropertyKey.dnKey) as! Int
let ol = aDecoder.decodeObjectForKey(PropertyKey.olKey) as! Int
let fr = aDecoder.decodeObjectForKey(PropertyKey.frKey) as! Int
// Must call designated initializer.
self.init(name: name, wt: wt, dn: dn, ol: ol, fr: fr)!
}
}
MealTableViewController.swift
import UIKit
class MealTableViewController: UITableViewController {
// MARK: Properties
var meals = [Meal]()
override func viewDidLoad() {
super.viewDidLoad()
// Use the edit button item provided by the table view controller.
navigationItem.leftBarButtonItem = editButtonItem()
// Load any saved meals, otherwise load sample data.
if let savedMeals = loadMeals() {
meals += savedMeals
} else {
}
}
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 meals.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 = "MealTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MealTableViewCell
// Fetches the appropriate meal for the data source layout.
let meal = meals[indexPath.row]
cell.nameLabel.text = meal.name
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 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
meals.removeAtIndex(indexPath.row)
saveMeals()
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 false 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?) {
if segue.identifier == "ShowDetail" {
let mealDetailViewController = segue.destinationViewController as! MealViewController
// Get the cell that generated this segue.
if let selectedMealCell = sender as? MealTableViewCell {
let indexPath = tableView.indexPathForCell(selectedMealCell)!
let selectedMeal = meals[indexPath.row]
mealDetailViewController.meal = selectedMeal
}
}
else if segue.identifier == "AddItem" {
print("Adding new meal.")
}
}
#IBAction func unwindToMealList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? MealViewController, meal = sourceViewController.meal {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing meal.
meals[selectedIndexPath.row] = meal
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
} else {
// Add a new meal.
let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0)
meals.append(meal)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
// Save the meals.
saveMeals()
}
}
// MARK: NSCoding
func saveMeals() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(meals, toFile: Meal.ArchiveURL.path!)
if !isSuccessfulSave {
print("Failed to save meals...")
}
}
func loadMeals() -> [Meal]? {
return NSKeyedUnarchiver.unarchiveObjectWithFile(Meal.ArchiveURL.path!) as! [Meal]
}
}
MealTableViewCell.swift
import UIKit
class MealTableViewCell: UITableViewCell {
// MARK: Properties
#IBOutlet weak var nameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
try this convenience required init?(coder aDecoder: NSCoder) method
convenience required init?(coder aDecoder: NSCoder) {
if let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as? String,
wt = aDecoder.decodeObjectForKey(PropertyKey.wtKey) as? Int,
dn = aDecoder.decodeObjectForKey(PropertyKey.dnKey) as? Int,
ol = aDecoder.decodeObjectForKey(PropertyKey.olKey) as? Int,
fr = aDecoder.decodeObjectForKey(PropertyKey.frKey) as? Int {
// Must call designated initializer.
self.init(name: name, wt: wt, dn: dn, ol: ol, fr: fr)
} else {
return nil
}
}
I have the following code, and now I am trying to implement accessory to the UITableView.
All the data is from Parse, even though there are tutorials out there helping out on how to do normal accessory, I am unable to find one that actually teaches on how to do that if I am getting data from online db like Parse.
//
// Listdoctors.swift
// ihealthtwo
//
// Created by David on 10/1/16.
// Copyright © 2016 ƒ. All rights reserved.
//
import UIKit
import Parse
class Listdoctors: UITableViewController {
#IBOutlet var listdoctors: UITableView!
var doctorName = [String]()
var doctorRate = [NSInteger]()
var doctorDetail = [String]()
var refresher: UIRefreshControl!
func refresh()
{
let query = PFQuery(className: "doctors")
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock(
{
(listll: [PFObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
print("Successfully retrieved \(listll!.count) names of the lawyers.")
// Do something with the found objects
if let objects = listll {
for object in objects {
print(object)
self.doctorName.append(object["doctorName"] as! String)
self.doctorRate.append(object["Rate"] as! NSInteger)
self.doctorDetail.append(object["doctorContent"] as! String)
// print(object["Lawyer_Name"] as! String )
// self.lawyersname.append(object["Lawyer_Name"] as! String)
//self.lblName.text = object["Lawyer_Name"] as? String
}
self.listdoctors.reloadData()
}
print(self.doctorName.count)
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
self.tableView.reloadData()
self.refresher.endRefreshing()
})
}
override func viewDidLoad() {
super.viewDidLoad()
refresher = UIRefreshControl()
refresher.attributedTitle = NSAttributedString(string: "Pull to refrehsh")
refresher.addTarget(self, action: "refresh", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refresher)
refresh()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return doctorName.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let doctorcell: doctorsCell = tableView.dequeueReusableCellWithIdentifier("doctorsproto") as! doctorsCell
// Configure the cell...
doctorcell.doctorname.text = doctorName[indexPath.row]
doctorcell.doctorcontent.text = doctorDetail[indexPath.row]
doctorcell.doctorrate.text = "\(doctorRate [indexPath.row])"
//lawyercell.lblExpll.text = lawyerExp[indexPath.row]
//lawyercell.lblPracareall.text = lawyerPracArea[indexPath.row]
//profImages[indexPath.row].getDataInBackgroundWithBlock{(imageData: NSData?, error: NSError?) -> Void in
// if imageData != nil {
// let image = UIImage(data: imageData!)
// lawyercell.imageLawyer.image = image
// }
// else
// {
// print(error)
// } }
return doctorcell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
print(indexPath.row)
}
// MARK: - Navigation to doctor detail
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier
{
switch identifier
{
doctor "TodoctorDetail":
let productDetailVC = segue.destinationViewController as! doctorDetail
if let indexPath = self.tableView.indexPathForCell(sender as! UITableViewCell)
{
}
default: break
}
}ˍ
}
//MARK: - Helper Method
//func productAtIndexPath(indexPath: NSIndexPath) ->?? (waht should i Put here)
}
My last line of code is not what I am sure what I exactly need to return, this is a example I am following from
https://www.youtube.com/watch?v=c-E_EbMR9wA
From the looks of it, you're trying to pass your data to a detail view which in this case you should be doing something like this assuming you have the proper indexPath and variables defined in the detailView already.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier
{
switch identifier
{
doctor "TodoctorDetail":
let productDetailVC = segue.destinationViewController as! doctorDetail
if let indexPath = self.tableView.indexPathForCell(sender as! UITableViewCell)
{
// Assuming you have the proper indexPath defined here for the selected cell and that you have these three values already defined in your productDetailVC
productDetailVC.doctorName = doctorName[indexPath.row]
productDetailVC.doctorContent = doctorContent[indexPath.row]
productDetailVC.doctorRate = doctorRating[indexPath.row]
}
default: break
}
}ˍ
}
So i have created an app where users create custom workouts.
You enter the first view add a x number of rounds,than you click on a button in the tableview where you add rounds.That click opens another activity with a collectionview.
So the problem is here when i click on an element witch represents the exercise that needs to be in the round it sends me an empty result.By default if i add a value to that string it works.
I have noticed that the unwind happens before my didSelectItemAtIndexPath
import UIKit
class InsertWorkout: UIViewController{
#IBOutlet var InsertRoundTable: UITableView!
#IBOutlet weak var txtName: UITextField!
var RoundNumber : NSMutableArray = ["Round 1"]
var RoundLabel : NSMutableArray = [""]
var RoundExercise : NSMutableArray = ["add-1"]
var RoundExerciseImages : NSMutableArray = ["providno"]
var RoundNumber_Count=1
var RoundNumber_Add_Counter=1
var studentData : StudentInfo!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: UIButton Action methods
#IBAction func btnBackClicked(sender: AnyObject)
{
self.navigationController?.popViewControllerAnimated(true)
}
#IBAction func btnSaveClicked(sender: AnyObject)
{
if(txtName.text == "")
{
Util.invokeAlertMethod("", strBody: "Please enter workout name.", delegate: nil)
}
else
{
let studentInfo: StudentInfo = StudentInfo()
studentInfo.workout_name = txtName.text!
studentInfo.workout_benefit_1=" CUSTOM "
studentInfo.workout_benefit_2=" WORKOUT "
studentInfo.workout_benefit_3=""
studentInfo.workout_requiremnets="arsutech.com"
studentInfo.workout_time="4min"
let isInserted = ModelManager.getInstance().addWorkoutData(studentInfo)
if isInserted {
Util.invokeAlertMethod("", strBody: "Workout added", delegate: nil)
} else {
Util.invokeAlertMethod("", strBody: "Error while adding workout.", delegate: nil)
}
self.navigationController?.popViewControllerAnimated(true)
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.view.endEditing(true)
InsertRoundTable.endEditing(true)
}
//UITableView
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return RoundNumber.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell : InsertRoundCell! = tableView.dequeueReusableCellWithIdentifier("InsertRoundCell") as! InsertRoundCell
if(cell == nil)
{
cell = NSBundle.mainBundle().loadNibNamed("InsertRoundCell", owner: self, options: nil)[0] as! InsertRoundCell;
}
let exerviseName = RoundNumber[indexPath.row]
let exerciseLabels = RoundLabel[indexPath.row]
let exerciseImage = RoundExercise[indexPath.row]
let exerciseImage_Holder = RoundExerciseImages[indexPath.row]
cell.insert_label_Round.text = exerviseName as? String
cell.addWorkout_Label.text = exerciseLabels as? String
cell.addExercise_Holder?.image = UIImage(named: exerciseImage_Holder as! String) as UIImage?
cell.addWorkoutBut.setBackgroundImage(UIImage(named: exerciseImage as! String) as UIImage?, forState: UIControlState.Normal)
cell.addWorkoutBut.tag = indexPath.row
cell.addWorkoutBut.addTarget(self, action: "logAction:", forControlEvents: .TouchUpInside)
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell as InsertRoundCell
}
#IBAction func logAction(sender: UIButton){
//self.RoundNumber.replaceObjectAtIndex(sender.tag, withObject: "Ezel")
//let titleString = self.RoundNumber[sender.tag] as? String
//Util.invokeAlertMethod("", strBody: titleString!, delegate: nil)
self.InsertRoundTable.reloadData()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let destinationVC = segue.destinationViewController as! ChoseExercise
destinationVC.id=sender.tag
RoundNumber_Count=sender.tag
}
#IBAction func unwinndChoseExercise(segue:UIStoryboardSegue){
if let svc = segue.sourceViewController as? ChoseExercise{
self.RoundLabel.replaceObjectAtIndex(RoundNumber_Count, withObject: "\(svc.exercise_label)")
self.RoundExercise.replaceObjectAtIndex(RoundNumber_Count, withObject: "providno")
self.RoundExerciseImages.replaceObjectAtIndex(RoundNumber_Count, withObject: "\(svc.exercise_image)")
self.InsertRoundTable.reloadData()
}
}
//Add Round
#IBAction func addRound(sender: AnyObject) {
if(RoundNumber_Add_Counter<12){
RoundNumber_Add_Counter++
self.RoundNumber.addObject("Round \(RoundNumber_Add_Counter)")
self.RoundLabel.addObject("")
self.RoundExercise.addObject("add-1")
self.RoundExerciseImages.addObject("providno")
self.InsertRoundTable.reloadData()
}else{
Util.invokeAlertMethod("", strBody: "This is the maximum number of rounds", delegate: nil)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.exercisesNames.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellexercises_insert", forIndexPath: indexPath) as! Insert_ExerciseCollectionViewCell
cell.insert_exerciseImage?.image = self.exercisesImages[indexPath.row]
cell.insert_exerciseLabel?.text = self.exercisesNames[indexPath.row]
cell.insert_exercisesHardnessImg?.image = self.exercisesHardnessImg[indexPath.row]
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
exercise_label="\(exercisesNames[indexPath.row])"
exercise_image="\(exercises_Exervise_Row_Names[indexPath.row])"
dismissViewControllerAnimated(true, completion: nil)
}
Here is how unwinding works:
First, put your IBAction unwind(segue:UIStoryboardSegue) in the vc where you intend to go back (presenter vc, InsertWorkout in this case).
Second, go to storyboard and choose the vc where you'll unwind from (collection view in this case) and ctrl-drag from the button/cell that causes the unwind (collection view cell in your case) to the exit button on top of that same vc and you should see a pop-up with your IBAction unwind method name under 'Selection Segue'.
Third, implement prepareforsegue in the current vc (collection vc in this case) to pass your data back.
class InsertWorkout
{
IBAction func unwind(segue:UIStoryboardSegue) {
//use the data passed from prepare to update your ui or you can also communicate to the sender using segue.sourceVC
}
}
class CollectionVC
{
override func prepareForSegue(segue: UIStoryboadSegue, sender: AnyObject?){
if segue.identifier == "whatever you put in storyboard for segue id" {
if let inserWorkoutVC = segue.destinationViewController as? InsertWorkout {
//pass the data here but don't attempt to update your destination's view ui!!,
}
}
}
}
In a project that I am working on I have note-esque function that is acting as an Exercise/Training Log. This Training Log is made up of 5 files: Note.swift, NotesTableViewController.swift, NoteDetailViewController.swift, NoteDetailTableViewCell.swift, and NoteStore.swift. The class for this table is NotesTableViewController, which is a UIViewController with UITableViewDelegate, and UITableViewDataSource. This note taking feature works decently, populating the tableview, but fails to delete a note from the .plist file and continues to retrieve it when the app is reopened. I do not know if this is actually failure to save/load, or if something is going wrong somewhere else. I would appreciate any help at all. The files are as follows:
Note.swift
import Foundation
class Note : NSObject, NSCoding {
var title = ""
var text = ""
var date = NSDate() // Defaults to current date / time
// Computed property to return date as a string
var shortDate : NSString {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM/dd/yy"
return dateFormatter.stringFromDate(self.date)
}
override init() {
super.init()
}
// 1: Encode ourselves...
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(title, forKey: "title")
aCoder.encodeObject(text, forKey: "text")
aCoder.encodeObject(date, forKey: "date")
}
// 2: Decode ourselves on init
required init(coder aDecoder: NSCoder) {
self.title = aDecoder.decodeObjectForKey("title") as! String
self.text = aDecoder.decodeObjectForKey("text") as! String
self.date = aDecoder.decodeObjectForKey("date") as! NSDate
}
}
NotesTableViewController.swift
import UIKit
class NotesTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var OpenButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Leverage the built in TableViewController Edit button
self.navigationItem.leftBarButtonItem = self.editButtonItem()
OpenButton.target = self.revealViewController()
OpenButton.action = Selector("revealToggle:")
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
self.tableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// ensure we are not in edit mode
editing = false
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Here we pass the note they tapped on between the view controllers
if segue.identifier == "NoteDetailPush" {
// Get the controller we are going to
var noteDetail = segue.destinationViewController as! NoteDetailViewController
// Lookup the data we want to pass
var theCell = sender as! NoteDetailTableViewCell
// Pass the data forward
noteDetail.theNote = theCell.theNote
}
}
#IBAction func saveFromNoteDetail(segue:UIStoryboardSegue) {
// We come here from an exit segue when they hit save on the detail screen
// Get the controller we are coming from
var noteDetail = segue.sourceViewController as! NoteDetailViewController
// If there is a row selected....
if let indexPath = tableView.indexPathForSelectedRow() {
// Update note in our store
NoteStore.sharedNoteStore.updateNote(theNote: noteDetail.theNote)
// The user was in edit mode
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
} else {
// Otherwise, add a new record
NoteStore.sharedNoteStore.createNote(theNote: noteDetail.theNote)
// Get an index to insert the row at
var indexPath = NSIndexPath(forRow: NoteStore.sharedNoteStore.count()-1, inSection: 0)
// Update tableview
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
// MARK: - Table view data source
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Just return the note count
return NoteStore.sharedNoteStore.count()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Fetch a reusable cell
let cell = tableView.dequeueReusableCellWithIdentifier("NoteDetailTableViewCell", forIndexPath: indexPath) as! NoteDetailTableViewCell
// Fetch the note
var rowNumber = indexPath.row
var theNote = NoteStore.sharedNoteStore.getNote(rowNumber)
// Configure the cell
cell.setupCell(theNote)
return cell
}
// Override to support editing the table view.
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
NoteStore.sharedNoteStore.deleteNote(indexPath.row)
// Delete the note from the tableview
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
}
NoteDetailViewController
import UIKit
class NoteDetailViewController: UIViewController {
var theNote = Note()
#IBOutlet weak var noteTitleLabel: UITextField!
#IBOutlet weak var noteTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// The view starts here. By now we either have a note to edit
// or we have a blank note in theNote property we can use
// Update the screen with the contents of theNote
self.noteTitleLabel.text = theNote.title
self.noteTextView.text = theNote.text
// Set the Cursor in the note text area
self.noteTextView.becomeFirstResponder()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Whenever we leave the screen, update our note model
theNote.title = self.noteTitleLabel.text
theNote.text = self.noteTextView.text
}
#IBAction func CancelNote(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
NoteDetailTableViewCell
import UIKit
class NoteDetailTableViewCell : UITableViewCell {
// The note currently being shown
weak var theNote : Note!
// Interface builder outlets
#IBOutlet weak var noteTitleLabel : UILabel!
#IBOutlet weak var noteDateLabel : UILabel!
#IBOutlet weak var noteTextLabel : UILabel!
// Insert note contents into the cell
func setupCell(theNote:Note) {
// Save a weak reference to the note
self.theNote = theNote
// Update the cell
noteTitleLabel.text = theNote.title
noteTextLabel.text = theNote.text
noteDateLabel.text = theNote.shortDate as String
}
}
and finally, NoteStore
import Foundation
class NoteStore {
// Mark: Singleton Pattern (hacked since we don't have class var's yet)
class var sharedNoteStore : NoteStore {
struct Static {
static let instance : NoteStore = NoteStore()
}
return Static.instance
}
// Private init to force usage of singleton
private init() {
load()
}
// Array to hold our notes
private var notes : [Note]!
// CRUD - Create, Read, Update, Delete
// Create
func createNote(theNote:Note = Note()) -> Note {
notes.append(theNote)
return theNote
}
// Read
func getNote(index:Int) -> Note {
return notes[index]
}
// Update
func updateNote(#theNote:Note) {
// Notes passed by reference, no update code needed
}
// Delete
func deleteNote(index:Int) {
notes.removeAtIndex(index)
}
func deleteNote(withNote:Note) {
for (i, note) in enumerate(notes) {
if note === withNote {
notes.removeAtIndex(i)
return
}
}
}
// Count
func count() -> Int {
return notes.count
}
// Mark: Persistence
// 1: Find the file & directory we want to save to...
func archiveFilePath() -> String {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory = paths.first as! NSString
let path = documentsDirectory.stringByAppendingPathComponent("NoteStore.plist")
return path
}
// 2: Do the save to disk.....
func save() {
NSKeyedArchiver.archiveRootObject(notes, toFile: archiveFilePath())
}
// 3: Do the reload from disk....
func load() {
let filePath = archiveFilePath()
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(filePath) {
notes = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as! [Note]
} else {
notes = [Note]()
}
}
}
it look's like you're not calling the save method after changing creating,deleting or updating notes
you could add for example :
func deleteNote(index:Int) {
notes.removeAtIndex(index)
save()
}
or call the save methods on vievWillDisappear if you don't want to write a new plist after every change