iOS 응용 (swift)

[iOS - swift] UITableViewCell에 pressed 효과 주는 방법 (highlighted 효과, pressed 효과)

jake-kim 2024. 10. 24. 01:42

UITableViewCell의 pressed 효과

  • 기대하는 pressed 효과 (이 화면은 pressed 효과를 직접 구현한 화면)
    • UIButton의 highlighted 효과처럼 동작

 

  • 하지만 위처럼 동작하려면 별도 구현이 필요하고, 디폴트는 이렇게 동작됨
    • 단, selectionStyle = .none으로 한 상태

코드)

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    let tableView = UITableView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.frame = view.bounds
        tableView.dataSource = self
        tableView.delegate = self
        view.addSubview(tableView)
        
        tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell")
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomTableViewCell
        cell.myLabel.text = "Row \(indexPath.row)"
        return cell
    }
}

class CustomTableViewCell: UITableViewCell {
    private enum Const {
        static let backgroundColor = UIColor.white
        static let pressedColor = UIColor.gray
    }
    
    let myLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textColor = .black
        return label
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        selectionStyle = .none
        
        contentView.addSubview(myLabel)
        
        NSLayoutConstraint.activate([
            myLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            myLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

pressed 효과 주는 방법

  • 우선 커스텀 셀에 setHighlighted를 오버라이딩하여 backgroundColor를 변경
class CustomTableViewCell: UITableViewCell {
	...
    
    override func setHighlighted(_ highlighted: Bool, animated: Bool) {
        super.setHighlighted(highlighted, animated: animated)
        contentView.backgroundColor = highlighted ? Const.pressedColor : Const.backgroundColor
    }

	...
}

결과) 

  • long pressed 한 상태에서만 색상이 변경됨
    • UIButton과 다르게 UITableViewCell은 setHighlighted 호출 시점이 long pressed 시점

  • 터치했을때도 pressed 효과가 발동해야하므로 didSelectRowAt 델리게이트 함수에서 별도로 pressed 효과를 발생시키기
    • 커스텀 셀 내부에 pressed 애니메이션 구현
class CustomTableViewCell: UITableViewCell {
...
    func animatePressed(completion: @escaping () ->()) {
        contentView.backgroundColor = Const.pressedColor
        UIView.animate(withDuration: 0.3, animations: {
            self.contentView.backgroundColor = Const.backgroundColor
            self.layoutIfNeeded()
        }, completion: { _ in
            completion()
        })
    }
...
}
  • 이 함수를 didSelectRowAt에서 호출
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

...
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        (tableView.cellForRow(at: indexPath) as? CustomTableViewCell)?.animatePressed {
            // no-op
        }
    }
}

완성)

전체 코드)

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    let tableView = UITableView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.frame = view.bounds
        tableView.dataSource = self
        tableView.delegate = self
        view.addSubview(tableView)
        
        tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell")
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomTableViewCell
        cell.myLabel.text = "Row \(indexPath.row)"
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        (tableView.cellForRow(at: indexPath) as? CustomTableViewCell)?.animatePressed {
            // no-op
        }
    }
}

class CustomTableViewCell: UITableViewCell {
    private enum Const {
        static let backgroundColor = UIColor.white
        static let pressedColor = UIColor.gray
    }
    
    override func setHighlighted(_ highlighted: Bool, animated: Bool) {
        super.setHighlighted(highlighted, animated: animated)
        contentView.backgroundColor = highlighted ? Const.pressedColor : Const.backgroundColor
    }
    
    let myLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textColor = .black
        return label
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        selectionStyle = .none
        
        contentView.addSubview(myLabel)
        
        NSLayoutConstraint.activate([
            myLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            myLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16)
        ])
    }
    
    func animatePressed(completion: @escaping () ->()) {
        contentView.backgroundColor = Const.pressedColor
        UIView.animate(withDuration: 0.3, animations: {
            self.contentView.backgroundColor = Const.backgroundColor
            self.layoutIfNeeded()
        }, completion: { _ in
            completion()
        })
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}