Search code examples
iosswiftuitableviewcgaffinetransform

Scale tableView cell with CGAffineTransform to cover entire screen


I have a tableView where when a user taps on that cell, I want the cell to expand and fill the entire screen with that color. Currently, it does scale to fill the entire screen but the cells above it will also show. The colors come from an API and each cell changes color when the user taps the "Generate" button. Due to how the API's JSON file is structured, I only want 5 colors on the screen at a time.

I can't figure out why the cells above it don't move upwards or how I can move the clicked cell to the top of the screen. I've tried offsetting the coordinates of the tapped cell and changing its frame size to be the height of the tableView tappedCell.bounds.size = CGSize(width: UIScreen.main.bounds.width, height: self.paletteTableView.bounds.height) but when I click the 1st or 2nd cells from the top, the screen doesn't fill all the way to the bottom.

I've also tried animating from a UIView (called colorDetailsView here) but wasn't able to successfully get it to animate from the center of each cell that's tapped on, only from the center of the screen.

The animation happens here:

@objc func userTap(sender: UITapGestureRecognizer) {
        if sender.state == UIGestureRecognizer.State.ended {
            let tapLocation = sender.location(in: self.paletteTableView)
            if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) {
                if let tappedCell = self.tableView.cellForRow(at: tapIndexPath) {

                    UIView.animate(withDuration: 0.5, animations: {

                        switch self.currentAnimation {
                        case 0:
                            tappedCell.transform = CGAffineTransform(scaleX: 1, y: 50)
                            //...
                        case 1:
                            tappedCell.transform = .identity
                        default: break
                        }
                    } )

                    currentAnimation += 1
                    if currentAnimation > 1 {
                        currentAnimation = 0
    } } } } }

Full code:

class PaletteController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    let tableView = UITableView()
    var colorPalette = [Color]()
    var currentAnimation = 0

    let colorDetailsView: UIView = {
        let view = UIView()
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
        tableView.isScrollEnabled = false
        \\...
        view.addSubview(colorDetailsView)
    }

    //Color cell expands to fill the screen of that color when user taps on the cell
    @objc func userTap(sender: UITapGestureRecognizer) {
        if sender.state == UIGestureRecognizer.State.ended {
            let tapLocation = sender.location(in: self.tableView)

            if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) {
                if let tappedCell = self.tableView.cellForRow(at: tapIndexPath) {
                    UIView.animate(withDuration: 0.5, animations: {

                        //When user taps on a cell, it is scaled to fill the screen with that color
                        switch self.currentAnimation {
                        case 0:
                            tappedCell.transform = CGAffineTransform(scaleX: 1, y: 50)

                            for i in 0..<self.colorPalette.count {
                                    if tapIndexPath.row == i {
                                        self.colorDetailsView.backgroundColor = UIColor(hexString: self.colorPalette[i])
                                    }
                                }
                        case 1:
                            tappedCell.transform = .identity
                        default: break
                        }
                    } )

                    currentAnimation += 1
                    if currentAnimation > 1 {
                        currentAnimation = 0
                    }
                }
            }
        }
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier:"ColorCell", for: indexPath)

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(userTap(sender:)))
        cell.addGestureRecognizer(tapGesture)
        cell.isUserInteractionEnabled = true

        //Assigns a new color to each row. Colors are converted from HEX to RGB
        for i in 0..<colorPalette.count {
            if tapIndexPath.row == i {
                cell.backgroundColor = UIColor(hexString: colorPalette[i])
            }
        }
        return cell
    }
}

I know the cell is scaling when I set tableView.isScrollEnabled to true, as shown below. Each cell should end up filling the whole screen and not get covered by any other cells, like the top-most cell. Ideally, I'd like the tableView to not scroll, but I'm not sure if the top cells will "move up" if scroll is disable.

Any help is appreciated!

enter image description here


Solution

  • The cells are after all sub-views of the table view. You need to bring the tapped cell at the front of the sub-view list.

    I would suggest adding

    tableView.bringSubviewToFront(tappedCell)

    just after the line

    if let tappedCell = self.tableView.cellForRow(at: tapIndexPath) {.

    See if this works!