오늘 하루종일 헤멨던 취소선 만들기를 정리해보려고 한다. 이 포스팅은 수많은 실패와 도전, 그리고 내 멘탈이 녹아져있다.
내 목표는 아래 사진과 같이 할 일을 추가하고(스위치 ON), 할 일을 완료하면 취소선(스위치 OFF)을 그리는 것이다. 물론 메인화면에 나갔다와도 유지되어야 한다.
취소선을 그리는 함수는 여러 블로그들을 통해 긁어온 코드이다.
이때 addAttribute가 있으면 remove도 있을거라고 생각했어야만....
extension String {
func strikeThrough() -> NSAttributedString {
let attributeString = NSMutableAttributedString(string: self)
attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0, attributeString.length))
return attributeString
}
}
취소선을 그리는 함수를 스위치 IBAction에 넣어서 스위치 상태에 따라 취소선을 그리기
class TableViewCell: UITableViewCell {
// 이전 코드 생략
// 이전 텍스트를 저장할 변수 추가
private var previousText: String? // (1)
// 스위치 상태를 저장하는 변수
var isSwitchOn = true // (2)
@IBAction func memoSwitch(_ sender: UISwitch) { // (3)
isSwitchOn = sender.isOn
updateLabelStrikeThrough()
}
func updateLabelStrikeThrough() { // (4)
if isSwitchOn {
// 스위치가 on인 경우 취소선을 없애고 이전 텍스트 복원
memoLabel.attributedText = nil // (5)
memoLabel.text = previousText // (6)
} else {
// 스위치가 off인 경우 취소선을 추가하고 이전 텍스트 저장
previousText = memoLabel.text // (7)
memoLabel.attributedText = memoLabel.text?.strikeThrough() // (8)
}
}
🥲 적용 화면
🤔 문제점/해결방안
(1) 메모를 추가하고 스위치를 작동했을 때 적용이 잘 되나, 메인화면에 갔다오면 스위치 작동이 초기화 되는 문제 발생
-> 셀을 불러올 때 TableViewController에서 updateLabelStrikeThrough 함수를 실행하면 될 듯(메모리스트의 스위치 상태에 따른 취소선)
(2) 스위치를 끈 상태에서 새 메모를 추가하면 기존 메모의 스위치 상태가 변하고 취소선도 맘대로 그어져있음
-> 스위치의 상태를 저장하고 셀이 재사용될 때 기존 셀의 상태를 가져오는 코드로 보완하면 어떨까 생각
첫번째 문제로 인해 내가 생각해본 해결방안을 적용해서 보완해보았다.
class TableViewController: UITableViewController {
// 이전 코드 생략
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
let target = myMemo.memoList[indexPath.row]
cell.memoLabel?.text = target.content
cell.memoSwitch.isOn = target.isCompleted
// 추가된 코드
cell.updateLabelStrikeThrough() // (1)
cell.memo = target // (2)
return cell
}
// 생략
}
class TableViewCell: UITableViewCell {
// 이전 코드 생략
// 이전 텍스트를 저장할 변수 추가
private var previousText: String?
// (삭제) 스위치 상태를 저장하는 변수
// var isSwitchOn = true
// 스위치 상태 가져오기
var memo: Memo? { // (3)
didSet {
guard let memo = memo else { return }
memoLabel.text = memo.content
memoSwitch.isOn = memo.isCompleted
}
}
@IBAction func memoSwitch(_ sender: UISwitch) { // (4)
guard let memo = memo else { return }
memo.isCompleted = sender.isOn
updateLabelStrikeThrough()
}
func updateLabelStrikeThrough() { // (5)
// 스위치가 on인 경우 취소선을 없애고 이전 텍스트 복원
if memoSwitch.isOn {
memoLabel.attributedText = nil
memoLabel.text = previousText
} else {
// 스위치가 off인 경우 취소선을 추가
previousText = memoLabel.text
memoLabel.attributedText = memoLabel.text?.strikeThrough()
}
}
🥲 적용 화면
🤔 해결한 것/문제점/해결방안
(1) (해결한 것) 셀을 불러올 때 메모리스트의 스위치 상태에 따른 취소선 적용 완료
(2) 스위치를 끈 상태에서 새 메모를 추가하면 처음 추가는 문제가 없으나, 두번째부터는 기존 메모의 취소선이 맘대로 그어져있음
-> 취소선을 제거하는 함수를 별도로 만들어 attributedText를 nil로 두지말고 함수로 실행해보면 어떨지?
취소선이 너무 싫다.. 왜 자꾸 안사라지고 남아있는 것임????
class TableViewController: UITableViewController {
// 이전 코드 생략
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
let target = myMemo.memoList[indexPath.row]
cell.memoLabel?.text = target.content
cell.memoSwitch.isOn = target.isCompleted
cell.updateLabelStrikeThrough()
// 삭제
// cell.memo = target
return cell
}
// 생략
}
class TableViewCell: UITableViewCell {
// 이전 코드 생략
// (삭제) 이전 텍스트를 저장할 변수 추가
// private var previousText: String?
// (삭제) 스위치 상태를 저장하는 변수
// var isSwitchOn = true
// 스위치 상태 가져오기
var memo: Memo? // (1)
@IBAction func memoSwitch(_ sender: UISwitch) { // (2)
guard let memo = memo else { return }
memo.isCompleted = sender.isOn
updateLabelStrikeThrough()
}
// 추가
func insertLabelContent(content: String?) { // (3)
memoLabel?.text = content
}
func updateLabelStrikeThrough() { // (4)
// 스위치 상태에 따른 취소선 업데이트
if self.memoSwitch.isOn {
insertLabelContent(content: memo?.content)
self.memoLabel?.attributedText = self.memoLabel?.text?.removeStrikeThrough()
} else {
self.memoLabel?.attributedText = self.memoLabel?.text?.strikeThrough()
}
}
}
extension String {
func strikeThrough() -> NSAttributedString {
let attributeString = NSMutableAttributedString(string: self)
attributeString.addAttribute(
NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0, attributeString.length))
return attributeString
}
func removeStrikeThrough() -> NSAttributedString { // (5)
let attributeString = NSMutableAttributedString(string: self)
attributeString.removeAttribute(
NSAttributedString.Key.strikethroughStyle, range: NSMakeRange(0, attributeString.length))
return attributeString
}
}
🥲 적용 화면
🤔 문제점/해결방안
(1) 스위치 Off 시 취소선 작동한 이후 스위치 On 시 취소선 삭제가 안되는 문제 발생, removeAttribute 안먹음
-> 구글링 결과... NSUnderlineStyle.single 취소선 스타일이 추가되지 않아 삭제가 안됐다고 함. 취소선을 없애는 스타일을 NSMutableAttributedString에 추가한 후 재시도
드디어.. 성공!!!!! 장장 몇시간이야.. 진짜 하루종일 이거 때매 난리였다..
extension String {
func strikeThrough() -> NSAttributedString {
let attributeString = NSMutableAttributedString(string: self)
attributeString.addAttribute(
NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0, attributeString.length))
return attributeString
}
func removeStrikeThrough() -> NSAttributedString {
let attributeString = NSMutableAttributedString(string: self)
// 수정된 부분
attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 0 , range: NSMakeRange(0, attributeString.length))
return attributeString
}
}
🥲 적용 화면
😭 회고
미쳤다. 진짜.. 취소선 어지간히 복잡한게 아니네...
처음에 앱 만들기 시작할 때 완료된 UI의 예시가 취소선으로 나와있어서 그대로 따라서 시작을 했는데 이렇게 힘들 줄 몰랐다.
그런데 두번째 시도까지만 해도 코드만 봐서는 이상한게 없었는데 취소선의 attributedText가 안먹는게 너무 이해가 안되서 취소선 함수 말고 폰트 색상을 바꿔서 시도해봤는데 너무 잘 되는거다!!
잘 되는거 보고 취소선 포기할까도 고민했지만, 이것저것 시도해보면서 이건 왜 안되고 이건 왜 되지?? 이런 생각을 하고 찾아보는 시간을 가질 수 있어서 좋았다.
그리고 취소선을 없애는걸 완성하면서 드는 생각은 앱을 기획할 때는 지극히 유저 입장을 먼저 고려해야한다는 것이다.
생각해보면 완료된 할 일을 [할 일 확인하기] 목록에서 봐야할 필요도 없을 뿐더러, 스위치를 Off 하는 순간 해당 목록에서 사라지는게 가장 UX/UI가 고려된 개발이 아닐까 싶다.
그리고 [할 일 확인하기] 목록에 남겨둘 거라면 취소선 보다 비활성화 느낌을 줄 수 있는 색상의 음영을 통해 표현하는 것이 더 좋을 것이다.
오늘의 경험을 토대로 추가 필수 기능 구현 후에 UX/UI가 고려된 어플로 보완해보고자 한다. 그러려면 먼저 기획을 수정해야겠지?
코드가 너무 복잡하고 removeAttribute가 있는데 제대로 못쓰고 있는게 아쉬워서 새마음 새뜻으로 다음날 아침에 수정봤는데 됐다.. Jump to Definition 확인하는 습관 가지자...!!
import UIKit
class TableViewCell: UITableViewCell {
var myMemo = MemoManager.myMemo
@IBOutlet weak var memoLabel: UILabel!
@IBOutlet weak var memoSwitch: UISwitch!
var memo: Memo?
@IBAction func memoSwitch(_ sender: UISwitch) {
guard let memo = memo else { return }
memo.isCompleted = sender.isOn
updateLabelStrikeThrough()
}
func updateLabelStrikeThrough() {
// 수정된 부분
if memoSwitch.isOn {
memoLabel.attributedText = memoLabel.text?.removestrikeThrough()
} else {
memoLabel.attributedText = memoLabel.text?.strikeThrough()
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func prepareForReuse() {
super.prepareForReuse()
// 추가된 부분
memoLabel.attributedText = nil
}
}
extension String {
func strikeThrough() -> NSAttributedString {
let attributeString = NSMutableAttributedString(string: self)
attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0, attributeString.length))
return attributeString
}
// 수정된 부분
func removestrikeThrough() -> NSAttributedString {
let attributeString = NSMutableAttributedString(string: self)
attributeString.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: NSMakeRange(0, attributeString.length))
return attributeString
}
}
동작 화면은 이전과 동일하기 때문에 추가 첨부는 하지 않겠다.
단순히 removeAttribute를 사용해 취소선을 긋는 범위와 name을 addAttribute와 동일하게 주고 셀을 재사용할 때 해당 기능을 nil로 바꿔주니까 해결이 됐다.
개발할 때 막히면 손을 좀 놨다가(...) 다시 해보면 또 다른 해결방안이 떠오를 수 있으니까 리프레시를 하자!!
좋은 글 보고 갑니다..
취소선 때문에 머리 아팠는데 한줄기의 빛이... ㅎㅎ