관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] UITableView 셀 터치 애니메이션 넣는 방법 (#프로토콜 활용하기, 추상화) 본문

iOS 응용 (swift)

[iOS - swift] UITableView 셀 터치 애니메이션 넣는 방법 (#프로토콜 활용하기, 추상화)

jake-kim 2024. 7. 17. 01:07

셀이 탭 되었을때 애니메이션 넣는 방법

  • 터치 이벤트는 tableView의 Delegate 메서드인 didSelectRowAt에서 받을 수 있는데, 여기서 특정 셀을 캐스팅해서 터치 애니메이션 실행이 가능
    • (애니메이션은 셀이 탭 되었을때 셀이 가지고 있는 메서드를 실행시키고, 셀 안에서 UIView.animate로 애니메이션처리)
    • (애니메이션 적용은 쉽기 때문에 이 포스팅 글에서는 생략)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = tableView.cellForRow(at: indexPath) as? CustomCellType1
    cell?.didTapCell()
}
  • 하지만 이렇게하면 확장성이 없는 문제가 존재
    • 만약 CutstomCellType2라는 셀이 생겼을 때도 똑같이 캐스팅해주어야함
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if let cell = tableView.cellForRow(at: indexPath) as? CustomCellType1 {
        cell?.didTapCell()
    } else if let cell = tableView.cellForRow(at: indexPath) as? CustomCellType2 {
        cell?.didTapCell()
    }
}

프로토콜을 활용하여 좋은 코드 만들기

  • 프로토콜에는 여러가지 기능이 있지만, 그 중에서 딱 필요한 기능만 추상화할 수 있는 기능이 존재
  • 만약 "셀이 탭 된다"는 것을 하나의 기능으로 생각하고 프로토콜로 먼저 정의한다면, 추상화 되었기 때문에 어떤 셀이든 사용이 가능
protocol CellTouchable {
    func didTapCell()
}
  • 이것을 더 넘어서 Cell 뿐만이 아닌 View로 더욱 추상화시켜서 view에도 적용이 가능
protocol Touchable {
    func didTap()
}
  • 이렇게 하면 사용하는 쪽에서도 일일이 캐스팅 필요 없이, 한줄로 표현이 가능
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    (tableView.cellForRow(at: indexPath) as? Touchable)?.didTap()
}

* 전체 코드

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    let tableView = UITableView()
    let data = ["Type 1", "Type 2", "Type 1", "Type 2", "Type 1"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.frame = self.view.bounds
        tableView.dataSource = self
        tableView.delegate = self
        self.view.addSubview(tableView)
        tableView.register(CustomCellType1.self, forCellReuseIdentifier: CustomCellType1.reuseIdentifier)
        tableView.register(CustomCellType2.self, forCellReuseIdentifier: CustomCellType2.reuseIdentifier)
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let item = data[indexPath.row]
        if item == "Type 1" {
            let cell = tableView.dequeueReusableCell(withIdentifier: CustomCellType1.reuseIdentifier, for: indexPath) as! CustomCellType1
            cell.label.text = "Type 1 cell"
            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: CustomCellType2.reuseIdentifier, for: indexPath) as! CustomCellType2
            cell.button.setTitle("Type 2 cell", for: .normal)
            return cell
        }
    }
    
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // before
//        if let cell = tableView.cellForRow(at: indexPath) as? CustomCellType1 {
//            cell?.didtapCell()
//        } else if let cell = tableView.cellForRow(at: indexPath) as? CustomCellType2 {
//            cell?.didtapCell()
//        }
    
    // after
    (tableView.cellForRow(at: indexPath) as? Touchable)?.didTap()
}
}

protocol Touchable {
    func didTap()
}

class CustomCellType1: UITableViewCell, Touchable {
    static let reuseIdentifier = "CustomCellType1"
    
    override var isHighlighted: Bool {
        didSet { print("test>", isHighlighted) }
    }
    
    override var isSelected: Bool {
        didSet { print("test>>", isSelected) }
    }
    
    let label: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.addSubview(label)
        NSLayoutConstraint.activate([
            label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16)
        ])
    }
    
    func didTap() {
        
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class CustomCellType2: UITableViewCell, Touchable {
    static let reuseIdentifier = "CustomCellType2"
    
    let button: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Press me", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.addSubview(button)
        NSLayoutConstraint.activate([
            button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            button.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16)
        ])
    }
    
    func didTap() {
        
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
Comments