Gold Challenge - tough one

(Note: I didn’t want to look at the other thread on this Challenge yet…)

I poured through the UITableViewDataSource documentation and the first I’ve discovered is that an .editingStyle for ‘Update’ doesn’t exist.

Because I didn’t see a way to add a button or caption to ‘Add as Favorites’ in the app’s Edit mode, my approach is to have the user Tap a cell to add it to favorites. The ideal result is a highlighted row with the additional ‘Favorites’ caption tacked on after the Item name.

Here’s what I’ve tried in ItemsViewController, which hasn’t yielded the desired results:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    // Create an instance of UITableViewCell with default appearance
    // let cell = UITableViewCell(style: .value1, reuseIdentifier: "UITableViewCell")
    
    
    
    // better yet, get a new or recycled cell
    let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
    
    // Set the text on the cell with the description of the item
    // that is at the nth index of items, where n = row this cell
    // will appear in on the table view
    
    // After the last reply, I first tried splitting my first if statement into two, checking whether each itemList is empty. If so, I set the cell text. But the text ONLY displayed after I clicked Add it added an item to the opposite section.
    
  
        
    if indexPath.section == 0 {
        cell.textLabel?.text = itemStore.over50Items[indexPath.row].name
        cell.detailTextLabel?.text = "$\(itemStore.over50Items[indexPath.row].valueInDollars)"
        
        if cell.isHighlighted {
            cell.textLabel?.text = "\(cell.textLabel?.text) is a Favorite!" // new for Gold Challenge
            itemStore.over50Items[indexPath.row].isFavorite = true
        }
        
        return cell
    } else {
        cell.textLabel?.text = itemStore.under50Items[indexPath.row].name
        cell.detailTextLabel?.text = "$\(itemStore.under50Items[indexPath.row].valueInDollars)"
        
        if cell.isHighlighted {
            cell.textLabel?.text = "\(cell.textLabel?.text) is a Favorite!" // new for Gold Challenge
            itemStore.under50Items[indexPath.row].isFavorite = true
        }
        
        return cell
    }

    // Gold Challenge: implement editing within a row?
override func tableView(_ tableView: UITableView,
                        willBeginEditingRowAt indexPath: IndexPath) {
    let addToFavorites = UIButton.init()
    addToFavorites.titleLabel?.text = "Add to Favorites"
    
}

// Gold Challenge: set up another action after a tapping a cell?
@IBAction func addToFavorites (_ sender: UITableViewCell) {
    isEditing = true
    
    sender.setHighlighted(true, animated: true)
    
    
}

I also discovered that I can’t use an IBAction to connect the Cell to this function within the Storyboard. (addToFavorites doesn’t appear in the selection menu after I control-drag between Items View Controller and the TableViewCell).

A few questions to start:

  1. Why doesn’t ‘Update’ exist as an .editingStyle? There’s only none, delete, and insert.
  2. Can’t you associate/connect an IBAction to a UITableViewCell?
  3. I once tried adding a button control to the cell in Storyboard, but it will only stay center-aligned. Did anyone else trying this approach notice this?

In Chapter 10, they have you create a custom table view cell; that might be what you would need to do to have more control over the placement of the button control within the cell. But I’m not sure how that would work out in practice. In order to implement a useful button, you would need to know which row the button was in when it was pressed and that isn’t part of the UIButton interface.

As for the other questions, I went with the recommended approach & implemented swipe right to toggle favorite on & off for an item:

Click to see code
    override func tableView(_ tableView: UITableView,
                            leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

        let favorite = getSectionItems(for: indexPath.section)[indexPath.row].favorite
        let title = favorite ?
            NSLocalizedString("Unfavorite", comment: "Unfavorite") :
            NSLocalizedString("Favorite", comment: "Favorite")

        let action = UIContextualAction(style: .normal,
                                        title: title,
                                        handler: { (action, view, completionHandler) in
                                            // Update data source when user taps action
                                            self.getSectionItems(for: indexPath.section)[indexPath.row].setFavorite(!favorite)
                                            completionHandler(true)
                                        }
        )

        action.backgroundColor = favorite ? .red : .green
        let configuration = UISwipeActionsConfiguration(actions: [action])
        return configuration
    }
  1. How did you even get here? I never came across this instance method.

  2. Is getSectionItems a custom function of yours?

  1. It’s part of UITableViewDelegate which I think I had looked at for something else. I probably did a google search on swipe right also, that code for creating the UIContextualAction doesn’t look like something I would have figured out entirely on my own just from the Apple documentation.

  2. Yes, getSectionItems is a custom function I wrote. It handles the section split as well as the filter for displaying only favorites.

I’m still struggling to implement this.

For one (and please don’t give me your code), I don’t have a proper implementation for getSectionItems:

func getSectionItems(for section: Int) -> [Item] {
    if section == 0 {
        return itemStore.over50Items
    } else {
        return itemStore.under50Items
    }
    // I figure there's more to it than this
}

More broadly, why do the authors expect readers to take these huge leaps (solving Gold Challenges) when (most) readers can barely walk?

What am I missing? I read each chapter, I try to understand Apple Documentation as best I can. Yet I still feel wholly ill-equipped. And therefore stuck. Do I need to stop this book and pour through Apple’s Swift book, or return to more “remedial” how-to-program materials?

(I commented on my frustrations a bit more here: [How to read Apple Developer Documentation])

Reading a book on Swift would definitely help. Apple’s book is free but seems to me to be very much like a reference book & not necessarily the best thing to learn from. The BNR Swift Programming book is a few years old but I’ve referred back to it a few times while going through this book & it’s been helpful. Swift concepts that are mentioned in passing here (or not mentioned at all) are gone over in more detail there: filters, closures, when to use classes vs structs, etc.

As for the Gold challenges being difficult & requiring the exploration of new concepts, I think that’s the intent. This book barely scratches the surface of what’s possible with iOS; when you get to the end of it you need to be able to go on & figure out the rest. The gold challenges are pushing you to start doing that.

As for getSectionItems, there is more to it, but not much more (and only if you’re doing the extra challenge to add a filter button). My implementation of that function is a single line of code, but it was a complex line I was having to write over & over so it was a good candidate to move to a function - doing so made the rest of the code more readable, and putting the complexity in one place reduced the chances for error & made the code more maintainable.

1 Like

Will I need getSectionItems if I skip the bonus challenge?

I’m just running into errors because of the ‘for’ part of the arguments.

I’m sorry, but I’m just completely frustrated. I’ve taken a few programming classes in the past for different languages so I (want to think that I) understand the basics. While I appreciate the author’s intent for these Gold challenges, I’m in need of further guidance to even start a solution.

For example, how did you find
‘tableView(_ tableView:,leadingSwipeActionsConfigurationForRowAt) -> UISwipeActionsConfiguration?’ ? What was your exact search term on the Documentation web site?

I didn’t see anything related to Swiping within their Documentation that would lead me to the function above.

Based on the requirements, I will start from scratch and try to complete it by the end of the week. If I don’t, I will pause and purchase their Swift book.

I’m just trying to not quit again.

I found that solution on line. I find that posing some of these questions in a search engine leads to an expanded selection of tutorials and examples. I didn’t have a clue about that function and couldn’t get to it through the Xcode window. One thing I could say positively about this process, is that once you go through the pain of researching a solution it is harder to forget. I find that my retention of the material is directly proportional to the pain I have gone through to get it.

You don’t need it at all. I only wrote it to make the rest of my code more succinct & readable. If you’d rather type

if section == 0 {
    do something to itemStore.over50Items
} else {
    do the same something to itemStore.under50Items
}

over & over, go ahead. But anytime I find myself typing the same thing repeatedly, I start thinking about wrapping it in a function instead.

It was almost a month ago, so I don’t recall now exactly how I got to that. I will say that I don’t do any searching directly on the Apple web site, I do all my searches on Google.

If I do a Google search on UITableViewDelegate, Apple’s web site for that protocol is the first search result. I might have looked at that & then scanned it for something mentioning “swipe”.

Or I might have just typed something like “swift table view swipe right” into Google & started reading.

As for the error messages: The first one is because you forgot the “:” after for in your getSectionItems call. The second one I think is because the argument list for the closure has an extra x,’ on the end of the third argument. It should be (action, view, completionHandler).

I’d like to wrap that in a single function. I just don’t know how to do that yet.

Meantime, I found something online for swiping on a cell and tried to implement it.

However, unless I’m not swiping on my simulator correctly (hold down the mouse button and slowly scroll on a cell from left-to-right), I don’t see a swiping, and the logs that are supposed to print do not appear in the console.

override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    print("leadingSwipeActionsConfigurationForRow called, is Editing \(tableView.isEditing)")
    
        let favorite = UIContextualAction(style: .normal, title: "Favorite!") { (action, view, completion ) in
        print("FavoriteCalled called, table is Editing \(tableView.isEditing)")
        tableView.isEditing = false
        
    }
    
    let config = UISwipeActionsConfiguration(actions: [favorite])
    return config
}

I pasted your function in place of mine, and when I swipe right & tap the button, I see

leadingSwipeActionsConfigurationForRow called, is Editing false
FavoriteCalled called, table is Editing true

in the log (first line on the swipe, second line on the tap). I scanned the rest of my code, and I don’t see anything else I did to enable swipe right, but I’ll take another look.

As for whether you’re swiping correctly, swiping right should be the same as swiping left except for the direction. When you swipe right, you should see the “Favorite” button appear; when you swipe left, you see the “Delete” button appear. Don’t swipe too far right, though, or the “Favorite” button will disappear & nothing will show up in the log.

It looked to me like what you had in your previous post was correct if you don’t want to do the favorite filtering.

Still not swiping on my side.

Did I need to implement anything else, whether it’s in code or on the Storyboard?

I can’t find any other settings that would disable swiping without also breaking other things like the Add & Edit buttons. Sorry.

Just to be sure, you didn’t need to add a Swipe Gesture Recognizer to the Storyboard?

Nope, no Swipe Gesture Recognizer.

Okay, thanks.

I find myself at a standstill.

The book suggest investigating UITableViewDataSource, but it’s UITableViewDelegate that you want. The following is the method needed, along with a simple implementation:

// Implement swipe right (leading)
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
let action = UIContextualAction(style: .normal, title: “Favourite”) {
(ac: UIContextualAction, view: UIView, actionPerformed:(Bool) -> Void) in
let item = self.itemStore.allItems[indexPath.row]
item.isFavourite.toggle()
self.tableView.reloadRows(at: [indexPath], with: .automatic)
actionPerformed(true)
}
action.backgroundColor = .systemBlue
return UISwipeActionsConfiguration(actions: [action])
}