FSCalendar 을 사용하여 TodoApp 을 만들고 있는데 문제가 생겼다.
투두리스트 작성시 달력에는 해당날짜의 투두 체크 비율을 계산하여 얼마나 완료했는지 보기쉽게 그림으로 표시하고 있다.
7월에만 몇개의 투두리스트를 작성했지만 달력을 넘기거나 다른탭에 갔다 돌아오면 투두가 없는 날도 랜덤으로 계속 표시되는 현상이 발생 했다.
코드를 보면 커스텀셀이 재사용되기전에 호출되는 prepareForReuse() 메서드에서 초기화도 해줬고,
VC의 cellFor at 메서드에서 셀에있는 완료율과 새로 들어가는 완료율이 다를때만 완료율을 다시 넣어주는 코드도 작성했는데 제대로 작동하지 않았다.
CustomCell 코드
import UIKit
import FSCalendar
final class CustomCalendarCell: FSCalendarCell {
var completionRate: Double = 0.0 {
didSet {
if oldValue != completionRate {
setNeedsDisplay()
}
}
}
override init!(frame: CGRect) {
super.init(frame: frame)
}
required init!(coder aDecoder: NSCoder!) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
completionRate = 0.0
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard completionRate > 0.0 else { return }
UIColor.white.setFill()
UIRectFill(rect)
guard let titleLabel = self.titleLabel else { return }
let labelFrame = titleLabel.frame
let labelCenter = CGPoint(x: labelFrame.midX, y: labelFrame.midY)
let radius = min(bounds.width, bounds.height) / 2.0 - 4.0
let startAngle = -CGFloat.pi / 2.0
let endAngle = startAngle + CGFloat.pi * 2.0 * CGFloat(completionRate)
// 배경 원 (투명도 0.5)
let backgroundPath = UIBezierPath(arcCenter: labelCenter, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
UIColor.red.withAlphaComponent(0.3).setStroke()
backgroundPath.lineWidth = 4.0 // 원의 두께
backgroundPath.stroke()
// 체크된 퍼센티지 부분 그리기 (투명도 1)
let foregroundPath = UIBezierPath(arcCenter: labelCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
UIColor.red.setStroke()
foregroundPath.lineWidth = 4.0
foregroundPath.stroke()
}
}
VC 의 cellFor at 메서드
func calendar(_ calendar: FSCalendar, cellFor date: Date, at position: FSCalendarMonthPosition) -> FSCalendarCell {
let cell = calendar.dequeueReusableCell(withIdentifier: "CustomCalendarCell", for: date, at: position) as! CustomCalendarCell
//캘린더의 날짜를 yyyy-mm-dd 형식으로 변경
let todoDate = ymdDateFormat(date: date)
var newCompletionRate: Double = 0.0
//데이터에서 해당날짜의 데이터가 있다면
if dicDataArray.keys.contains(todoDate) {
if let todos = dicDataArray[todoDate] {
let completedCount = completedTodoCount[todoDate] ?? 0
newCompletionRate = Double(completedCount) / Double(todos.count)
}
}
if cell.completionRate != newCompletionRate {
cell.completionRate = newCompletionRate
} else {
cell.completionRate = 0.0
}
return cell
}
이렇게 해줘도 랜덤으로 계속 나오는 것...
며칠동안 구글링해봐도 셀을 재사용하기 전에 리셋하라는 정보 말고는 찾을 수 가 없었다.
그래서!! 뭔가 완벽한 해결은 아니지만 꼼수? 를 써서 완벽한척 해보았다.
투두가 없는 날은 newCompletionRate에 나올 수 없는 값을 넣고
CustomCell 에서는 그값이 들어오면 흰색 동그라미를 그려서 그림이 안그려진척 하는 것이다.
CustomCell 코드 - 퍼센테이지에 맞게 그림도 변경해줬다.
import UIKit
import FSCalendar
final class CustomCalendarCell: FSCalendarCell {
var completionRate: Double? = nil {
didSet {
setNeedsDisplay()
}
}
override init!(frame: CGRect) {
super.init(frame: frame)
}
required init!(coder aDecoder: NSCoder!) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
completionRate = nil
}
override func draw(_ rect: CGRect) {
super.draw(rect)
// titleLabel의 프레임 가져오기
guard let titleLabel = self.titleLabel else { return }
let labelFrame = titleLabel.frame
// titleLabel의 중심 계산
let labelCenter = CGPoint(x: labelFrame.midX, y: labelFrame.midY)
let radius = min(bounds.width, bounds.height) / 2.0 - 8.0
let startAngle = -CGFloat.pi / 2.0
guard let comple = completionRate else { return }
let endAngle = startAngle + CGFloat.pi * 2.0 * CGFloat(comple)
UIColor.white.setFill()
UIRectFill(rect)
//100% 완료 -> 빨간색 동그라미
if comple >= 1.0 {
let radius = min(bounds.width, bounds.height) / 2.0 - 6.0
let foregroundPath = UIBezierPath(arcCenter: labelCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
UIColor.red.setFill()
foregroundPath.fill()
//투두가 없을떄 -> 흰색 동그라미
} else if comple == 2 {
let backgroundPath = UIBezierPath(arcCenter: labelCenter, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
UIColor.white.withAlphaComponent(0.3).setFill()
backgroundPath.fill()
//몇개만 완료했을때 -> 각각 퍼센테이지에 맞는 그림으로
}else if comple > 0.0 {
let backgroundPath = UIBezierPath(arcCenter: labelCenter, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
UIColor.red.withAlphaComponent(0.3).setStroke()
backgroundPath.lineWidth = 4.0 // 원의 두께
backgroundPath.stroke()
let foregroundPath = UIBezierPath(arcCenter: labelCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
UIColor.red.setStroke()
foregroundPath.lineWidth = 4.0
foregroundPath.stroke()
//투두리스트는 있지만 하나도 완료하지 못했을때 -> 투명도가 들어간 빨간색 동그라미
} else if comple == 0.0 {
let radius = min(bounds.width, bounds.height) / 2.0 - 6.0
let backgroundPath = UIBezierPath(arcCenter: labelCenter, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
UIColor.red.withAlphaComponent(0.3).setFill()
backgroundPath.fill()
}
}
}
VC 의 cellFor at 메서드
func calendar(_ calendar: FSCalendar, cellFor date: Date, at position: FSCalendarMonthPosition) -> FSCalendarCell {
let cell = calendar.dequeueReusableCell(withIdentifier: "CustomCalendarCell", for: date, at: position) as! CustomCalendarCell
let todoDate = ymdDateFormat(date: date)
//나올수 없는 값을 기본으로 설정
var newCompletionRate: Double? = 2
//데이터에서 해당날짜의 데이터가 있다면
if dicDataArray.keys.contains(todoDate) {
if let todos = dicDataArray[todoDate] {
//체크표시된 갯수
let completedCount = completedTodoCount[todoDate] ?? 0
newCompletionRate = Double(completedCount) / Double(todos.count)
}
}
if cell.completionRate != newCompletionRate {
cell.completionRate = newCompletionRate
} else {
cell.completionRate = nil
}
return cell
}
이렇게 하니 랜덤으로 다른날짜에 표시되는 걸 막을 수 있었다.
이것은 완벽한 해결 방법이 아니어서
이 이후 여러가지 시도를 해보다 해결법을 찾아서 아래 포스팅으로 남깁니다.
2024.07.15 - [전체 글 보기] - [UIKit] FSCalendar CustomCell 재사용 문제 - 완전해결
[UIKit] FSCalendar CustomCell 재사용 문제 - 완전해결
저번 셀의 재사용 문제 포스팅을 했는데 완벽한 해결은 아니였어서 다른방법을 찾던중 해결을 하여 포스팅 합니다. 문제어떤 문제였는지는 저번포스팅을 참고해주세요~2024.07.15 - [분류 전체
boon-hong.tistory.com