Swift code. fatal error: unexpectedly found nil while unwrapping an Optional value - ios

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
}
}

Related

Set labels in a custom cell to names from an array using a separate controller

I have a view controller with a table. The view controller has a button that segues to another controller with 4 textfields. There is a name textfield and three others where numbers can be inputed. When a user presses add, an array is appended and the view controller is dismissed while updating the table in the previous view controller. The user is then presented a table with a new cell added. The cell has 4 labels.
I am trying to figure out how to set each label in a cell to the 4 textfields that are in the view controller that created the new cell. Here is a picture of the view controller with a cell created:
Here is the view controller when the plus button is pressed that creates the cell by updating the array:
I would like to be able to set the far left label to represent the name that is added to the array every time. The first time a person clicks the plus button, the textfields view controller will pop up and the the user can put the name in the textfield. When the add button is pressed the label on the left would show the name in the controller that appended the array. The next name that is added through the view controller will be in the cell below the previous name entered and so forth. I need to get the array to show the text properly. I originally wanted the cells to have textfields that the user could put the info in there but the cells data wasn't sending to firebase database properly.
Here is the code for the first view controller:
import UIKit
import Firebase
class ConsiderationsTestViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var tableView: UITableView!
static var numberOfPeople: [String] = []
var AddPersonCell = "AddPersonCell"
#IBOutlet weak var CompanyImage: UIImageView!
#IBOutlet weak var companyNameTextFieldConsiderations: UITextField!
#IBOutlet weak var companyDescriptionTextFieldConsiderations: UITextField!
#IBOutlet weak var startDateTextFieldConsiderations: UITextField!
#IBOutlet weak var endDateTextFieldConsiderations: UITextField!
let datePickerS = UIDatePicker()
let datePickerE = UIDatePicker()
var database: Database!
var storage: Storage!
var selectedImage: UIImage?
var ref:DatabaseReference?
var databaseHandle:DatabaseHandle = 0
let dbref = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
self.companyNameTextFieldConsiderations.delegate = self
self.companyDescriptionTextFieldConsiderations.delegate = self
// Set the Firebase reference
ref = Database.database().reference()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ConsiderationsTestViewController.handleSelectCompanyImageView))
CompanyImage.addGestureRecognizer(tapGesture)
CompanyImage.isUserInteractionEnabled = true
self.navigationController!.navigationBar.isTranslucent = false
navigationItem.backBarButtonItem = UIBarButtonItem(
title: "", style: .plain, target: nil, action: nil)
createDatePickerForStart()
createDatePickerForEnd()
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
}
#objc func loadList(){
//load data here
self.tableView.reloadData()
}
#objc func handleSelectCompanyImageView() {
let pickerController = UIImagePickerController()
pickerController.delegate = self
pickerController.allowsEditing = true
present(pickerController, animated: true, completion: nil)
}
#IBAction func AddPersonTapped(_ sender: Any) {
}
#IBAction func sendButtonTapped(_ sender: Any) {
let companyNameC = companyNameTextFieldConsiderations.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let companyDescriptionC = companyDescriptionTextFieldConsiderations.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let today = Date()
let formatter1 = DateFormatter()
formatter1.dateFormat = "MMM d y"
print(formatter1.string(from: today))
let todaysDate = formatter1.string(from: today)
let storageRef = Storage.storage().reference(forURL: "STORAGE URL HERE")
let imageName = companyNameTextFieldConsiderations.text!
let storageCompanyRef = storageRef.child("Company_Image_Considerations").child("\(todaysDate)").child(imageName)
let companyDescriptionTextFieldText = companyDescriptionTextFieldConsiderations.text
let dateToStart = startDateTextFieldConsiderations.text
let dateToDecide = endDateTextFieldConsiderations.text
let companyRef = Database.database().reference().child("Considerations").child("\(todaysDate)").child(imageName)
let considerationInfluencerRef = Database.database().reference().child("Considerations").child("\(todaysDate)").child(imageName).child("Users")
//let values = ["Feed_Quantity": "feedTFC", "Story_Quantity": "storyTFC", "Compensation": "compensationTFC"]
guard let imageSelected = self.CompanyImage.image else {
print ("Avatar is nil")
return
}
var dict: Dictionary<String, Any> = [
"Company Image": "",
"Company Description": companyDescriptionTextFieldText!,
"Start Date": dateToStart!,
"Decision Date": dateToDecide! ]
guard let imageData = imageSelected.jpegData(compressionQuality: 0.5) else {
return
}
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
storageCompanyRef.putData(imageData, metadata: metadata, completion:
{ (StorageMetadata, error) in
if (error != nil) {
return
}
storageCompanyRef.downloadURL { (url, error) in
if let metadateImage = url?.absoluteString {
dict["Company Image"] = metadateImage
companyRef.updateChildValues(dict, withCompletionBlock: {
(error, ref) in
if error == nil {
print("Done")
return
}
}
)
}
}
storageRef.updateMetadata(metadata) { metadata, error in
if error != nil {
//Uh-oh, an error occurred!
} else {
// Updated metadata for 'images/forest.jpg' is returned
}
}
})
// considerationInfluencerRef.updateChildValues(values as [AnyHashable : Any]) { (error, ref) in
// if error != nil {
// print(error ?? "")
// return
// }
self.navigationController?.popViewController(animated: true)
// }
}
func createDatePickerForStart() {
// center text in field
startDateTextFieldConsiderations.textAlignment = .center
// toolbar
let toolbar = UIToolbar()
toolbar.sizeToFit()
// barbutton
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressedStart))
toolbar.setItems([doneButton], animated: true)
// assign toolbar to textfield
startDateTextFieldConsiderations.inputAccessoryView = toolbar
// assign datePicker to text field
startDateTextFieldConsiderations.inputView = datePickerS
// date picker mode
datePickerS.datePickerMode = .date
}
func createDatePickerForEnd() {
// center text in field
endDateTextFieldConsiderations.textAlignment = .center
// toolbar
let toolbar = UIToolbar()
toolbar.sizeToFit()
// barbutton
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressedEnd))
toolbar.setItems([doneButton], animated: true)
// assign toolbar to textfield
endDateTextFieldConsiderations.inputAccessoryView = toolbar
// assign datePicker to text field
endDateTextFieldConsiderations.inputView = datePickerE
// date picker mode
datePickerE.datePickerMode = .dateAndTime
}
#objc func donePressedStart() {
// formatter
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
startDateTextFieldConsiderations.text = formatter.string(from: datePickerS.date)
self.view.endEditing(true)
}
#objc func donePressedEnd() {
// formatter
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .medium
endDateTextFieldConsiderations.text = formatter.string(from: datePickerE.date)
self.view.endEditing(true)
}
#IBAction func testTapped(_ sender: Any) {
print(ConsiderationsTestViewController.numberOfPeople)
}
}
extension ConsiderationsTestViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
//print("did Finish Picking Media")
if let image = info[UIImagePickerController.InfoKey(rawValue: "UIImagePickerControllerEditedImage")] as? UIImage{
selectedImage = image
CompanyImage.image = image
}
dismiss(animated: true, completion: nil)
}
}
extension ConsiderationsTestViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ConsiderationsTestViewController.numberOfPeople.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: AddPersonCell, for: indexPath) as! ConsiderationsCell
return cell
}
}
Here is the code for the second view controller:
import UIKit
import Firebase
class DropDownCellViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UITextFieldDelegate {
var numberOfPeople = [String]()
var users = [User]()
var userName = [String]()
var filteredNames: [String]!
let dropDownCell = "dropDownCell"
var emptyField = [String]()
override func viewDidLoad() {
super.viewDidLoad()
updateDataArray()
tableView.register(UserCell.self, forCellReuseIdentifier: dropDownCell)
filteredNames = userName
tableView.delegate = self
tableView.dataSource = self
nameTextField.delegate = self
tableView.isHidden = true
// Manage tableView visibility via TouchDown in textField
nameTextField.addTarget(self, action: #selector(textFieldActive), for: UIControl.Event.touchDown)
}
#IBOutlet weak var nameTextField: NoCopyPasteUITextField!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var gridTextField: UITextField!
#IBOutlet weak var storyTextField: UITextField!
#IBOutlet weak var compensationTextField: UITextField!
#IBAction func textFieldChanged(_ sender: AnyObject) {
tableView.isHidden = true
}
#IBAction func cancelButtonTapped(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
#IBAction func addButtonTapped(_ sender: Any) {
ConsiderationsTestViewController.numberOfPeople.append("\(nameTextField.text!)")
self.navigationController?.popViewController(animated: true)
print(ConsiderationsTestViewController.numberOfPeople)
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
}
}
let searchController = UISearchController(searchResultsController: nil)
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
guard let touch:UITouch = touches.first else
{
return;
}
if touch.view != tableView
{
nameTextField.endEditing(true)
tableView.isHidden = true
}
}
#objc func textFieldActive() {
tableView.isHidden = !tableView.isHidden
}
// MARK: UITextFieldDelegate
func textFieldDidEndEditing(_ textField: UITextField) {
// TODO: Your app can do something when textField finishes editing
print("The textField ended editing. Do something based on app requirements.")
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return filteredNames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: dropDownCell, for: indexPath) as!
UserCell
let user = users[indexPath.row]
cell.textLabel?.text = user.name
if let profileImageUrl = user.profileImageUrl {
cell.profileImageView.loadImageUsingCacheWithUrlString(urlString: profileImageUrl)
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 72
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Row selected, so set textField to relevant value, hide tableView
// endEditing can trigger some other action according to requirements
nameTextField.text = userName[indexPath.row]
tableView.isHidden = true
nameTextField.endEditing(true)
}
//Mark: Search Bar Config
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredNames = []
if searchText == "" {
filteredNames = userName
}
else {
for names in userName {
if names.lowercased().contains(searchText.lowercased()) {
filteredNames.append(names)
}
}
}
self.tableView.reloadData()
}
func updateDataArray() {
Database.database().reference().child("users").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
//self.setValuesForKeys(dictionary)
user.name = dictionary["name"] as? String
user.email = dictionary["email"] as? String
user.facebookUrl = dictionary["facebookUrl"] as? String
user.instagramUrl = dictionary["instagramUrl"] as? String
user.linkedInUrl = dictionary["linkedInUrl"] as? String
user.profileImageUrl = dictionary["profileImageUrl"] as? String
user.twitterUrl = dictionary["twitterUrl"] as? String
user.id = dictionary["id"] as? String
user.userType = dictionary["userType"] as? String
self.users.append(user)
self.userName.append(user.name!)
self.filteredNames.append(user.name!)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}, withCancel: nil)
}
}
I am trying to append the label with a function that is called in the addButton function called renameLabel. Here is the func rename label code:
func renameLabel() {
let cell = ConsiderationsTestViewController.init().tableView.dequeueReusableCell(withIdentifier: AddPersonCell) as! ConsiderationsCell
cell.nameLabelC.text = ("\(ConsiderationsTestViewController.numberOfPeople)")
}
I am now getting the error:
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Any help would be awesome!
First of all create a protocol above second VC.
protocol UpdatingArrayDelegate: class {
func updateArray(newArray: [String])
}
And inside the second VC create:
var updatingArrayDelegate: UpdatingArrayDelegate!
Then before pushing to the second VC, set the delegate to self:
#objc func pushToSecondVCButtonTapped() {
let destVC = secondVC()
updatingArrayDelegate = self
self.navigationController?.pushViewController(destVC, animated: true)
// if you don't have self.navigationController use:
// present(destVC(), animated: true, completion: nil)
}
Now when finishing the edit on secondVC when pressing your addButton do the following:
#objc func doneButtonTapped() {
updatingArrayDelegate?.updateArray(newArray: myNewArrayCreatedOnThisSecondVC)
self.navigationController?.popViewController(animated: true)
}
Then add the delegate function on first
extension FirstVC: UpdatingArrayDelegate {
func updateArray(newArray: [String]) {
print("updateArray")
let myCell = myTableView.cellForRow(at: IndexPath(row: x, section: y)) as! MyCell
myCell.textLabel1.text = newArray[0]
myCell.textLabel2.text = newArray[1]
myCell.textLabel3.text = newArray[2]
myCell.textLabel4.text = newArray[3]
}
}
You can think about delegate like this:
The boss (secondVC) gives a the worker (firstVC) a protocol of what to do. The Worker reads the protocol and is doing the work with his function given in the protocol. Before the worker runs to the boss, he must be sure that he can do the next task (written on the protocol he is going to have.)

How can I take the sum of table view cells' data in Swift?

I'm trying to add up all of the amount data and send to a different table view cell. I think I need to convert String to Double before I can do this, but I'm not sure how to do this either. Does anyone know how you can take the sum of data and present it somewhere else? I'm very new to swift and am having trouble figuring out where I need to write this code.
import Foundation
struct Income: Codable {
var name: String
var amount: String
init(name: String, amount: String) {
self.name = name
self.amount = amount
}
static let DocumentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("incomes").appendingPathExtension("plist")
static func loadSampleIncomes() -> [Income] {
return [
Income(name: "Main Income", amount: "0"),
Income(name: "Secondary Income", amount: "0"),
Income(name: "Interest Income", amount: "0")]
}
static func saveToFile(incomes: [Income]) {
let propertyListEncoder = PropertyListEncoder()
let codedIncomes = try? propertyListEncoder.encode(incomes)
try? codedIncomes?.write(to: ArchiveURL, options: .noFileProtection)
}
static func loadFromFile() -> [Income]? {
guard let codedIncomes = try? Data(contentsOf: ArchiveURL) else { return nil }
let propertyListDecoder = PropertyListDecoder()
return try? propertyListDecoder.decode(Array<Income>.self, from: codedIncomes)
}
}
import UIKit
class IncomeTableViewController: UITableViewController {
var incomes: [Income] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: CGRect.zero)
if let savedIncomes = Income.loadFromFile() {
incomes = savedIncomes
} else {
incomes = Income.loadSampleIncomes()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
// table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return incomes.count
} else {
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "incomeCell", for: indexPath) as! IncomeTableViewCell
let income = incomes[indexPath.row]
cell.update(with: income)
cell.showsReorderControl = true
return cell
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
override func tableView(_ tableView: UITableView, commit
editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath:
IndexPath) {
if editingStyle == .delete {
incomes.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: . automatic)
Income.saveToFile(incomes: incomes)
}
}
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
let movedIncome = incomes.remove(at: fromIndexPath.row)
incomes.insert(movedIncome, at: to.row)
tableView.reloadData()
}
#IBAction func editButtonTapped(_ sender: UIBarButtonItem) {
let tableViewEditingMode = tableView.isEditing
tableView.setEditing(!tableViewEditingMode, animated: true)
}
// Navigation
#IBAction func unwindToIncomeTableView(segue:UIStoryboardSegue) {
guard segue.identifier == "saveIncomeUnwind",
let sourceViewController = segue.source as? AddEditIncomeTableViewController,
let income = sourceViewController.income else { return }
if let selectedIndexPath = tableView.indexPathForSelectedRow {
incomes[selectedIndexPath.row] = income
tableView.reloadRows(at: [selectedIndexPath],
with: .none)
} else {
let newIndexPath = IndexPath(row: incomes.count, section: 0)
incomes.append(income)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
Income.saveToFile(incomes: incomes)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EditIncome" {
let indexPath = tableView.indexPathForSelectedRow!
let income = incomes[indexPath.row]
let navController = segue.destination as! UINavigationController
let addEditIncomeTableViewController = navController.topViewController as! AddEditIncomeTableViewController
addEditIncomeTableViewController.income = income
}
}
}
import UIKit
class AddEditIncomeTableViewController: UITableViewController {
#IBOutlet weak var saveButton: UIBarButtonItem!
#IBOutlet weak var incomeNameTextField: UITextField!
#IBOutlet weak var incomeAmountTextField: UITextField!
var income: Income?
override func viewDidLoad() {
super.viewDidLoad()
if let income = income {
incomeNameTextField.text = income.name
incomeAmountTextField.text = income.amount
}
updateSaveButtonState()
}
override func prepare(for segue: UIStoryboardSegue, sender:
Any?) {
super.prepare(for: segue, sender: sender)
guard segue.identifier == "saveIncomeUnwind" else { return }
let name = incomeNameTextField.text ?? ""
let amount = incomeAmountTextField.text ?? ""
income = Income(name: name, amount: amount)
}
func updateSaveButtonState() {
let nameText = incomeNameTextField.text ?? ""
let amountText = incomeAmountTextField.text ?? ""
saveButton.isEnabled = !nameText.isEmpty && !amountText.isEmpty
}
#IBAction func textEditingChanged(_ sender: UITextField) {
updateSaveButtonState()
}
}
this is the table view controller in which I want the new data to be presented.
import UIKit
class BudgetHomeTableViewController: UITableViewController {
var incomes: [Income] = []
var expenses: [Expense] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: CGRect.zero)
if let savedIncomes = Income.loadFromFile() {
incomes = savedIncomes
} else {
incomes = Income.loadSampleIncomes()
}
if let savedExpenses = Expense.loadFromFile() {
expenses = savedExpenses
} else {
expenses = Expense.loadSampleExpenses()
}
}
}
This is the cell where the data will be presented specifically
import UIKit
class BugetBalanceCell: UITableViewCell {
#IBOutlet weak var budgetBalanceText: UILabel!
var incomes: [Income] = []
var expenses: [Expense] = []
override func awakeFromNib() {
super.awakeFromNib()
let incomeTotal = incomes.map({Double($0.amount) ?? 0}).reduce(0, +)
let expenseTotal = expenses.map({Double($0.amount) ?? 0}).reduce(0, +)
let balance = incomeTotal - expenseTotal
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
First define your income,expenses arrays and load data
var incomes: [Income] = []
var expenses: [Income] = []
if let savedIncomes = Income.loadFromFile() {
incomes = savedIncomes
} else {
incomes = Income.loadSampleIncomes()
}
//do the same thing for load data to expenses array
you can get your balance = incomes - expenses using
let incomeTotal = incomes.map({Double($0.amount) ?? 0}).reduce(0, +)
let expenseTotal = expenses.map({Double($0.amount) ?? 0}).reduce(0, +)
let balance = incomeTotal - expenseTotal
use this value where you want to show in next Table View conntroller
(income as NSArray).valueForKeyPath("#sum.self")

getting the error of NSUnknownKeyException', reason: setValue:forUndefinedKey: this class is not key value coding-compliant for the key description

I am facing the issue of passing the data from HomeViewController to PostDetailViewController,
I have checked the classes connected to the View Controllers are correct, class connected to the XIB file is PostTableViewCell,
and still getting this error of
'NSUnknownKeyException', reason:
'[
setValue:forUndefinedKey:]: this class is not key value
coding-compliant for the key description
upon clicking the tablecell
HOMEVIEWCONTROLLER
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView:UITableView!
var posts = [Post]()
var db: Firestore!
var postKey:String = ""
private var documents: [DocumentSnapshot] = []
//public var posts: [Post] = []
private var listener : ListenerRegistration!
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
self.navigationController?.navigationBar.isTranslucent = false
tableView = UITableView(frame: view.bounds, style: .plain)
let cellNib = UINib(nibName: "PostTableViewCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "postCell")
tableView.backgroundColor = UIColor(white: 0.90,alpha:1.0)
view.addSubview(tableView)
var layoutGuide:UILayoutGuide!
if #available(iOS 11.0, *) {
layoutGuide = view.safeAreaLayoutGuide
} else {
// Fallback on earlier versions
layoutGuide = view.layoutMarginsGuide
}
tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
retrieveAllPosts()
//checkForUpdates()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func handleLogout(_ sender:Any) {
try! Auth.auth().signOut()
self.dismiss(animated: false, completion: nil)
}
func retrieveAllPosts(){
let postsRef = Firestore.firestore().collection("posts").limit(to: 50)
postsRef.getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
// self.postKey = document.documentID
let username = data["username"] as? String ?? ""
let postTitle = data["postTitle"] as? String ?? ""
let postcategory = data["postcategory"] as? String ?? ""
let postContent = data["postContent"] as? String ?? ""
let newSourse = Post( _documentId: document.documentID, _username: username, _postTitle: postTitle, _postcategory: postcategory, _postContent: postContent)
self.posts.append(newSourse)
print(self.postKey)
}
self.tableView.reloadData()
}
}
}
}
/* postsRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.posts = querySnapshot!.documents.flatMap({Post(dictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}*/
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
cell.set(post: posts[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let post = self.posts[indexPath.row]
//print("row selected: \(indexPath.row)")
//Swift.print(post._documentId!)
let postKey = post._documentId
let postName = post._username
print(postKey! + postName!)
performSegue(withIdentifier: "toDetailView", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//segue.forward(posts, to: segue.destination)
guard let details = segue.destination as? PostDetailViewController,
let index = tableView.indexPathForSelectedRow?.row
else {
return
}
details.detailView = posts[index]
}
}
POSTTABLEVIEWCELL
class PostTableViewCell: UITableViewCell {
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var subtitleLabel: UILabel!
#IBOutlet weak var postTextLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
// profileImageView.layer.cornerRadius = profileImageView.bounds.height / 2
// profileImageView.clipsToBounds = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func set(post:Post) {
usernameLabel.text = post._username
postTextLabel.text = post._postTitle
subtitleLabel.text = post._postcategory
}
}
POST DETAIL VIEW CONTROLLER
class PostDetailViewController: UIViewController {
#IBOutlet var usernamelabel: UILabel!
#IBOutlet var posttitlelabel: UILabel!
#IBOutlet var postIdlabel: UILabel!
// #IBOutlet var description: UILabel!
#IBOutlet var postcategorylabel: UILabel!
var detailView: Post?
var postId:String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
postIdlabel?.text = detailView?._documentId
posttitlelabel?.text = detailView?._postTitle
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
This situations usually happens when you have already set IBOutlet in your XIB file and you comment out it's connecting outlets in code.
Here in your case, In your PostDetailViewController
// #IBOutlet var description: UILabel!
You have commented the description Label, but IBOutlet is still connected in your XIB file.
So, Look for your XIB file and checkout for active IBOutlet Connections and remove it for description label, Clean, Build and Run.
Hope it helps.

iOS master detail sec

I'm working on a Contacts app which I'm developing using swift. I recently implemented sections and now the detail controller is not working properly. Whenever I click on a contact, it shows details for some other contact. I think the main problem is in prepareforsegue function but I can't figure it out. Help Pls!
//
// ContactListViewController.swift
// TechOriginators
//
// Created by Xcode User on 2017-10-09.
// Copyright © 2017 Xcode User. All rights reserved.
//
import UIKit
import Foundation
class ContactListViewController : UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating {
#IBOutlet var contactsTableView : UITableView!
var contactViewController: ContactViewController? = nil
var contacts : [Contact] = [] {
didSet{
self.contactsTableView.reloadData()
}
}
//Variables to implement sections in UITableView
var sectionLetters: [Character] = []
var contactsDict = [Character: [String]]()
var contactsName = [String]()
//Search Controller
let searchController = UISearchController(searchResultsController: nil)
//Variable to store filtered contacts through search
var filteredContacts = [Contact]()
//Function to show details of a contact record
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail"{
if let indexPath = contactsTableView.indexPathForSelectedRow{
let object = contacts[indexPath.row]
//let object = contactsDict[sectionLetters[indexPath.section]]![indexPath.row]
let controller = segue.destination as! ContactViewController
controller.detailItem = object
}
}
}
func createDict(){
contactsName = contacts.map{$0.FirstName}
//print(contactsName)
sectionLetters = contactsName.map{ (firstName) -> Character in
return firstName[firstName.startIndex]
}
sectionLetters.sorted()
sectionLetters = sectionLetters.reduce([], { (list, firstName) -> [Character] in
if !list.contains(firstName){
return list + [firstName]
}
return list
})
for entry in contactsName{
if contactsDict[entry[entry.startIndex]] == nil {
contactsDict[entry[entry.startIndex]] = [String]()
}
contactsDict[entry[entry.startIndex]]!.append(entry)
}
for (letter, list) in contactsDict{
contactsDict[letter] = list.sorted()
}
print(sectionLetters)
print(contactsDict)
}
// //Function to load contacts
func loadContacts()
{
self.contacts = getContacts()
}
/*private let session: URLSession = .shared
func loadContacts()
{
//let url = URL(string: "http://127.0.0.1:8080/api/Contacts")!
let url = URL(string: "http://10.16.48.237/api/Contacts")!
let task = session.dataTask(with: url) { (data, response, error) in
print("dataRecieved \(data)")
print("error \(error)")
print ("response \(response)")
guard let data = data else { return }
do {
self.contacts = try parse(data)
}
catch {
print("JSONParsing Error: \(error)")
}
}
task.resume() // firing the task
}*/
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44
}
func numberOfSections(in tableView: UITableView) -> Int {
return sectionLetters.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if self.searchController.isActive {
return nil
} else {
return String(sectionLetters[section])
}
//return String(sectionLetters[section])
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.isActive && searchController.searchBar.text != "" {
return filteredContacts.count
}
//return self.contacts.count
print(contactsDict[sectionLetters[section]]!.count)
return contactsDict[sectionLetters[section]]!.count
}
//Function to display cells in UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell" , for : indexPath)
let contact = contacts[indexPath.row]
let object: Contact
if searchController.isActive && searchController.searchBar.text != ""{
let contact = self.filteredContacts[indexPath.row]
cell.textLabel?.text = contact.FirstName + " " + contact.LastName
}else{
//contact = contactsDict[sectionLetters[indexPath.section]]![indexPath.row]
//cell.textLabel?.text = contact.FirstName + " " + contact.LastName
cell.textLabel?.text = contactsDict[sectionLetters[indexPath.section]]![indexPath.row]
}
return cell
}
//Function to update search results when the user types in search bar
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
func updateSearchResultsForSearchController(searchController: UISearchController){
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
//Function to find matches for text entered in search bar
func filterContentForSearchText(searchText: String){
filteredContacts = contacts.filter{p in
var containsString = false
if p.FirstName.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.LastName.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.Division.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.Department.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.BusinessNumber.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.HomePhone.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.CellularPhone.lowercased().contains(searchText.lowercased()){
containsString = true
}else if p.Role.lowercased().contains(searchText.lowercased()){
containsString = true
}
return containsString
}
contactsTableView.reloadData()
}
//Function to sorts contacts by First Name
func sortContacts() {
contacts.sort() { $0.FirstName < $1.FirstName }
contactsTableView.reloadData();
}
override func viewDidLoad() {
super.viewDidLoad()
loadContacts()
sortContacts()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
self.definesPresentationContext = true
createDict()
contactsTableView.tableHeaderView = searchController.searchBar
//contactsTableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
//
// ContactViewController.swift
// TechOriginators
//
// Created by Xcode User on 2017-10-09.
// Copyright © 2017 Xcode User. All rights reserved.
//
import UIKit
class ContactViewController: UIViewController {
//#IBOutlet weak var detailDescriptionLabel: UILabel!
#IBOutlet weak var firstNameLabel: UILabel?
#IBOutlet weak var lastNameLabel: UILabel?
#IBOutlet weak var divisionLabel: UILabel?
#IBOutlet weak var departmentLabel: UILabel?
#IBOutlet weak var businessPhoneButton: UIButton?
#IBOutlet weak var homePhoneButton: UIButton?
#IBOutlet weak var cellularPhoneButton: UIButton?
#IBOutlet weak var roleLabel: UILabel?
/*#IBOutlet weak var firstNameLabel: UILabel?
#IBOutlet weak var lastNameLabel: UILabel?
#IBOutlet weak var phoneButton: UIButton?
#IBOutlet weak var emailButton: UIButton?*/
func configureView() {
// Update the user interface for the detail item.
if let detail = self.detailItem {
self.title = detail.FirstName + " " + detail.LastName
firstNameLabel?.text = detail.FirstName
lastNameLabel?.text = detail.LastName
divisionLabel?.text = detail.Division
departmentLabel?.text = detail.Department
businessPhoneButton?.setTitle(detail.BusinessNumber, for: .normal)
homePhoneButton?.setTitle(detail.HomePhone, for: .normal)
cellularPhoneButton?.setTitle(detail.CellularPhone, for: .normal)
roleLabel?.text = detail.Role
}
}
#IBAction func businessPhoneButtonPressed(sender: UIButton){
if let bPhone = detailItem?.BusinessNumber{
if let url = URL(string: "tel://\(bPhone)"), UIApplication.shared.canOpenURL(url){
if #available(iOS 10, *){
UIApplication.shared.open(url)
}else{
UIApplication.shared.openURL(url)
}
}
}
}
#IBAction func homePhoneButtonPressed(sender: UIButton){
if let hPhone = detailItem?.HomePhone{
if let url = URL(string: "tel://\(hPhone)"), UIApplication.shared.canOpenURL(url){
if #available(iOS 10, *){
UIApplication.shared.open(url)
}else{
UIApplication.shared.openURL(url)
}
}
}
}
#IBAction func cellularPhoneButtonPressed(sender: UIButton){
if let cPhone = detailItem?.CellularPhone{
/*if let url = NSURL(string: "tel://\(phone)"){
UIApplication.shared.open(url as URL, options: [:], completionHandler: nil)
}*/
if let url = URL(string: "tel://\(cPhone)"), UIApplication.shared.canOpenURL(url) {
if #available(iOS 10, *) {
UIApplication.shared.open(url)
} else {
UIApplication.shared.openURL(url)
}
}
}
}
/*#IBAction func emailButtonPressed(sender: UIButton){
if let email = detailItem?.email{
if let url = URL(string:"mailto:\(email)"){
UIApplication.shared.open(url as URL)
}
}
}*/
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
configureView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var detailItem: Contact? {
didSet {
// Update the view.
self.configureView()
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
I suggest using the same data structure for all your table operations to be consistent. Try populating your dictionary as:
var contactsDict = [Character: [Contact]]()
That way, you can always use the section to find the right array and the row to find the right offset in the array.
Separating the names from the contacts in the table's data is inviting them to get out of synch.

Inconsistent behavior from newly added UITableView cells in Swift

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()
}
}

Resources