안녕하세요 후르륵짭짭이 입니다.
이번에는 TableView Cell을 동적으로 만드는 방법에 대해 알아보려고 합니다.
항상 동적인 Cell을 어떻게 하면 개발할 수 있을지 궁금했는데,,, 이번에 좋은 기회를 잡게 돼서 올려보려고 합니다.
그리고 TableViewCell 안에 TableView를 넣는 것 까지 다뤄보도록 하겠습니다.
** StoryBoard로 구현하기 **
일단 저의 스토리보드는 이렇게 구성되어 있습니다.
일단 MainCell은 코드로 크기가 조절 됩니다.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(Cell의 크기)
}
따라서 MainCell의 크기는 변할 수 있습니다.
이 부분을 이용해서 StoryBoard로 만들 수 있게 됐습니다.
- MainCell 셀 내용 구성하기 -
Cell 부분에 (1)과 (2)을 고정된 값으로 주었습니다. (AutoLayout을 적용했습니다.)
(1)은 높이가 150이고 MainCell과 상좌우의 constraint를 0으로 주었습니다.
(2)은 (1)뷰와 Top Constraint를 주고 MainCell과 좌우하를 Constraint를 0으로 주었습니다.
이렇게 하면 MainCell의 크기가 변할 때 (1)은 크기가 유지 되지만 (2)은 그렇지 않고 MainCell의 크기에 따라서 달라지게 됩니다.
- (2)번 셀에 내용 넣기 -
이렇게 유동적으로 변하는 (2)View를 구성했으니, 이제 안에다가 내용물을 넣겠습니다.
(2)셀은 크기가 유동적으로 변한다는 것을 고려해야합니다.
따라서 크기가 유동적으로 변화하고 그에 맞게 자동으로 내용물을 조절해주는 StackView를 넣었습니다.
( 물론 View 두개를 사용할 수도 있습니다. 하지만 이럴 경우 Constraint를 코드로 조절해줘야하는데,,, 전 이부분이 부족했습니다.
Constraint.content = 50 이렇게 줬을 경우, MainCell의 크기를 벗어나는 경우가 생기고 ViewController의 하위 뷰이기 때문에 아무래도
layoutIfNeeded()를 사용해도 바로 적용이 되지 않았습니다.... 이 부분은 좀더 고려해봐야 할 것 같습니다.)
StackView를 (2)View의 상하좌우 Constraint를 줍니다. 이러면 (2)View의 크기가 변할 때 StackView 크기도 동적으로 변하게 됩니다.
- StackView 내용 구성하기 -
위에서 StackView는 MainCell의 크기에 따라서 유동적으로 변한다고 설명했습니다.
이제 StackView 안에 두개의 뷰를 넣어줍니다.
그런데 두개의 뷰를 넣었을 때, (4)과 (5)의 크기가 정해져 있지 않기 때문에 Error가 나올 겁니다.
우리는 (4)의 높이를 40으로 고정해주면 나머지는 StackView가 고려해서 크기를 정해줍니다.
즉 (5)View는 높이가 정해져 있지 않기 때문에 유동적으로 변하게 됩니다.
** Code로 버튼을 눌렀을 때 작동하기 **
(4)번 View 내부에 버튼을 하나 넣어줍니다.
그리고 해당하는 Cell에 IBOutlet으로 버튼을 연결 시켜주고
// MainTableViewCell
//ViewController로 보내줄 Closure
var expandHandler : ((Bool)->())?
//Open or Close 인지 판별해주는 프로퍼티
var selectedBtn : Bool = false
@IBAction func expandViewBtn(_ sender: UIButton) {
selectedBtn = !selectedBtn
expandHandler?(selectedBtn)
}
// MainViewController
//Open or Close 선택된 indexPath를 저장하는 변수
var selectedCellPath: [IndexPath] = []
override func viewDidLoad() {
super.viewDidLoad()
mainTableView.estimatedRowHeight = 190
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = mainTableView.dequeueReusableCell(withIdentifier: "MainCell") as? MainTableViewCell else {return UITableViewCell()}
cell.expandHandler = { [weak self] (response) in
if response {
self?.selectedCellPath.append(indexPath)
}
else{
self?.selectedCellPath = self!.selectedCellPath.filter({ (index) -> Bool in
return index != indexPath
})
}
self?.mainTableView.reloadRows(at: self!.selectedCellPath, with: .none)
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if !selectedCellPath.isEmpty && selectedCellPath.contains(indexPath){
return 280
}
return UITableView.automaticDimension
}
이제 코드를 보도록 하겠습니다.
reloadRows 함수를 사용하면 특정 Cell로 이동해서 그 부분을 수정 해줍니다.
self?.mainTableView.reloadRows(at: self!.selectedCellPath, with: .none)
따라서 저는 높이만 변동하기 위해
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if !selectedCellPath.isEmpty && selectedCellPath.contains(indexPath){
return 280
}
return UITableView.automaticDimension
}
이렇게 사용했습니다.
이렇게 되면 각 Cell이 저장된 selectedCellPath 안에 현재 해당하는 Cell이 존재하면 높이를 변하게 해주고
그렇지 않으면 원래대로 돌아가게 합니다.
여기서 중요한 것은 원래대로 돌아갈 때 automaticDimension을 사용한다는 것 입니다.
automaticDimension은 동적으로 높이가 재설정 된다는 것을 알려주게 됩니다.
그리고 우리가 viewDidLoad()에
mainTableView.estimatedRowHeight = 190
이렇게 estimatedRowHeight를 지정해주면 예측되는 값과 비슷하게 높이가 저정이 됩니다.
이렇게 하면 View들의 충돌을 줄일 수 있습니다.
(참고로 automaticDimension을 사용하기 위해서는 Cell에 Constraint를 지정해줘야 합니다!!!)
만약에 Cell 자체의 뷰를 변경하고 싶다면 willDisplay를 사용해주면 됩니다.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {..}
** Cell 내부에 TableView를 넣는 방법 **
이렇게 StackView안에 (5)번 View안에 TableView를 넣어주시고
우리가 만든 MainCell 안에 TableView를 IBOutlet으로 넣어주세요
class MainTableViewCell: UITableViewCell {
@IBOutlet weak var insideTableView: UITableView!
var selectedBtn : Bool = false
var expandHandler : ((Bool)->())?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
//storyBoard에서도 Nib으로 생성되는 것과 마찬가지의 역할을 한다.
//따라서 내부에 tableView를 만들게 될 때 이렇게 호출해도 된다.
setInsideTableViewDelegate()
}
@IBAction func expandViewBtn(_ sender: UIButton) {,,,}
}
extension MainTableViewCell : UITableViewDelegate , UITableViewDataSource {
func setInsideTableViewDelegate(){
insideTableView.delegate = self
insideTableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = insideTableView.dequeueReusableCell(withIdentifier: "InsideCell") as? InsideTableViewCell else {return UITableViewCell()}
cell.backgroundColor = .red
return cell
}
}
그리고 위에 처럼 awakeFromNib() 안에 Delegate를 설정해주시기 바랍니다.
( Nib은 XIB 파일로 생성할 때만 작동하는 줄 알았는데, StroyBoard로 생성해도 awakeFromNib() 함수가 호출 되었습니다.
따라서 Cell의 ViewdidLoad()와 같은 역할을 하는 것 같습니다. )
이렇게 해주시면 TableView Cell 안에 TableView를 생성 할 수 있습니다.
지금 까지 TableView를 동적으로 크기 변동하는 방법과 Cell 안에 TableView를 넣는 방법에 대해서 공부해봤습니다.
모두 즐코딩 하세요
참고 사이트:
처음으로 reloadRows()함수를 찾은 부분 :
stackoverflow.com/q/35515597/13065642
특정 셀의 크기를 변동하는 방법에 대한 설명 :
developer.apple.com/forums/thread/120128
Cell과 IBOutlet을 연결해야하는데 실수로 ViewController에 연결 했을 때 등장하는 오류 :
stackoverflow.com/questions/26561461/outlets-cannot-be-connected-to-repeating-content-ios
TableView layoutIfNeed() 사용하는 방법 :
stackoverflow.com/questions/30692417/layoutifneeded-affecting-table-view-cells
TableView Cell 안에 Tableview를 넣는 방법 :
stackoverflow.com/a/35855258/13065642
TableView Cell의 automaticDimension과 estimated :
automaticDimension과 estimated를 알게된 사이트 :
stackoverflow.com/a/36574367/13065642
Autolayout 적용으로 Contraint 충돌이 발생 했을 때 확인하는 방법 :
'Xcode > IOS' 카테고리의 다른 글
IOS) TableVIew의 Pagination을 구현해보자 (0) | 2020.12.17 |
---|---|
IOS) TableView의 Section을 다뤄보자 (0) | 2020.12.13 |
IOS) Timer를 이용한 UI 처리를 배워보자 (0) | 2020.12.04 |
IOS) Local Notification을 이용해보자 2부 (0) | 2020.11.25 |
IOS) Local Notification을 이용해보자 1부 (0) | 2020.11.22 |
댓글