일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- flutter 회전
- 플러터 뷰 컨트롤러
- 안드로이드 바로가기
- 노티피케이션 익스텐션
- silent push
- 스위프트 푸시
- Swift flutterviewcontroller
- Swift flutterview
- native flutter view
- 안드로이드 앨범
- NotificationService Extension
- flutter rotate
- 스위프트 UserDefaults
- FlutterView MethodChannel
- Flutter NativeView
- Flutter UIKitView MethodChannel
- swift sms
- 안드로이드 숏컷
- 스위프트 앨범
- 스위프트 웹뷰
- swift 문자
- 앱 백그라운드 푸시 데이터 저장
- 푸시 데이터 저장
- 안드로이드 FCM
- 스위프트 테이블 뷰 셀
- 앱 꺼졌을 때 푸시 데이터 저장
- 안드로이드 에러
- 스위프트 카메라
- 스위프트
- swift autolayout
- Today
- Total
Things take time
[SWIFT] UITableView의 Custom Cell 사용 시, UI가 중첩 및 뭉쳐지는 현상 본문
[현상]
iOS의 TableView를 그릴 때 기본적으로 사용하는 함수 중 하나는 아래의 함수다.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
각 테이블 뷰의 row를 그릴 때, cell을 편집 및 리턴하는 함수다.
여기서 스토리보드에 cell의 모양을 미리 그리고, cell 파일을 생성하여 연결 후 reuse identifier를 이용하여 호출하게 한다.
이 과정은 알고 있다 가정한다.
아래의 상황을 가정하자.
1. 테이블 뷰에는 여러 가지 타입의 셀이 있다.
2. 각각의 셀에는 다양한 UI를 포함한다. (이미지 뷰 1개 + 텍스트 뷰 1개 & 뷰 안에 뷰 + 이미지 뷰2개 + 라벨 3개 등)
3. 그리고 그 UI 위젯들 또한 AutoLayout을 통해 제약조건이 걸려있다.
4. 테이블의 높이는 유동적이라 AutomaticDimension을 RowHeight로 설정한다.
이 상황에서 위 함수를 통해 작성할때는 보통 아래와 같은 로직을 거쳐야 한다.
1. 테이블에 넣을 데이터를 이전 뷰에서 넘겨받거나 Restful API로 넘겨받는다. (보통 Dictionary Array를 많이 사용할 것이다)
2.데이터를 넘겨받고 TableView.reloadData()를 호출한다.
3. 그리고 cellForRowAt 및 numberRowsInSection 등의 초기화 함수를 호출한다.
그리고 아래와 같은 코드를 cellForRowAt에서 작성했다.
let cell = tableView.dequeueReusableCell(withIdentifier: "TlkMsgLstToCellUrl") as! TlkMsgLstToCellUrl cell.lblMsgCont.text = self.msgContLstList[indexPath.row] cell.lblMsgCont.lineBreakMode = .byWordWrapping cell.lblMsgCont.sizeToFit() cell.lblMsgCont.numberOfLines = 0 cell.lblDt.text = "2019.03.22" cell.lblInq.text = "1" cell.lblUrlTtl.text = "네이버" cell.lblUrlAddr.text = "https://www.naver.com" cell.lblUrlInfo.text = "이것은 네이버의 주소랍니다."
데이터를 넣어주고, sizeToFit으로 automaticDimension에 맞게 변형되도록 한다.
그리고 실행을하면 아래와 같이 나온다.
일단 두개의 셀을 캡쳐한 것인데
위와 아래의 문제점은 셀의 너비가 똑같다는 것이다.
위에는 www.naver.com 밖에 적지 않았기에 저 너비가 맞지만, 아래는 문자열의 길이가 다른데 왜 너비가 같이 나오냐는 것이다.
그리고 또한, 테이블 뷰를 스크롤하다보면 이 문자열 너비에 맞춰서 다른 애들도 같이 영향을 받게 된다. 이런 문제를 해결하는 것이 이 포스팅의 주제다.
[해결]
답은 Cell파일, UITableViewCell을 상속받는 그 커스터마이징 클래스 파일에서 수행한다.
1. prepareForReuse()
이 함수는 셀을 재 사용할때 호출한다. 테이블 뷰에서 셀은 해당 로우의 개수만큼을 생성하지 않는다. 당연히 느려지기 때문이다. 기기마다 보이는 한 화면에 보일 수 있는 로우의 개수정도를 생성하고, 화면이 바뀌면 그 로우를 재사용한다. 흔히 웹에서도 페이징이란 것과 같다.
그러나, 이 방법을 사용하다보니 UI 그 중에서도 autoLayout이나 addSubView를 통해 동적으로 UI를 넣게 되면, 스크롤 할 때마다 꼬이게 된다.
addSubView들을 사용하는 셀에 대한 대응 방법은
타입에 따라 템플릿 셀을 각각 생성한다.
동적으로 생성하는 방법의 장점은 따로 템플릿을 만들지 않고 한 코드 안에서 바로 추가한다는 것이지만
유지보수가 어렵고, 새로운 타입이 추가되면 다시 코드를 분석해야 하며, heightForRowAt도 고려해야하는 골치아픔이있다.
정적, 템플릿으로 셀들을 대부분 정의하면 초반 시간이 오래드는 단점이 있지만, 유지보수가 간편하다는게 가장 큰 장점이다.
어쨌든 나같은 경우는 템플릿을 다 커스텀으로 미리 구분해 놓았음에도, 라벨 하나의 텍스트 내용 때문에 UI가 위처럼 나온다는 것이다. 당연한 얘기지만, 저 라벨은 sizeToFit()으로 라벨에 들어가는 텍스트 길이 만큼 유동적인 데이터이다.
이럴때는 prepareForReuse()함수에서 해당 셀 클래스 파일에 연결된 아울렛 변수들의 값을 모두 nil 및 초기값으로 초기화 한다.
참고로 초기화 할때는 text값 뿐 아니라 Constraint 등도 cellForRowAt에서 재 설정해야 한다면, 초기화해야 한다.
override func prepareForReuse() { super.prepareForReuse() self.lblMsgCont.text = nil self.lblUserNm.text = nil
self.vMsgWrapWidth.constant = 250 }
이미지뷰나 라벨같은 애들은 nil로 초기화해주고 위 스크린샷과 같이 메시지 너비도 변경되기 때문에 초기화(스토리 보드에서 설정한 값)한다.
2. setNeedsLayout, layoutIfNeeded
위의 방법으로는 일반적인 테이블 뷰 셀에 모두 적용된다. 또한, 구글링을 하다보면 여기까지 알려주는게 대부분.
그러나 나같은 경우에는 1번 방법으로는 해결되지 않았다.
원인은 오토레이아웃, 사용자가 작성한 텍스트의 길이만큼을 계산해서 해당 너비만큼 화면을 넓히고 줄여야하는데 각 셀이 재사용되면서 꼬이는 듯 하다.
그래서 최종 코드는
override func prepareForReuse() { super.prepareForReuse() self.lblMsgCont.text = nil self.lblUserNm.text = nil self.vMsgWrapWidth.constant = 250 self.updateLayout() } func updateLayout(){ self.setNeedsLayout() self.layoutIfNeeded() }
해결되었다.
왼쪽으로 더 길어야하는거 아니냐 할 수 있겠지만, 여기에 작성되진 않았지만 나는 저 문자열의 최대 맥시멈 값을 설정해서 그렇다.
저 두 함수는 자주보이는 함수이므로 뜻은 알 고 있는 것이 좋다.
2-1) setNeedsLayout
일반적으로 뷰 컨트롤러의 기본 함수중 하나인 layoutSubViews()라는 함수가 있다. 하위 뷰를 그릴 때 호출되는 함수인데, 이 함수를 호출시켜달라는 함수다. 즉시는 아니고 지금부터 다음 가능한 타임까지. layoutSubViews()함수는 자고로 직접 호출 불가하다.
2-2) layoutIfNeeeds
만약 필요하다면 레이아웃! 뭐 이렇게 억지해석할 수 있다. 위와 연결지어 layoutSubViews()함수를 즉시 호출하라는 함수이다. 그러므로 setNeedsLayout과 같이 쓰게 되면, 바로 layoutSubViews()를 호출하게 된다.
layoutSubviews()는 기본적으로 제약조건이랑 관련이있다. 제약조건을 변경할 때 사용한다.(물론 다른 경우도 많음)
안드로이드의 Gone 옵션이 iOS에도 얼른 도입되었으면 좋겠다.
[문제 도움]
https://stackoverflow.com/questions/28642404/uiimageview-in-a-uitableviewcell-and-auto-layout-not-working-on-cell-reuse
[용어 도움]
https://zeddios.tistory.com/359
https://baked-corn.tistory.com/105
'iOS (기능)' 카테고리의 다른 글
[SWIFT] 테이블 뷰 에서 AutomaticDimension 사용시, 이미지뷰에 따른 높이 조절하기 (0) | 2019.04.08 |
---|---|
[SWIFT] Autolayout, 오토레이아웃에서 Android의 Gone과 같은 뷰 조절을 해보자. (0) | 2019.03.28 |
[SWIFT] 특수문자, 한글, 숫자, 이모티콘 정규식! (4) | 2019.03.04 |
[SWIFT] Command /usr/sbin/chown failed with exit code 1 (0) | 2019.01.22 |
[SWIFT] Embed ViewController(+ View) programmatically (0) | 2019.01.21 |