티스토리 뷰
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)
}
}
두 가지 방법중 한 가지를 선택해서 구현해 보시면 다음과 같이 버튼의 상태가 엉키지 않는 것을 확인하실 수 있습니다.
읽어주셔서 감사합니다.
'Swift&iOS > iOS' 카테고리의 다른 글
[iOS] Network - TLS 문제 해결 (0) | 2020.12.19 |
---|---|
[iOS] MVVM 디자인 패턴 정리 및 예제코드 (11) | 2020.11.17 |
[iOS] Cell을 재사용시 생기는 문제점들과 해결 방법 (1) | 2020.10.26 |
[iOS] CollectionView 3D 전환, carousel effect 주기 (2) (1) | 2020.08.03 |
[iOS] CollectionView 3D 전환, carousel effect 주기 (1) (0) | 2020.08.01 |