checkbox button in tableview in Swift - swift

I'm trying to create a checkbox functionality in a tableview .
It's a to-do list where if the checkbox is pressed the cell hides and appears as completed in a different section.
Coming from C# this was very straightforward to do but in swift there isn't even a checkbox button to start with :(...
I made it to work by adding a button with two images(checked, unchecked) to a custom prototype cell in IB.
Since you can't have the tableView and the in-cell-button declared in the same viewcontroller/class I had to subclass the tableViewCell.
Now, how do I access the checkbox from didSelectRowAtIndexPath ? When I select the cell the event fires but when I press the checkbox button in the same cell nothing fires and I can't hide the cell.
var indexTag = checkBoxImage.tag
//this is what I have in TableViewCell class
#IBAction func checkBoxInCell(sender: UIButton) {
checkBoxImage.setImage(UIImage(named:"checked"),forState:UIControlState.Normal)
if isChecked != false {
isChecked = false
cellitemcontent.removeAtIndex(indexTag)
//can't access the cell from here to update the tableview
}
else {
isChecked = true
checkBoxImage.setImage(UIImage(named:"unchecked"),forState:UIControlState.Normal)
}
}
//this is what I have in my FirstViewController that contains the tableview
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
cellitemcontent.removeAtIndex(indexTag)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
//when I press the checkBoxImage button in the cell it doesn't fire this event...
}

I use sample code below to implement check box button in cell (Xcode 7/ Swift 2.0):
-In viewDidLoad {}:(save check box state in each cell to .plist file)
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let fnstring = (documentsPath as String)+"/yr-filename.plist"
let fM = NSFileManager.defaultManager()
//if (let a != (fM.fileExistsAtPath(fnstring))) {
if !(fM.fileExistsAtPath(fnstring)) {
let onlyarr = NSMutableArray()
for var i = 0 ; i < 5; ++i{ // number of sections
var arr = NSArray()
switch i { // number cell in each section
case 0: arr = [Int](count: 12, repeatedValue: 0) // 0 means unchecked box.
case 1: arr = [Int](count: 13, repeatedValue: 0)
case 2: arr = [Int](count: 14, repeatedValue: 0)
case 3: arr = [Int](count: 15, repeatedValue: 0)
case 4: arr = [Int](count: 16, repeatedValue: 0)
default: arr = [Int]()
}
onlyarr.addObject(arr)
}
onlyarr.writeToFile(fnstring, atomically: false)
}
- In override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { }
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let fnstring = (documentsPath as String)+"/yr-filename.plist"
let fM = NSFileManager.defaultManager()
if fM.fileExistsAtPath(fnstring) {
let chcklstDick = NSMutableArray(contentsOfURL: NSURL(fileURLWithPath: fnstring))
let chcklstsortedCat: NSArray? = chcklstDick
let ttt: NSMutableArray = chcklstsortedCat?.objectAtIndex(indexPath.section) as! NSMutableArray
if ttt.count > 0 {
var img = UIImage()
let j : Int = ttt.objectAtIndex(indexPath.row) as! Int
NSLog("j: %i", j)
if j > 0 {
img = UIImage(named: "checked")!
}else {
img = UIImage(named: "unchecked")!
}
let bttn : UIButton = UIButton(type: UIButtonType.Custom)
bttn.frame = CGRectMake(0, 0, img.size.width, img.size.height)
bttn.setBackgroundImage(img, forState: UIControlState.Normal)
bttn.addTarget(self, action:"chckBttnTapped:eventy:", forControlEvents: UIControlEvents.TouchUpInside)
cell.accessoryView = bttn
}
}else {NSLog("nonononononononno")}
- In func chckBttnTapped(sender: AnyObject, eventy event: AnyObject) { }
let touches: NSSet = event.allTouches()!
let touch: UITouch = touches.anyObject()! as! UITouch
let crrntTouchPos : CGPoint = touch.locationInView(self.tableView)
let idxpth: NSIndexPath = self.tableView.indexPathForRowAtPoint(crrntTouchPos)!
if idxpth.row != NSNotFound {
self.tableView(self.tableView, accessoryButtonTappedForRowWithIndexPath: idxpth)
}
-In override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) { }
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let fnstring = (documentsPath as String)+"/yr-filename.plist"
let fM = NSFileManager.defaultManager()
if fM.fileExistsAtPath(fnstring) {
let chcklstDick = NSMutableArray(contentsOfURL: NSURL(fileURLWithPath: fnstring))
let chcklstsortedCat: NSMutableArray? = chcklstDick
let ttt: NSMutableArray = chcklstsortedCat?.objectAtIndex(indexPath.section) as! NSMutableArray
if ttt.count > 0 {
let j : Int = ttt.objectAtIndex(indexPath.row) as! Int
var newimg = UIImage()
if j == 0 {
newimg = UIImage(named: "checked")!
}else {
newimg = UIImage(named: "unchecked")!
}
let cell = tableView.dequeueReusableCellWithIdentifier("cellID", forIndexPath: indexPath)
let bttn : UIButton = UIButton(type: UIButtonType.Custom)
bttn.frame = CGRectMake(0, 0, newimg.size.width, newimg.size.height)
bttn.setBackgroundImage(newimg, forState: UIControlState.Normal)
bttn.addTarget(self, action:"chckBttnTapped:eventy:", forControlEvents: UIControlEvents.TouchUpInside)
cell.accessoryView = bttn
self.tableView.reloadData()
self.tableView.reloadInputViews()
ttt.replaceObjectAtIndex(indexPath.row, withObject: 1 - j)
chcklstsortedCat?.replaceObjectAtIndex(indexPath.section, withObject: ttt)
chcklstsortedCat?.writeToFile(fnstring, atomically: false)
}
}
Hope its useful.

I know what you are trying to accomplish, but I would tackle it a different way. Perhaps in your model, you could have an array of tasks that are pending, and another array of tasks that are completed.
The number of sections can be 2. The number of rows in sections 0 and 1 can be the number of elements in the first and second arrays respectively.
When didSelectRowAtIndexPath is called, you can remove the item at that index in the first array and add the task to the second array. Then you must call tableView.reloadData().
I know you want to just pick up the row and change the placement of it, but in iOS the cells get reused. It's best to update the data source and then reload the table.
For the checkmarks, you can ensure that the items in Section 0 do not have the checkmark accessory, while the items in Section 1 do. You would set the accessory in cellForRowAtIndexPath after the cell has been dequeued.

Related

How do you pass data from tableview cell button with a UILongPressGestureRecognizer?

I have a button in my cell that if the user holds for a certain length of time it will trigger a popup. I am having trouble passing the cell data with the long press button.
Heres how I submit and pass data with a regular tap...
cell.addButton.tag = (indexPath as NSIndexPath).row
cell.addButton.addTarget(self, action: #selector(Dumps.addAction(_:)), for: UIControl.Event.touchUpInside)
.
#IBAction func addAction(_ sender: Any) {
let tag = (sender as AnyObject).tag
let cell = tableView.cellForRow(at: IndexPath.init(row: tag!, section: 0)) as! DumpsCell01
codeData = cell.codeField.text! }
The above works fine.
Heres how I submit the button with the long press gesture. Its passing nil through _sender I think
cell.deleteButton.tag = (indexPath as NSIndexPath).row
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(Dumps.deleteAction(_:)))
cell.deleteButton.addGestureRecognizer(longGesture)
.
#objc func deleteAction(_ sender: UIGestureRecognizer){
let tag = (sender as AnyObject).tag
let cell = tableView.cellForRow(at: IndexPath.init(row: tag!, section: 0)) as! DumpsCell01
cell.codeLabel.backgroundColor = UIColor.red }
How would I pass the data through this method?
You should be using the tag of the UIButton instead of the UILongPressGestureRecognizer as you have done above.
func deleteAction(_ sender: UILongPressGestureRecognizer) {
guard let tag = (sender.view as? UIButton)?.tag else { return }
let cell = tableView.cellForRow(at: IndexPath(row: tag, section: 0)) as? DumpsCell01
cell?.codeLabel.backgroundColor = .red
}
Note: I've also avoided force unwrapping as you should too through-out the project.

How to prevent UILabel.textColor to change in a dequeueReusableCell when scrolling?

I have this code in my ViewController named PlayViewController:
var words = [String]()
var numberOfRevealedLabels = 1
var indexGA = 0
var indexWA = 0
override func viewDidLoad() {
super.viewDidLoad()
playTableView.delegate = self
playTableView.dataSource = self
view.isUserInteractionEnabled = true
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeRight.direction = .right
view.addGestureRecognizer(swipeRight)
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeLeft.direction = .left
view.addGestureRecognizer(swipeLeft)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "PlayTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? PlayTableViewCell else {
fatalError("The dequeued cell is not an instance of PlayTableViewCell.")
}
cell.wordLabel.text = words[indexPath.row]
var count = 0
while (count < words.count)
{
if (indexPath.row == count) {
if (count == 0) {
cell.wordLabel.isHidden = false
}
if (indexGA - 1 == count) {
cell.wordLabel.textColor = UIColor.green
} else if (indexWA - 1 == count) {
cell.wordLabel.textColor = UIColor.red
}
}
count += 1
}
cell.wordLabel.isHidden = !(indexPath.row <= numberOfRevealedLabels - 1)
return cell
}
#objc func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizer.Direction.right:
indexGA = numberOfRevealedLabels
numberOfRevealedLabels += 1
playTableView.reloadData()
case UISwipeGestureRecognizer.Direction.left:
indexWA = numberOfRevealedLabels
numberOfRevealedLabels += 1
playTableView.reloadData()
default:
break
}
}
}
So basically, the app does the following stuff:
It prompts the first word of the list in black.
Swiping right changes the color of the word to green, left to red. Swipes also prompts the next word in black.
numberOfRevealedLabels counts the number of words currently displayed, indexGA helps to track the position of the word to turn in green, same for indexWA for the red ones.
When I scroll down to read the next word (I start scrolling at the twelfth word), it's prompted in the same color of the previous word (green if I swiped right or red if left). Also, the first words of the list change their color randomly (either black, green or red).
I've read threads like these ones and I know it's because of dequeueReusableCell.
My code has no else condition. When adding one to set the .textColor to black turns all the words but the last two swiped to black.
To fix the else statement, I can code something like this:
else {
if (cell.wordLabel.textColor == UIColor.green) {
cell.wordLabel.textColor = UIColor.green
} else if (cell.wordLabel.textColor == UIColor.red) {
cell.wordLabel.textColor = UIColor.red
} else {
cell.wordLabel.textColor = UIColor.black
}
}
Unfortunately, it didn't solve my problem, labels in cells keep changing colors in a weird fashion (plus, it adds a few more ugly LOC to a not so logic loop).
Last thing I've tried: to set wordLabel.textColor = UIColor.blackin PlayTableViewCell.swiftbut it didn't fixed anything.
I'm out of ideas/logic, any help would be very much appreciated!
You should set cell.wordLabel.textColor = UIColor.black right after you call cell.wordLabel.text = words[indexPath.row].
So it should look like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "PlayTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? PlayTableViewCell else {
fatalError("The dequeued cell is not an instance of PlayTableViewCell.")
}
cell.wordLabel.text = words[indexPath.row]
cell.wordLabel.textColor = UIColor.black //HERE
var count = 0
while (count < words.count)
{
if (indexPath.row == count) {
if (count == 0) {
cell.wordLabel.isHidden = false
}
if (indexGA - 1 == count) {
cell.wordLabel.textColor = UIColor.green
} else if (indexWA - 1 == count) {
cell.wordLabel.textColor = UIColor.red
}
}
count += 1
}
cell.wordLabel.isHidden = !(indexPath.row <= numberOfRevealedLabels - 1)
return cell
}
Adding an else doesn't solve the problem because it's placed in a nested if statement. A cell with the color can still be reused since you aren't setting a default color before any if statements. Adding it outside of all if statements will ensure the color is always black unless a condition is met.

2 items in a cell with same tapGestureRecognizer (swift)

in my app i have a tableview with a profile image and a username label. If you click on one of the 2 then the need to do this function:
func goToProfileScreen(gesture: UITapGestureRecognizer) {
self.performSegueWithIdentifier("profile", sender: nil)
}
however if i try to implement this in my cellForRowAtIndexPath it only works for the last time i've added it.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("NewsCell") as? NewsCell {
let post = self.posts[indexPath.row]
cell.request?.cancel()
let profileTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(NewsVC.goToProfileScreen(_:)))
profileTapRecognizer.numberOfTapsRequired = 1
// profileTapRecognizer.delegate = self
cell.profileImg.tag = indexPath.row
cell.profileImg.userInteractionEnabled = true
cell.profileImg.addGestureRecognizer(profileTapRecognizer)
cell.usernameLabel.tag = indexPath.row
cell.usernameLabel.userInteractionEnabled = true
cell.usernameLabel.addGestureRecognizer(profileTapRecognizer)
var img: UIImage?
if let url = post.profileImageURL {
if url != "" {
img = NewsVC.imageCache.objectForKey(url) as? UIImage
}
}
cell.configureCell(post, img: img)
cell.selectionStyle = .None
return cell
} else {
return NewsCell()
}
}
so now it works for the username label. if i put the usernamelabel first in the code and then the profileImg, then it only works for the profileImg?
how can i get it to work for both of them?
You need to use 2 different tapRecognizers, because a UITapGestureRecognizer can only be attached to one view.
("They are objects that you attach to a view", Apple Doku)
let profileTapRecognizer1 = UITapGestureRecognizer(target: self, action: #selector(NewsVC.goToProfileScreen(_:)))
let profileTapRecognizer2 = UITapGestureRecognizer(target: self, action: #selector(NewsVC.goToProfileScreen(_:)))
profileTapRecognizer1.numberOfTapsRequired = 1
profileTapRecognizer2.numberOfTapsRequired = 1
// profileTapRecognizer1.delegate = self
// profileTapRecognizer2.delegate = self
cell.profileImg.tag = indexPath.row
cell.profileImg.userInteractionEnabled = true
cell.profileImg.addGestureRecognizer(profileTapRecognizer1)
cell.usernameLabel.tag = indexPath.row
cell.usernameLabel.userInteractionEnabled = true
cell.usernameLabel.addGestureRecognizer(profileTapRecognizer2)

UITableView cellForRowAtIndexPath function not updating after reload function

I am trying to reload data in my tableview after getting the data from a delegate method. But the issue is that all the data is not coming through to the tableView function cellForRowAtIndexPath. I have set the variable self.restNames to hold the values from the delegate method which comes around 4 values but not all of them show up in the function cellForRowAtIndexPath. When I change the tabs and go back to the tableView, some data does come through but not all of it.Will appreciate any help on this. Apologies if I have missed something, I am new to Swift and haven't raised much questions in StackOverflow.
Regards,
Saurabh
Below is my the code for the tableViewController
class RestTableViewController: UITableViewController,getDistanceTime {
var RestTable = [Restaurant]()
let label = UILabel()
let menuUrl = "menu url"
let listProjectUrl = "url"
override func viewDidLoad() {
super.viewDidLoad()
let headerView = self.tableView
headerView.tableHeaderView?.frame = CGRectMake(0, 30, self.view.frame.width, 40)
headerView.tableHeaderView?.backgroundColor = UIColor.redColor()
self.tableView.tableHeaderView = self.tableView.tableHeaderView
self.tableView.tableHeaderView?.frame = CGRectMake(0, 30, self.view.frame.width, 40)
let token = keychain[""]
let menuData = Alamofire.request(Method.GET, self.menuUrl, headers: ["Authorization":"JWT \(token!)"])
menuData.responseJSON{ response in
let data = JSON(response.result.value!)
var localmenu = [menu]()
for (_,item) in data{
let menuOne = menu(place: item["place"].string!, types: item["types"].string!, name: item["name"].string!, price: item["price"].string!)
localmenu.append(menuOne)
}
self.menus = localmenu
}
}
// Delegate method
func loadWithDisTime(distance: String,name: String)
{
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.userDistanceFromLocation = distance
//This will hold 4 values
self.restNames = name
self.tableView.reloadData()
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
self.getDistance = getDistanceTimeVC()
self.getDistance.delegate = self
let restCount = self.RestTable.count
if restCount == 0
{
let alertView = UIAlertController(title: "There are no restaurants near your location", message: "Press Okay to go back", preferredStyle: UIAlertControllerStyle.Alert)
let alertAction = UIAlertAction(title: "Okay", style: UIAlertActionStyle.Cancel, handler: nil)
alertView.addAction(alertAction)
self.presentViewController(alertView, animated: true, completion: nil)
}
else
{
for item in 0...restCount - 1
{
self.restLat = self.RestTable[item].lat
self.restLng = self.RestTable[item].lng
self.getDistance.getDistance(self.userLat, userLng: self.userLng, restLat: self.restLat, restLng: self.restLng,restName: self.RestTable[item].name)
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.RestTable.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! RestTableViewCell
//Not all values coming through here
print(self.restNames)
if self.RestTable[indexPath.row].name == self.restNames
{
cell.textLabel?.text = "\(indexPath.row + 1) - \(self.RestTable[indexPath.row].name) - \(self.userDistanceFromLocation)"
cell.backgroundColor = UIColor.whiteColor()
cell.textLabel?.textColor = UIColor.blackColor()
cell.textLabel?.font = UIFont(name: "Avenir-Heavy", size: 15)
}
return cell
}
In this block leave only reload table view
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.tableView.reloadData()
}

CoreData not saving - TableViewCell returning nill

This is my code:
import UIKit
import CoreData
class ExerciseViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
//sets stepper configs
setsStepper.wraps = false
setsStepper.autorepeat = true
setsStepper.continuous = true
setsStepper.tintColor = UIColor.redColor()
setsStepper.minimumValue = 0
setsStepper.maximumValue = 500
setsStepper.value = 0
//reps stepper configs
repsStepper.wraps = false
repsStepper.autorepeat = true
repsStepper.continuous = true
repsStepper.tintColor = UIColor.orangeColor()
repsStepper.minimumValue = 0
repsStepper.maximumValue = 500
repsStepper.value = 0
exerciseTableView.reloadData()
}
var moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var fetchedResultsController: NSFetchedResultsController?
#IBOutlet var exerciseTableView: UITableView!
#IBOutlet var dayName: UITextField!
#IBOutlet var exerciseName: UITextField!
#IBOutlet var setsStepper: UIStepper!
#IBOutlet var repsStepper: UIStepper!
#IBOutlet var setsNumber: UILabel!
#IBOutlet var repsNumber: UILabel!
var daysArray = [String]()
var namesArray = [String]()
var setsArray = [Int]()
var repsArray = [Int]()
func appendDaysToArray() {
let dayLabel = dayName.text
daysArray.append(dayLabel)
let entityDescription = NSEntityDescription.entityForName("TrainingDay", inManagedObjectContext: moc!)
let trainingday = TrainingDay(entity: entityDescription!, insertIntoManagedObjectContext: moc)
trainingday.day = dayName.text
var error: NSError?
moc?.save(&error)
if let err = error {
var status = err.localizedFailureReason
println("\(status)")
} else {
println("Day #\(dayName.text) saved successfully!")
}
}
func appendNamesToArray () {
let nameLabel = exerciseName.text
namesArray.append(nameLabel)
let entityDescription = NSEntityDescription.entityForName("TrainingDetails", inManagedObjectContext: moc!)
let trainingdetails = TrainingDetails(entity: entityDescription!, insertIntoManagedObjectContext: moc)
trainingdetails.exerciseName = exerciseName.text
var error: NSError?
moc?.save(&error)
if let err = error {
var status = err.localizedFailureReason
println("\(status)")
} else {
println("Exercise: #\(exerciseName.text) saved successfully!")
}
}
func appendNumberToSets () {
let numberOfSets = setsNumber.text?.toInt()
setsArray.append(numberOfSets!)
let entityDescription = NSEntityDescription.entityForName("TrainingDetails", inManagedObjectContext: moc!)
let trainingdetails = TrainingDetails(entity: entityDescription!, insertIntoManagedObjectContext: moc)
trainingdetails.setsNumber = setsNumber.text!
var error: NSError?
moc?.save(&error)
if let err = error {
var status = err.localizedFailureReason
println("\(status)")
} else {
println("Exercise: #\(setsNumber.text) saved successfully!")
}
}
func appendNumberOfReps () {
let numberOfReps = repsNumber.text?.toInt()
repsArray.append(numberOfReps!)
let entityDescription = NSEntityDescription.entityForName("TrainingDetails", inManagedObjectContext: moc!)
let trainingdetails = TrainingDetails(entity: entityDescription!, insertIntoManagedObjectContext: moc)
trainingdetails.repsNumber = repsNumber.text!
var error: NSError?
moc?.save(&error)
if let err = error {
var status = err.localizedFailureReason
println("\(status)")
} else {
println("Exercise: #\(repsNumber.text) saved successfully!")
}
}
#IBAction func doneButton(sender: AnyObject) {
println("\(dayName.text)")
appendDaysToArray()
println("\(exerciseName.text)")
appendNamesToArray()
println("\(setsNumber.text)")
appendNumberToSets()
println("\(repsNumber.text)")
appendNumberOfReps()
exerciseTableView.reloadData()
}
#IBAction func setsStepperAction(sender: UIStepper) {
println("\(Int(sender.value))")
setsNumber.text = Int(sender.value).description
}
#IBAction func repsStepper(sender: UIStepper) {
println("\(Int(sender.value))")
repsNumber.text = Int(sender.value).description
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return namesArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "exerciseCell"
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Value2, reuseIdentifier: cellIdentifier)
}
let row = indexPath.row
let name = namesArray[indexPath.row]
let numberReps = repsArray[indexPath.row]
let numberSets = setsArray[indexPath.row]
cell!.textLabel!.text = name
cell?.detailTextLabel?.text = "Sets: #\(numberSets) Reps: #\(numberReps)"
return cell!
}
}
and
class ViewExercisesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
override func viewDidLoad() {
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchTrainingDetails(), managedObjectContext: moc!, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController?.delegate = self
fetchedResultsController?.performFetch(nil)
self.viewExerciseTableView.reloadData()
sundayButton.frame = CGRectMake(-30,150,125,125)
sundayButton.addTarget(self, action: "sundayButtonTouch:", forControlEvents: UIControlEvents.TouchDown)
sundayButton.setImage(imageSunday, forState: .Normal)
sundayButton.imageEdgeInsets = UIEdgeInsetsMake(30,30,30,30)
self.view.addSubview(sundayButton)
mondayButton.frame = CGRectMake(120,150,125,125)
mondayButton.addTarget(self, action: "mondayButtonTouch:", forControlEvents: UIControlEvents.TouchDown)
mondayButton.setImage(imageMonday, forState: .Normal)
mondayButton.imageEdgeInsets = UIEdgeInsetsMake(30,30,30,30)
self.view.addSubview(mondayButton)
tuesdayButton.frame = CGRectMake(270,150,125,125)
tuesdayButton.addTarget(self, action: "tuesdayButtonTouch:", forControlEvents: UIControlEvents.TouchDown)
tuesdayButton.setImage(imageTuesday, forState: .Normal)
tuesdayButton.imageEdgeInsets = UIEdgeInsetsMake(30,30,30,30)
self.view.addSubview(tuesdayButton)
wednesdayButton.frame = CGRectMake(-30,250,125,125)
wednesdayButton.addTarget(self, action: "wednesdayButtonTouch:", forControlEvents: UIControlEvents.TouchDown)
wednesdayButton.setImage(imageWednesday, forState: .Normal)
wednesdayButton.imageEdgeInsets = UIEdgeInsetsMake(30,30,30,30)
self.view.addSubview(wednesdayButton)
thursdayButton.frame = CGRectMake(70,250,125,125)
thursdayButton.addTarget(self, action: "thursdayButtonTouch:", forControlEvents: UIControlEvents.TouchDown)
thursdayButton.setImage(imageThursday, forState: .Normal)
thursdayButton.imageEdgeInsets = UIEdgeInsetsMake(30,30,30,30)
self.view.addSubview(thursdayButton)
fridayButton.frame = CGRectMake(170,250,125,125)
fridayButton.addTarget(self, action: "fridayButtonTouch:", forControlEvents: UIControlEvents.TouchDown)
fridayButton.setImage(imageFriday, forState: .Normal)
fridayButton.imageEdgeInsets = UIEdgeInsetsMake(30,30,30,30)
self.view.addSubview(fridayButton)
saturdayButton.frame = CGRectMake(270,250,125,125)
saturdayButton.addTarget(self, action: "saturdayButtonTouch:", forControlEvents: UIControlEvents.TouchDown)
saturdayButton.setImage(imageSaturday, forState: .Normal)
saturdayButton.imageEdgeInsets = UIEdgeInsetsMake(30,30,30,30)
self.view.addSubview(saturdayButton)
}
//VAR AND LET
var sundayButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
var imageSunday = UIImage(named: "day.png")
var mondayButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
var imageMonday = UIImage(named: "day.png")
var tuesdayButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
var imageTuesday = UIImage(named: "day.png")
var wednesdayButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
var imageWednesday = UIImage(named: "day.png")
var thursdayButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
var imageThursday = UIImage(named: "day.png")
var fridayButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
var imageFriday = UIImage(named: "day.png")
var saturdayButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
var imageSaturday = UIImage(named: "day.png")
#IBOutlet var viewExerciseTableView: UITableView!
var moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var fetchedResultsController: NSFetchedResultsController?
// FUNCTIONS
func sundayButtonTouch(sender: UIButton!) {
println("future event will be added, button working fine - sunday")
}
func mondayButtonTouch(sender: UIButton!) {
println("future event will be added, button working fine - monday")
}
func tuesdayButtonTouch(sender: UIButton!) {
println("future event will be added, button working fine - tuesday")
}
func wednesdayButtonTouch(sender: UIButton!) {
println("future event will be added, button working fine - wednesday")
}
func thursdayButtonTouch(sender: UIButton!) {
println("future event will be added, button working fine - thursday")
}
func fridayButtonTouch(sender: UIButton!) {
println("future event will be added, button working fine - friday")
}
func saturdayButtonTouch(sender: UIButton!) {
println("future event will be added, button working fine - saturday")
}
// FETCH REQUEST METHODS
func fetchTrainingDay() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "TrainingDay")
// let predicate = NSPredicate(format: "day == %#")
let sortDescriptor = NSSortDescriptor(key: "day", ascending: true)
fetchRequest.predicate = nil
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.fetchBatchSize = 20
return fetchRequest
}
func fetchTrainingDetails() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "TrainingDetails")
fetchRequest.predicate = nil
let sortDescriptor1 = NSSortDescriptor(key: "exerciseName", ascending: true)
let sortDescriptor2 = NSSortDescriptor(key: "repsNumber", ascending: true)
let sortDescriptor3 = NSSortDescriptor(key: "setsNumber", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor1, sortDescriptor2, sortDescriptor3]
fetchRequest.fetchBatchSize = 20
return fetchRequest
}
//TABLE VIEW DELEGATE METHODS
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultsController?.sections?[section].numberOfObjects ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "exCell"
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
if let exCell = fetchedResultsController?.objectAtIndexPath(indexPath) as? TrainingDetails {
println("THE ERROR IS HERE #\(exCell.exerciseName) \(exCell.repsNumber) \(exCell.setsNumber)")
cell!.textLabel?.text = exCell.exerciseName
cell?.detailTextLabel?.text = "Sets: #\(exCell.setsNumber) Reps: #\(exCell.repsNumber)"
}
return cell!
}
// MARK: NSFetchedResultsControllerDelegate
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.viewExerciseTableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController,
didChangeObject anObject: AnyObject,
atIndexPath indexPath: NSIndexPath?,
forChangeType type: NSFetchedResultsChangeType,
newIndexPath: NSIndexPath?)
{
switch(type) {
case .Insert:
if let newIndexPath = newIndexPath {
viewExerciseTableView.insertRowsAtIndexPaths([newIndexPath],
withRowAnimation:UITableViewRowAnimation.Fade)
}
case .Delete:
if let indexPath = indexPath {
viewExerciseTableView.deleteRowsAtIndexPaths([indexPath],
withRowAnimation: UITableViewRowAnimation.Fade)
}
case .Update:
break
case .Move:
if let indexPath = indexPath {
if let newIndexPath = newIndexPath {
viewExerciseTableView.deleteRowsAtIndexPaths([indexPath],
withRowAnimation: UITableViewRowAnimation.Fade)
viewExerciseTableView.insertRowsAtIndexPaths([newIndexPath],
withRowAnimation: UITableViewRowAnimation.Fade)
}
}
}
}
func controller(controller: NSFetchedResultsController,
didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
atIndex sectionIndex: Int,
forChangeType type: NSFetchedResultsChangeType)
{
switch(type) {
case .Insert:
viewExerciseTableView.insertSections(NSIndexSet(index: sectionIndex),
withRowAnimation: UITableViewRowAnimation.Fade)
case .Delete:
viewExerciseTableView.deleteSections(NSIndexSet(index: sectionIndex),
withRowAnimation: UITableViewRowAnimation.Fade)
default:
break
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
viewExerciseTableView.endUpdates()
}
}
and
import Foundation
import CoreData
class TrainingDetails: NSManagedObject {
#NSManaged var exerciseName: String
#NSManaged var setsNumber: String
#NSManaged var repsNumber: String
#NSManaged var relationship2: NSManagedObject
}
The logic is this: In "ExerciseViewController", I put some stuff in CoreData, this view works fine. Now, in ViewExercisesViewController, I should retrieve the exerciseName and set it as the cell's textLabel, but it crashes right here: println("THE ERROR IS HERE #\(exCell.exerciseName) \(exCell.repsNumber) \(exCell.setsNumber)"). It seems that I'm not retrieving it correctly, but I'm certain it is being saved in CoreData.
Anyone has any idea? I have an app that does something similar, but this one is not working.
Thanks for your help, I'm looking forward to figure out what's wrong here.
EDIT -> image with crush error:
EDIT 2
Doing this:
leads to this:
FINAL EDIT -> SOLUTION
As Tom said, doing this:
func appendTrainingDetails () {
let nameLabel = exerciseName.text
namesArray.append(nameLabel)
let numberOfSets = setsNumber.text?.toInt()
setsArray.append(numberOfSets!)
let numberOfReps = repsNumber.text?.toInt()
repsArray.append(numberOfReps!)
let entityDescription = NSEntityDescription.entityForName("TrainingDetails", inManagedObjectContext: moc!)
let trainingdetails = TrainingDetails(entity: entityDescription!, insertIntoManagedObjectContext: moc)
trainingdetails.exerciseName = exerciseName.text
trainingdetails.setsNumber = setsNumber.text!
trainingdetails.repsNumber = repsNumber.text!
var error: NSError?
moc?.save(&error)
if let err = error {
var status = err.localizedFailureReason
println("\(status)")
} else {
println("Exercise: #\(exerciseName.text) saved successfully!")
println("Number of sets: #\(setsNumber.text) saved successfully!")
println("Number of reps: #\(repsNumber.text) saved successfully!")
}
}
with the methods, led me to fix what I was doing wrong! Thank you very very much!!
Here's what it looks like is happening...
Your methods appendNumberToSets(), appendNumberOfReps(), and appendNamesToArray() all have this code:
let entityDescription = NSEntityDescription.entityForName("TrainingDetails", inManagedObjectContext: moc!)
let trainingdetails = TrainingDetails(entity: entityDescription!, insertIntoManagedObjectContext: moc)
That means that each one of these is creating a new instance of TrainingDetails. However,
appendNumberToSets() only sets a value for the setsNumber property
appendNamesToArray() only sets a value for the exerciseName property
appendNumberOfReps() only sets a value for the repsNumber property
In all three methods you're creating new TrainingDetails instances but leaving most of the properties with nil values. That would be OK except that later on you attempt to look up exerciseName, setsNumber, and repsNumber on the same instance. Since none of your instances have values for all three, your app crashes.
This is happening because Core Data has an idea of what an optional value is, and Swift also has an idea of what "optional" means, but these are not the same thing. Core Data doesn't care that you're leaving these properties with nil values, but Swift does. The conflict between these two ideas is leading to your problem.
What you should do:
If these values should be optional, that is, they're allowed to be nil, you should change your TrainingDetails class to make them Swift optionals. It looks like you're using code that Xcode generated, but that code is wrong and it's completely OK to fix it. The compiler will then force you to check whether the attributes have values, and you won't crash.
If these values should not be optional, that is, any TrainingDetails must have values for every one of these attributes, you should do two things. First, edit your Core Data model. For each of these attributes, un-check the "Optional" box in the model editor. That way Core Data will know that nil isn't allowed and it will prevent you from saving changes if you have unexpected nils. Second, you need to change the three methods mentioned above to either (a) always assign values to all of the attributes, or (b) re-use the same TrainingDetails instance instead of creating a new one in each method (the choice depends on your requirements, so it's up to you to decide which is correct).
In your function appendNumberOfReps(), it seems that you are using dot syntax to add it to the TrainingDay object (not sure where you defined it, though I assume it is an NSManagedObject?). In your Core Data entity TrainingDay, create an attribute named repsNumber and I assume we can set it as a String and try this:
// Save To Core Data
let entityDescription = NSEntityDescription.entityForName("TrainingDay", inManagedObjectContext: moc!)
let trainingday = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: moc)
var error: NSError?
moc?.save(&error)
trainingday.setValue(repsNumber.text!, forKey: "repsNumber")
if !moc.save(&error) {
println("Could not save \(error)")
} else {
println("Exercise: #\(repsNumber.text) saved successfully!")
}
Then in order for your tables to be set correctly, it would be easiest for them to set by reading the count and data off of an array. So maybe you can call Core Data to return the data and then append them to an array named var repsArray: String = []:
// Fetch From Core Data
let trainingFetchRequest = NSFetchRequest(entityName:"TrainingDay")
let fetchedResults = moc.executeFetchRequest(trainingFetchRequest, error: &error) as? [NSManagedObject]
if let results = fetchedResults {
if results.count > 0 {
for index in 0...results.count-1 {
let match = results[index] as NSManagedObject
repsArray.append(match.valueForKey("repsNumber") as! String)
}
}
}
Then it might be easier to count how many rows there should be in the table by using the repsArray (just for example) to return a row count:
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return repsArray.count
}
And then as long as you have the right table cell class (for example, CustomCell below) and label names created in Interface Builder (for example, repNumberLabel below) and then attached to CustomCell, you can just use the indexPath.Row to work off of the arrays you built:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell") as? CustomCell
if !(cell != nil) {
cell = CustomCell(style: .Subtitle, reuseIdentifier: "cell")
}
cell?.repNumberLabel.text = self.repsArray[indexPath.row]
}
Hope this helps and wasn't redundant to what you already tried! This is what has worked for me when trying to save to Core Data and then retrieve to data for use in a table cell.