Expand cell when tapped in Swift

后端 未结 5 1090
南方客
南方客 2020-12-13 01:14

I have been trying to implement a feature in my app so that when a user taps a cell in my table view, the cell expands downwards to reveal notes. I have found plenty of exam

相关标签:
5条回答
  • 2020-12-13 01:30

    I suggest solving this with modyfing height layout constraint

    class ExpandableCell: UITableViewCell {
    
    @IBOutlet weak var img: UIImageView!
    
    
    @IBOutlet weak var imgHeightConstraint: NSLayoutConstraint!
    
    
    var isExpanded:Bool = false
        {
        didSet
        {
            if !isExpanded {
                self.imgHeightConstraint.constant = 0.0
    
            } else {
                self.imgHeightConstraint.constant = 128.0
            }
        }
    }
    
    }
    

    Then, inside ViewController:

    class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var tableView: UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.delegate = self
        self.tableView.dataSource = self
        self.tableView.estimatedRowHeight = 2.0
        self.tableView.rowHeight = UITableViewAutomaticDimension
        self.tableView.tableFooterView = UIView()
    }
    
    
    // TableView DataSource methods
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell:ExpandableCell = tableView.dequeueReusableCell(withIdentifier: "ExpandableCell") as! ExpandableCell
        cell.img.image = UIImage(named: indexPath.row.description)
        cell.isExpanded = false
        return cell
    }
    
    // TableView Delegate methods
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
        guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
            else { return }
    
            UIView.animate(withDuration: 0.3, animations: {
                tableView.beginUpdates()
                cell.isExpanded = !cell.isExpanded
                tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.top, animated: true)
                tableView.endUpdates()
    
            })
    
        }
    
    
    
    
    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
            else { return }
        UIView.animate(withDuration: 0.3, animations: {
            tableView.beginUpdates()
            cell.isExpanded = false
            tableView.endUpdates()
        })
    }
    }
    

    Full tutorial available here

    0 讨论(0)
  • 2020-12-13 01:30

    After getting the index path in didSelectRowAtIndexPath just reload the cell with following method reloadCellsAtIndexpath and in heightForRowAtIndexPathMethod check following condition

    if selectedIndexPath != nil && selectedIndexPath == indexPath {    
           return yourExpandedCellHieght
    }
    
    0 讨论(0)
  • 2020-12-13 01:37

    The first comparison in your if statement can never be true because you're comparing an indexPath to an integer. You should also initialize the selectedRowIndex variable with a row value that can't be in the table, like -1, so nothing will be expanded when the table first loads.

    var selectedRowIndex: NSIndexPath = NSIndexPath(forRow: -1, inSection: 0)
    
    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        if indexPath.row == selectedRowIndex.row {
            return 100
        }
        return 70
    }
    

    Swift 4.2 var selectedRowIndex: NSIndexPath = NSIndexPath(row: -1, section: 0)

    0 讨论(0)
  • 2020-12-13 01:41

    It took me quite a lot of hours to get this to work. Below is how I solved it.

    PS: the problem with @rdelmar's code is that he assumes you only have one section in your table, so he's only comparing the indexPath.row. If you have more than one section (or if you want to already account for expanding the code later) you should compare the whole index, like so:

    1) You need a variable to tell which row is selected. I see you already did that, but you'll need to return the variable to a consistent "nothing selected" state (for when the user closes all cells). I believe the best way to do this is via an optional:

    var selectedIndexPath: NSIndexPath? = nil
    

    2) You need to identify when the user selects a cell. didSelectRowAtIndexPath is the obvious choice. You need to account for three possible outcomes:

    1. the user is tapping on a cell and another cell is expanded
    2. the user is tapping on a cell and no cell is expanded
    3. the user is tapping on a cell that is already expanded

    For each case we check if the selectedIndexPath is equal to nil (no cell expanded), equal to the indexPath of the tapped row (same cell already expanded) or different from the indexPath (another cell is expanded). We adjust the selectedIndexPath accordingly. This variable will be used to check the right rowHeight for each row. You mentioned in comments that didSelectRowAtIndexPath "didn't seem to be called". Are you using a println() and checking the console to see if it was called? I included one in the code below.

    PS: this doesn't work using tableView.rowHeight because, apparently, rowHeight is checked only once by Swift before updating ALL rows in the tableView.

    Last but not least, I use reloadRowsAtIndexPath to reload only the needed rows. But, also, because I know it will redraw the table, relayout when necessary and even animate the changes. Note the [indexPath] is between brackets because this method asks for an Array of NSIndexPath:

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
            println("didSelectRowAtIndexPath was called")
            var cell = tableView.cellForRowAtIndexPath(indexPath) as! MyCustomTableViewCell
            switch selectedIndexPath {
            case nil:
                selectedIndexPath = indexPath
            default:
                if selectedIndexPath! == indexPath {
                    selectedIndexPath = nil
                } else {
                    selectedIndexPath = indexPath
                }
            }
            tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
    }
    

    3) Third and final step, Swift needs to know when to pass each value to the cell height. We do a similar check here, with if/else. I know you can made the code much shorter, but I'm typing everything out so other people can understand it easily, too:

    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
            let smallHeight: CGFloat = 70.0
            let expandedHeight: CGFloat = 100.0
            let ip = indexPath
            if selectedIndexPath != nil {
                if ip == selectedIndexPath! {
                    return expandedHeight
                } else {
                    return smallHeight
                }
            } else {
                return smallHeight
            }
        }
    

    Now, some notes on your code which might be the cause of your problems, if the above doesn't solve it:

    var cell:CustomTransactionTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell
    

    I don't know if that's the problem, but self shouldn't be necessary, since you're probably putting this code in your (Custom)TableViewController. Also, instead of specifying your variable type, you can trust Swift's inference if you correctly force-cast the cell from the dequeue. That force casting is the as! in the code below:

    var cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier" forIndexPath: indexPath) as! CustomTransactionTableViewCell
    

    However, you ABSOLUTELY need to set that identifier. Go to your storyboard, select the tableView that has the cell you need, for the subclass of TableViewCell you need (probably CustomTransactionTableViewCell, in your case). Now select the cell in the TableView (check that you selected the right element. It's best to open the document outline via Editor > Show Document Outline). With the cell selected, go to the Attributes Inspector on the right and type in the Identifier name.

    You can also try commenting out the cell.selectionStyle = UITableViewCellSelectionStyle.None to check if that's blocking the selection in any way (this way the cells will change color when tapped if they become selected).

    Good Luck, mate.

    0 讨论(0)
  • 2020-12-13 01:51

    A different approach would be to push a new view controller within the navigation stack and use the transition for the expanding effect. The benefits would be SoC (separation of concerns). Example Swift 2.0 projects for both patterns.

    • https://github.com/justinmfischer/SwiftyExpandingCells
    • https://github.com/justinmfischer/SwiftyAccordionCells
    0 讨论(0)
提交回复
热议问题