티스토리 뷰

2020/10/26 - [Swift&iOS/iOS] - [iOS] Cell을 재사용 시 생기는 문제점들과 해결 방법에 이어서 오늘은 셀을 재사용함으로써 생기는 조금 더 심화적인 문제를 다뤄보려 합니다. 

이전 글에서는 셀의 index 별로 터치 됐다 안됐다를 기준으로 하트 버튼의 상태를 변경해 줬었는데요. 이제 하트 버튼이 동작하도록 해볼 예정입니다. 

셀 안에 스위치, 체크박스, 하트(좋아요)와 같은 버튼등이 들어가게 되면 상황이 조금 골치 아파집니다. 셀을 재활용하기 때문에, 누르지도 않은 인덱스에 버튼이 눌려져 있다던가 아니면 눌렀는데도 버튼이 안 눌려져 있다던가... 다음 보시는 것처럼 스크롤 몇 번 해보면 아주 뒤죽박죽 난장판이 됩니다. ㅜㅜ

 

이 문제를 해결하기 위해서는 사용자가 버튼을 눌렀을 때 해당 인덱스 버튼의 상태 (사용자가 눌렀는지 안눌렀는지)를 기억하고 있을 필요가 있습니다. 그래야 셀의 인덱스 별로 정확한 버튼의 상태를 반영해 줄 수 있기 때문이죠. 저는 Delegate Pattern과 인덱스 별 버튼 상태를 저장하기 위한 배열을 갖고 문제를 해결했습니다. 

import UIKit

class ViewController: UIViewController{

    @IBOutlet weak var tableView: UITableView!
    var likes: [Int] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        likes = Array<Int>(repeating: 0, count: 30)
    }
}

extension ViewController: UITableViewDataSource, TestCellDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 30
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "testCell") as! TestCell
        cell.delegate = self
        cell.index = indexPath.row
        
        if likes[indexPath.row] == 1 {
            cell.isTouched = true
        }else{
            cell.isTouched = false
        }
        return cell
    }
    
    func didPressHeart(for index: Int, like: Bool) {
        if like == true{
            likes[index] = 1
        }else{
            likes[index] = 0
        }
    }
}
import UIKit

protocol TestCellDelegate {
    func didPressHeart(for index: Int,like: Bool)
}

class TestCell: UITableViewCell {
    
    var delegate: TestCellDelegate?
    var index: Int?
    
    @IBOutlet weak var heartButton: UIButton!
    @IBAction func didPressedHeart(_ sender: UIButton) {
        guard let idx = index else {return}
        if sender.isSelected {
            isTouched = true
            delegate?.didPressHeart(for: idx, like: true)
        }else {
            isTouched = false
            delegate?.didPressHeart(for: idx, like: false)
        }
        sender.isSelected = !sender.isSelected
    }
    
    var isTouched: Bool? {
        didSet {
            if isTouched == true {
                heartButton.setImage(UIImage(systemName: "heart.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large)), for: .normal)
            }else{
                heartButton.setImage(UIImage(systemName: "heart", withConfiguration: UIImage.SymbolConfiguration(scale: .large)), for: .normal)
            }
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

    }
}

 

여기서 저는 셀이 30개라고 가정해서 미리 버튼의 상태를 저장할 길이 30의 배열을 만들었지만, 만약 사용자에게 보여질 셀(데이터)이 많다면 이 방법은 메모리 관리 측면에서 비효율 적일 것 같습니다. 만약 셀이 만개라면 길이가 1만 인 배열을 갖고 있어야 하니까요. 그래서 만약 화면에 표시해야 할 데이터가 많다면 다음과 같이 배열 대신 딕셔너리로 구현하는 것이 더 좋을 것 같습니다 :)

import UIKit

class ViewController: UIViewController{

    @IBOutlet weak var tableView: UITableView!
    lazy var likes: [Int:Int] = [:]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
    }
}

extension ViewController: UITableViewDataSource, TestCellDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1000
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "testCell") as! TestCell
        cell.delegate = self
        cell.index = indexPath.row
        
        if likes[indexPath.row] == 1 {
            cell.isTouched = true
        }else{
            cell.isTouched = false
        }
        return cell
    }
    
    func didPressHeart(for index: Int, like: Bool) {
        if like{
            likes[index] = 1
        }else{
            likes[index] = 0
        }
    }
}
import UIKit

protocol TestCellDelegate {
    func didPressHeart(for index: Int, like: Bool)
}

class TestCell: UITableViewCell {
    
    var delegate: TestCellDelegate?
    var index: Int?
    
    @IBOutlet weak var heartButton: UIButton!
    @IBAction func didPressedHeart(_ sender: UIButton) {
        guard let idx = index else {return}
        if sender.isSelected {
            isTouched = true
            delegate?.didPressHeart(for: idx, like: true)
        }else {
            isTouched = false
            delegate?.didPressHeart(for: idx, like: false)
        }
        sender.isSelected = !sender.isSelected
    }
    
    var isTouched: Bool? {
        didSet {
            if isTouched == true {
                heartButton.setImage(UIImage(systemName: "heart.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large)), for: .normal)
            }else{
                heartButton.setImage(UIImage(systemName: "heart", withConfiguration: UIImage.SymbolConfiguration(scale: .large)), for: .normal)
            }
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

    }
}

 

두 가지 방법중 한 가지를 선택해서 구현해 보시면 다음과 같이 버튼의 상태가 엉키지 않는 것을 확인하실 수 있습니다.

 

읽어주셔서 감사합니다.

댓글
링크
최근에 올라온 글
최근에 달린 댓글