TIL(230211)

Youth·2023년 2월 11일
0

1. Protocol사용해서 UITableView의 Cell중복코드 줄여보기

나는 기본적으로 UITableViewCell을 사용할때
이니셜라이즈를 아래와 같이 설정해주고

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

타입변수로 cellId를 설정해준다(identifier로 설정할떄도 있음)

static let identifier = "TestTableViewCell"

이런식으로 할때의 단점은 내가 키보드를 치는 방식이 잘못된건지 table을 tabel로 써서 이상하게 여기서 오류가 발생할리가 없는데하고 찾아보면 스펠링문제였던적이 한두번이 아니다

그래서 보통 register 메서드에 인자를 집어넣을때

estTableView.register(cellClass: AnyClass?, forCellReuseIdentifier: String)

forCellReuseIdentifer에 아얘 TestTableViewCell.identifier로 타입변수를 넣어버린다
사실은 이게 크게 불편하지는 않았다 tableView야 워낙 많이 쓰는데다가 알고쓴다 느낌보다는 그냥 손이 먼저움직일정도로 외우고있는 느낌이라...

근데 한달동안 여행을 갔다와서 register메서드랑 아래 방식을 까먹어서 꽤나 애먹었다

guard let cell = tableView.dequeueReusableCell(withIdentifier: TestTableViewCell.identifier, for: indexPath) as? TestTableViewCell else { return UITableViewCell() }

근데 tableView관련해서 자료를 찾아보다보니 이렇게 매번 타입변수로 cellId나 identifer를 이용하지 않고 protocol이랑 extension을 통해서 이걸 편하게 사용할수있는 방법이 있길래 한번 사용해봤다

우선 결론부터 보여주자면

protocol TableViewCellReuseProtocol {
    static func register(tableView: UITableView)
    // MARK: - 여기서 Self는 타입인데 protocol을 채택하는 타입을 이야기함(확장성을 고려할수있게됨)
    static func dequeueReusableCell(tableView: UITableView) -> Self
    static var reuseIdentifier: String { get }
}

// MARK: - TableViewCelled라는 프로토콜은 기본값(defalult)를 가진다 -> 언제? -> UITableViewCell이라는 타입의 클래스에 채택되었을때만!
extension TableViewCellReuseProtocol where Self: UITableViewCell {
    static func register(tableView: UITableView) {
        // MARK: - 타입속성에서 self를 사용하면, 인스턴스가 아니라 타입자체를 가리킨다
        // 즉, 타입속성에서 다른 타입속성에 접근하려면 self로 접근해야한다
        tableView.register(self, forCellReuseIdentifier: self.reuseIdentifier)
        //== tableView.register(Self.self, forCellReuseIdentifier: self.reuseIdentifier)
        // MARK: - register에는 AnyClass라는 메타타입의 value값과 CellIdentifier이 들어가야함
        // cellidentifer은 다른 static변수인 reuseIdentifier을 넣어주면됨
        //tableView.register(<#T##cellClass: AnyClass?##AnyClass?#>, forCellReuseIdentifier: <#T##String#>)
    }
    
    static func dequeueReusableCell(tableView: UITableView) -> Self {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: self.reuseIdentifier) else { fatalError("Error! \(self.reuseIdentifier)") }
        return cell as! Self
    }
    
    static var reuseIdentifier: String {
        // 타입속성에서 Self.self와 self는 같은 의미를 가진다
        return String(describing: self)
        //return String(describing: <#T##Subject#>)
        //Subject에는 메타타입의 value값을 넣어준다
    }
}

cell이 이 프로토콜을 채택하게 되면 타입메서드로 register 메서드와 cell등록 메서드를 대체할수있게된다

기본적으로 위의 코드를 전부 이해하려면(나도 한 두시간걸린거같다...) 두가지의 개념정도가 필요하다
1.self와 Self의 차이(메타타입정도)
2.protocol과 extension(protocol을 extesion으로 확장하면 기본값이 생긴다는 개념정도?)

간단히 설명을 해보자면
첫번째로 보통 protocol을 만들어서 클래스에서 채택하면 선언한 메서드를 직접 구현해줘야했지만 extension으로 protocol을 확장하고 protocol의 메서드들을 구현해놓으면 그 구현된 메서드들이 기본값으로 선언된다?의 느낌이다 그러니까 어느 클래스가 채택해도 똑같을 메서드면 그냥 extension으로 확장해서 구현해놓으면 편하다는거다

두번째는 self와 Self 그리고 메타타입에관한 이야기인데 간단히 이야기해보자면 Self는 "타입"이다 근데 변수에는 타입이 들어가는게 아니라 value가 들어간다 그래서 타입의 value를 만들어주는게 self다
메타타입은 000.Type인데 여기다가 타입을 넣을수가 없다 그래서 여기다는 000.self를 넣어야한다
그리고 return 4 이렇게 하지 return Int 이렇게 하지 않으니까 이 Int를 value로 만들어주는게 Int.self라고 생각하면 편하다
그리고 프로토콜선언할떄 Self가 나온다? 이건 프로토콜을 채택한 타입을 뜻한다
예를 들어서 Int가 프로토콜을 채택했으면 Self는 Int가 되는거고 Float이 채택했으면 Float가되는거다
더 자세한 내용은 https://velog.io/@kimscastle/Swift문법-21Self키워드 여기서 확인하자

어쩃든 이렇게 프로토콜을 선언해놓으면
identifer나 cellId를 선언하지 않아도 된다

기존에는 register을 아래와같이 선언했다면

testTableView.register(TestTableViewCell.self, forCellReuseIdentifier: TestTableViewCell.identifier)

프로토콜을 Cell이 채택했다면 타입메서드들을 이미 가지고 있기때문에

TestTableViewCell.register(tableView: testTableView)

이렇게 아주 아주 간단해진다

그리고 두번째
dataSource에서 cell을 연결해주는 부분이 원래 guard문을 통과해야해서

guard let cell = tableView.dequeueReusableCell(withIdentifier: TestTableViewCell.identifier, for: indexPath) as? TestTableViewCell else { return UITableViewCell() }

이렇게 길고 다운캐스팅에 아주 복잡하지만 이것도

let cell = TestTableViewCell.dequeueReusableCell(tableView: testTableView)

이렇게 아주아주 간단해진다 그리고 여기서 만든 cell을 configure만 해주면 된다

팀프로젝트를 할때 미리 이렇게 프로젝트 세팅단계에서 프로토콜을 만들어놓고 BaseTableViewCell같은거에다가 프로토콜을 채택해두고 git에다가 사용방법을 적어놓으면 유용할거같다는 생각이 든다


2. Cell높이지정해주기

Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a table view cell's content view.

tableView를 만들었더니 이런 경고가 발생했다. 앱은 문제없이 돌아가는거보니 안고쳐도 될거같은데 터미널에서 이런게 계속 보이는건 좀 거슬린다 읽어보니 tableView의 cell row를 지정하지 않아서 생긴 문제인거 같다

private var testTableView = UITableView()
testTableView.rowHeight = 70

rowHeight를 선언해주면 경고가 깔끔하게 사라진다


전체코드
https://github.com/kimscastle/SwiftPrograming_Study/tree/main/TrialAndError/CellReuseProblem

profile
AppleDeveloperAcademy@POSTECH 1기 수료, SOPT 32기 iOS파트 수료

0개의 댓글