Bronze Challenge - Generally speaking, what's the best approach to tackle Challenges throughout the book?

Because this thread helped me, I thought I’d add my own contribution.

My opinion and experience with this is that it’s a lot easier with a multidimensional array. I think there’s less redundancy in the code vs. using two arrays, and the changes needed to make it work in ItemStore and ItemsViewController are pretty minor. Mostly I needed to add the section number to the allItems array calls (that is, allItems[sectionNumber] etc. depending on what I was doing).

Here’s what I did.

Item.swift -> added var valueGT50 = false. In init, if valueInDollars > 50, self.valueGT50 =. true. I also added this comparison to the function for Equatable.

ItemStore.swift -> changed allItems to (those are empty square brackets. Creating empty elements in the array was necessary for append and other functions in ItemsViewController to work). createItem was also modified.

class ItemStore
{
var allItems : [[Item]] = [,]

@discardableResult func createItem() -> Item
{
    let newItem = Item(random: true)
    var sectionNum = 0
    
    if newItem.valueGT50
    {
        sectionNum = 1
    }
    
    allItems[sectionNum].append(newItem)
    
    return newItem
}

func removeItem(_ item: Item)
{
    var sectionNum = 0
    
    if item.valueGT50
    {
        sectionNum = 1
    }

    if let index = allItems[sectionNum].firstIndex(of: item)
    {
        allItems[sectionNum].remove(at: index)
    }
}

func moveItem(from fromIndex: IndexPath, to toIndex: IndexPath)
{
    if fromIndex == toIndex
    {
        return
    }
    
    // get reference to object being moved so it can be reinserted
    let movedItem = allItems[fromIndex.section][fromIndex.row]
    
    // remove item from array
    allItems[fromIndex.section].remove(at: fromIndex.row)
    
    // insert item in array at new location
    allItems[fromIndex.section].insert(movedItem, at: toIndex.row)
}

}

ItemsViewController.swift looks like this. There are a couple of added functions which are also described in the thread above and some of the existing functions were modified to include the section number in the array call.

class ItemsViewController: UITableViewController { var itemStore: ItemStore!
@IBAction func addNewItem(_ sender: UIButton)
{
    // create a new item and add it to the store
    let newItem = itemStore.createItem()
    var sectionNum = 0
    
    if newItem.valueGT50
    {
        sectionNum = 1
    }

    // figure out where that item is in the array
    if let index = itemStore.allItems[sectionNum].firstIndex(of: newItem)
    {
        let indexPath = IndexPath(row: index, section: sectionNum)
        
        // Insert this new row into the table
        tableView.insertRows(at: [indexPath], with: .automatic)
    }
}

@IBAction func toggleEditingMode(_ sender: UIButton)
{
    // if you are currently in editing mode
    if isEditing
    {
        // change text of button to inform user of state
        sender.setTitle("Edit", for: .normal)
        
        // turn off editing mode
        setEditing(false, animated: true)
    }
    else
    {
        // change text of button to inform user of state
        sender.setTitle("Done", for: .normal)
        
        // enter editing mode
        setEditing(true, animated: true)
    }
}

override func numberOfSections(in tableView: UITableView) -> Int {
   return itemStore.allItems.count
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return itemStore.allItems[section].count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // get a new or reused 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 the table view
    let item = itemStore.allItems[indexPath.section][indexPath.row]
    
    cell.textLabel?.text = item.name
    cell.detailTextLabel?.text = "$\(item.valueInDollars)"
    
    return cell
}

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    // if the table view is asking to commit a delete command…
    if editingStyle == .delete
    {
        let item = itemStore.allItems[indexPath.section][indexPath.row]
        
        // remove the item
        itemStore.removeItem(item)
        
        // also remove that row from the table view with an animation
        tableView.deleteRows(at: [indexPath], with: .automatic)
    }
}

override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    // update the model
    itemStore.moveItem(from: sourceIndexPath, to: destinationIndexPath) // remove .row references to send entire indexPath for each item
}

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section == 0
    {
        return "$50 or less"
    }
    else
    {
        return "Over $50"
    }
}


override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
    
    // I need to identify section
    let sourceSection = sourceIndexPath.section
    let destinationSection = proposedDestinationIndexPath.section
    
    // only allow the move if sections are the same, otherwise, slide them within the same section
    if destinationSection < sourceSection {
        return IndexPath(row: 0, section: sourceSection)
    } else if destinationSection > sourceSection {
        return IndexPath(row: self.tableView(tableView, numberOfRowsInSection: sourceSection)-1, section: sourceSection)
    }
    
    return proposedDestinationIndexPath
    
}

}

I probably don’t need valueGT50 as part of the Item, but it was left over from my first pass at the challenge, where I was trying to do this with a one dimensional array. I gave up on that path when I realized that indexPath wasn’t giving me enough information to find the element I needed. In fact, my first working pass at two sections would correctly put the first elements in a section, but as soon as the other section had elements going to it, it was showing the elements from the first section. Fun bug! :smile:

Again, thanks for the help. I hope someone else might find this useful.

1 Like

BTW, I’m not sure what I’m doing wrong with the pasted code in my previous post. It’s surrounded by < code > </ code > tags yet not all of the code in between the tags is scrolling together.

Paste your code between a pair of three back-tick (```) characters like this:
```
Code…
```

For Example:

auto bar () -> long;

auto foo () -> long {
   return bar ()
}
1 Like

ItemsViewController

import UIKit

class ItemsViewController: UITableViewController {
    
    var itemStore: ItemStore!
    let sectionsArray = ["Above $50", "Below $50"]
    
    @IBAction func addNewItem(_ sender: UIButton) {
        let newItem = itemStore.createItem()
        
        if newItem.valueInDollars > 50 {
        if let index = itemStore.aboveFifty.firstIndex(of: newItem) {
            let indexPath = IndexPath(row: index, section: 0)
            
            tableView.insertRows(at: [indexPath], with: .automatic)
        }
        } else {
            if let index = itemStore.belowFifty.firstIndex(of: newItem) {
                let indexPath = IndexPath(row: index, section: 1)
                
                tableView.insertRows(at: [indexPath], with: .automatic)
            }
        }
    }
    
    @IBAction func toggleEditingMode(_ sender: UIButton) {
        if isEditing {
            sender.setTitle("Edit", for: .normal)
            
            setEditing(false, animated: true)
        } else {
            sender.setTitle("Done", for: .normal)
            
            setEditing(true, animated: true)
        }
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return sectionsArray.count
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
            return itemStore.aboveFifty.count
        } else {
            return itemStore.belowFifty.count
        }
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if section == 0 {
            return sectionsArray[0]
        } else {
            return sectionsArray[1]
        }
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//        let cell = UITableViewCell(style: .value1, reuseIdentifier: "UITableViewCell")
        let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
        
        if indexPath.section == 0 {
            let item = itemStore.aboveFifty[indexPath.row]
            cell.textLabel?.text = item.name
            cell.detailTextLabel?.text = "$\(item.valueInDollars)"
        } else {
            let item = itemStore.belowFifty[indexPath.row]
            cell.textLabel?.text = item.name
            cell.detailTextLabel?.text = "$\(item.valueInDollars)"
        }
        return cell
    }
    
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            
            if indexPath.section == 0 {
            let item = itemStore.aboveFifty[indexPath.row]
            itemStore.removeItem(item)
            tableView.deleteRows(at: [indexPath], with: .automatic)
            } else {
                let item = itemStore.belowFifty[indexPath.row]
                itemStore.removeItem(item)
                tableView.deleteRows(at: [indexPath], with: .automatic)
            }
        }
    }

    override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        itemStore.moveItem(from: sourceIndexPath.row, to: destinationIndexPath.row)
    }
}

Item

import UIKit

class Item: Equatable {
    
    static func ==(lhs: Item, rhs: Item) -> Bool {
        return lhs.name == rhs.name
        && lhs.serialNumber == rhs.serialNumber
        && lhs.valueInDollars == rhs.valueInDollars
        && lhs.dateCreated == rhs.dateCreated
    }
    var name: String
    var valueInDollars: Int
    var serialNumber: String?
    let dateCreated: Date
    
    init(name: String, serialNumber: String?, valueInDollars: Int) {
        self.name = name
        self.serialNumber = serialNumber
        self.dateCreated = Date()
        self.valueInDollars = valueInDollars
    }
    
    convenience init(random: Bool = false) {
        if random {
            let adjectives = ["Fluffy", "Rusty", "Shiny"]
            let nouns = ["Bear", "Spork", "Mac"]
            
            let randomAdjective = adjectives.randomElement()!
            let randomNoun = nouns.randomElement()!
            
            let randomName = "\(randomAdjective) \(randomNoun)"
            let randomValue = Int.random(in: 0..<100)
            let randomSerialNumber = UUID().uuidString.components(separatedBy: "-").first!
            
            self.init(name: randomName, serialNumber: randomSerialNumber, valueInDollars: randomValue)
        } else {
            self.init(name: "", serialNumber: nil, valueInDollars: 0)
        }
    }
}

ItemStore

import UIKit

class ItemStore {
    
//    var allItems = [Item]()
    var belowFifty = [Item]()
    var aboveFifty = [Item]()
    
    @discardableResult func createItem() -> Item {
        let newItem = Item(random: true)
        if newItem.valueInDollars > 50 {
            aboveFifty.append(newItem)
        } else {
            belowFifty.append(newItem)
        }
        return newItem
    }
    
    func removeItem(_ blank: Item) {
        if blank.valueInDollars > 50 {
        if let index = aboveFifty.firstIndex(of: blank) {
            aboveFifty.remove(at: index)
            }
        } else {
            if let index = belowFifty.firstIndex(of: blank) {
                belowFifty.remove(at: index)
                }
        }
    }

    func moveItem(from fromIndex: Int, to toIndex: Int) {
        if fromIndex == toIndex {
            return
        }

        let sectionZero = aboveFifty[fromIndex]
        aboveFifty.remove(at: fromIndex)
        aboveFifty.insert(sectionZero, at: toIndex)
        
        let sectionOne = belowFifty[fromIndex]
        belowFifty.remove(at: fromIndex)
        belowFifty.insert(sectionOne, at: toIndex)
    }
}

I’m extremely new to coding and had a very hard time even reading through the chapters. The above is what I came up with, mostly cutting/pasting already used code… I was only able to modify the already written code, I couldn’t think of any better solutions by myself, I feel terrible because of that… Is this normal? Should I try harder?