Notice
Recent Posts
Recent Comments
Link
관리 메뉴

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

[iOS - swift] extendable tableView 구현 방법 (동적으로 늘어나는 셀 구현), reloadRows(at:with:) 본문

UI 컴포넌트 (swift)

[iOS - swift] extendable tableView 구현 방법 (동적으로 늘어나는 셀 구현), reloadRows(at:with:)

jake-kim 2022. 12. 25. 22:50

셀을 누를 경우 밑으로 내용들이 펼쳐지는 UI

Extendable tableVeiw 구현 아이디어

  • 셀의 UI는 stackView에 label을 넣고 hidden을 on/off하며 펼쳐지거나 줄어들게끔 구현
  • 데이터 소스 타입에 isDescHidden와 같이 플래그를 넣고, 확장하고 싶은 경우 isDescHidden을 false로 한 다음 해당 데이터 부분만 reloadRows(at:with:)을 통해 업데이트
    • reloadRows(at:with:)에서 애니메이션도 지정할수 있는데, 아래로 팽창하듯이 늘어나는 옵션은 UITableView.RowAnimation.automatic 사용
// 해당 셀 업데이트
tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 0)], with: UITableView.RowAnimation.automatic)

구현 방법

  • 셀에 표시될 데이터 소스 타입 정의
typealias Item = (title: String, desc: String, isDescHidden: Bool)
  • 셀 정의
    • hidden시키는 경우 intrinsic content size가 자동으로 줄어들어야 하므로, 편의를 위해 stackView를 사용
    • descLabel을 hidden시킬 것
    • 사용하는 쪽에서 prepare()를 호출하여 데이터 초기화
final class MyTableViewCell: UITableViewCell {
    static let id = "MyTableViewCell"
    
    private let stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }()
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.numberOfLines = 1
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    private let descLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .regular)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        contentView.addSubview(stackView)
        
        [titleLabel, descLabel]
            .forEach(stackView.addArrangedSubview(_:))
        
        NSLayoutConstraint.activate([
            stackView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
            stackView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
            stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
        ])
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        self.prepare(item: nil)
    }
    
    func prepare(item: Item?) {
        titleLabel.text = item?.title
        descLabel.text = item?.desc
        descLabel.isHidden = item?.isDescHidden ?? true
    }
}
  • tableView를 가지고 있는 ViewController 정의
class ViewController: UIViewController {
    private let tableView: UITableView = {
        let view = UITableView()
        view.backgroundColor = .clear
        view.separatorStyle = .none
        view.bounces = true
        view.showsVerticalScrollIndicator = true
        view.contentInset = .zero
        view.register(MyTableViewCell.self, forCellReuseIdentifier: MyTableViewCell.id)
        view.estimatedRowHeight = 34
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private var items = (0...50)
        .map(String.init)
        .map { val in
            Item(title: val, desc: "long description, long description \n long descriptionlong descriptionlong descriptionlong description\n", isDescHidden: true)
        }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(tableView)
        NSLayoutConstraint.activate([
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
        ])
        
        tableView.dataSource = self
        tableView.delegate = self
    }
}
  • 데이터소스
extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        items.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: MyTableViewCell.id, for: indexPath) as! MyTableViewCell
        let item = items[indexPath.row]
        cell.prepare(item: item)
        return cell
    }
}
  • 셀을 탭했을 때, 데이터를 변경한 후 reload(at:with:) 호출
extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let previous = items[indexPath.row]
        items[indexPath.row] = Item(title: previous.title, desc: previous.desc, isDescHidden: false)
        tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 0)], with: UITableView.RowAnimation.automatic)
    }
}

* 전체 코드: https://github.com/JK0369/ExpendableTableView

* 참고

https://blog.devgenius.io/easy-way-to-create-an-expandable-uitableviewcell-in-swift-5-a5c72d1361da

Comments