Things take time

[SWIFT] Static Cell을 사용하는 테이블 뷰의 커스터마이징(뷰 추가하기) 본문

iOS (기능)

[SWIFT] Static Cell을 사용하는 테이블 뷰의 커스터마이징(뷰 추가하기)

겸손할 겸 2018. 6. 19. 13:15

[개요]


제목만으로는 헛갈리는 내용이다.


간략히 설명하면

iOS의 테이블 뷰 안에 들어가는 Cell은 두 가지 타입이 존재한다.



Static / Dynamic


뜻은 다 알것이고, 간략히 스토리보드상에서 UI의 대부분을 그려야할 때, 데이터가 크게 변하지 않을때(물론 변하더라도 static에서 조정 가능) 사용하며, Dynamic은 일반적으로 책에서 소개하는 테이블 뷰 셀이다.


그리고 static은 section이라는 부분이 있어서, 테이블 뷰의 셀들을 관리하는 그 상위의 영역이 존재한다.

즉, 1개의 테이블에는 1개 이상의 섹션이 존재하며, 각 섹션은 1개 이상의 셀을 포함하고 있다. 그러므로 셀들의 공통집합들을 섹션으로 구분하고, 이 섹션의 집합이 테이블 뷰가 되는 것이다.


dynamic은 하나의 테이블 뷰에는 1개이상의 셀로만 구성되어있다.


그리고 결론과 마찬가지 이지만, static cell을 사용할 수 있는 것은 해당 테이블뷰가 포함된 뷰 컨트롤러는 UITableViewController를 상속받아아야한다. 즉, 일반적인 UIViewController를 상속받는 일반 뷰 컨트롤러에서는 static cell을 가진 테이블 뷰를 사용할 수없다!



[문제]


static cell로 사용하고 있던 UITableViewController를 상속받는 컨트롤러의 UI가 변경되었다.

해당 화면 전체는 당연히 모두 테이블 뷰로 이루어진 화면이었으나, 테이블 뷰 하단에 고정된 영역이 필요하다는 것이었다. (스크롤을 해도 영향을 받지않는 fixed view)


구글링을 통해 열심 열심히 알아본결과 해당 테이블 뷰의 Footer영역을 고정시킬 수 있는 방법, 혹은 또 다른 뷰를 넣어 고정시키는 방법 등을 조사했으나, 결론은 둘다 안 됨. 이유는 위에서 설명한 것과 같았다.



그래서 하는 수 없이 새로 뷰 컨트롤러를 생성하여 테이블 뷰 하나와 하단에는 UIView를 넣어 코드를 작성하기 시작했다. 그러나 이미 만들어진 뷰를 다시 재작성하기엔 너무 작업량이 방대했다.


스토리보드에만 작성한 각 Section별 Cell을 모두 커스터마이징하여 각각의 cell로 만들어서 작업해야 했고, UI, Constaraint 등 생각할게 많았다.


그래서 다시 또 열심히 알아본 결과, 기존 static cell을 사용하는 소스코드의 viewDidLoad에 아래의 화면을 추가하고 마무리 지었다.



[소스]

weak var _staticView: UIView?

전역으로 사용할 _staticView를 선언한다.

override func viewDidLoad() { super.viewDidLoad() // bounds : 현재 뷰의 경계 사이즈, origin을 찍으면 0부터 시작 / frame : 현재 뷰의 superview에서 상대, origin을 찍으면 superview에서 위치한 값부터 시작 // tableView의 frame은 self.view.frame과 같음 (테이블 뷰 컨트롤러를 상속받는 뷰 컨트롤러기 때문) // 70 = 50(사이즈) + 20(TopLayoutGuide) let tableViewHeight = self.tableView.frame.height let tableViewWidth = self.tableView.frame.width let staticView = UIView(frame: CGRect(x: 0, y: tableViewHeight - 70, width: tableViewWidth * 0.8, height: 50)) staticView.backgroundColor = UIColor.white let ivQuit = UIImageView(frame: CGRect(x: 0, y: staticView.frame.height - 50, width: 50, height: 50)) ivQuit.image = UIImage(named: "Set_Logout") let ivSetting = UIImageView(frame: CGRect(x: staticView.frame.width - 50, y: staticView.frame.height - 50, width: 50, height: 50)) ivSetting.image = UIImage(named: "Setting_On") ivQuit.isUserInteractionEnabled = true ivSetting.isUserInteractionEnabled = true ivSetting.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(moveSetting))) ivQuit.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(quitView))) staticView.addSubview(ivQuit) staticView.addSubview(ivSetting) self.tableView.addSubview(staticView) // 전역 선언 self._staticView = staticView // contentInset : 현재 스크롤 뷰와 그 안에 있는 콘텐츠 뷰 사이의 거리(top, left, bottom, right) // contentOffSet : 현재 스크롤 뷰 안에 있는 콘텐츠 뷰가 가진 현재 위치(x,y) // 현재 테이블 뷰 의 bottom(3번째 파라미터)의 값을 +80으로 하겠다는 의미(staticView가 올라갔으니 스크롤을 끝까지 했을때 하단 것도 보이기 위함) self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 70, 0) // 스크롤의 위치 (해당 위치만큼 재조정해야 테이블 뷰 내의 스크롤의 위치가 정상적으로 잡힘) self.tableView.scrollIndicatorInsets = self.tableView.contentInset }

staticView라는 UIView는 현재 테이블 뷰의 크기를 기준으로 frame값을 할당하였다.

나 같은경우에는 현재 테이블 뷰 자체가 컨테이너 뷰 안에 들어있어, 컨테이너 뷰 안에 제약조건이 걸린 상태이기 때문에 또 값을 계산하였다 (0.8 곱하기 같은.. 아래 스크린샷 참고)


어쨌든 위의 소스는 필요한 크기만큼의 뷰를 생성하여 테이블 뷰안에 넣었다. 그리고 contentInset을 통해 테이블 뷰 안에들어가는 콘텐츠들의 마진, 거리 값을 bottom부분만 위로 70으로 조정했으며, 그에 따라 스크롤할 때 보이는 우측의 스크롤위치도 조정해주었다.

    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        _staticView?.transform = CGAffineTransform(translationX: 0, y: scrollView.contentOffset.y)
    }
    
    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        self.tableView.bringSubview(toFront: _staticView!)
    }

그리고 스크롤을 할때마다 staticView는 하단에 고정되어야하므로, 현재 스크롤뷰 안에있는 데이터들의 위치 중 y의 값만큼을 staticView가 이동하게 했다.

그리고 아래의 willDisPlay는 cell이 보여질때마다 호출되는 함수로 staticView가 무조건 앞에 나오라는 의미의 코드 한 줄을 추가했다.




** 참고

나의 경우에는 위 처럼 패널을 오픈했을 때, 화면의 80%만을 차지하는 컨테이너뷰와 그 안에 테이블뷰컨트롤러를 상속받는 뷰 컨트롤러를 넣었기 때문에, 아래 staticView의 크기를 계산할 때는 저 화면에 열린 흰색 바탕의 뷰 크기만을 필요로 했다.


그러나 저 바탕 뷰의 경우에는 viewDidLoad에서 얻어낼 수가 없다. 제약조건이 걸린 뷰는 viewDidLoad에선 실제 값을 얻어낼 수 없고 이후, viewDidLayoutSubView라는 함수가 호출되었을 때 알아낼 수 있기 때문이다.


또한 호출된 순서는 viewDidLoad -> tableView기본 함수들(cellForRowAt, willDisplay같은..) -> viewDidLayoutSubView 순서기때문에, 만약 화면이 보여지는 viewDidLoad에서 실제 값을 알고 싶다면 나처럼 viewDidLoad에서 제약조건의 값들을 다시 계산해야하는 번거로움이 있다.


viewDidLayoutSubView에서 코드를 넣으면 tableView기본 함수들에서 _staticView가 뭐냐고 에러를 뱉기 때문에 사용할 수 없었다.