본문 바로가기
Xcode/IOS

IOS) 동적인 TableView Cell을 만드는 방법

by 후르륵짭짭 2020. 12. 12.
728x90
반응형

 

안녕하세요 후르륵짭짭이 입니다.

이번에는 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

 

UITableView: How to change cell height dynamically when a button is clicked in it? Swift

I can tell how to do this in objective-c from here, but how can I convert that to swift? I have a UITableView with custom TableViewCells that has a UIButton called "expandButton". I am trying to f...

stackoverflow.com

 

특정 셀의 크기를 변동하는 방법에 대한 설명 :

developer.apple.com/forums/thread/120128

 

UITableView.reloadRows does not re… | Apple Developer Forums

I created a tableview using custom cell. I want reload the target cell, let's say [section = 0, row = 0], and use the UITableView.reloadRows method. let indexPath = IndexPath.init(row: 0, section: 0) self.tableView.reloadRows(at: [indexPath], with: .fade)

developer.apple.com

 

Cell과 IBOutlet을 연결해야하는데 실수로 ViewController에 연결 했을 때 등장하는 오류 :

stackoverflow.com/questions/26561461/outlets-cannot-be-connected-to-repeating-content-ios

 

Outlets cannot be connected to repeating content iOS

I have just created an app and have started hooking up @IBOutlet's to the storyboard. I am connecting some of them to labels in a UITableViewCell Prototype Cell with a Basic Style. When I connect it

stackoverflow.com

 

TableView layoutIfNeed() 사용하는 방법 :

stackoverflow.com/questions/30692417/layoutifneeded-affecting-table-view-cells

 

layoutIfNeeded affecting table view cells

In my view controller I have a table view as a subview. When the keyboard disappears, I'm using self.view.layoutIfNeeded() to animate the table view. This goes all well except for the table view ce...

stackoverflow.com

 

TableView Cell 안에 Tableview를 넣는 방법 :

stackoverflow.com/a/35855258/13065642

 

create tableview inside tableviewcell using swift

well i create tableview and inside of it tableview cell with xib file now i want create tableview inside xib file , the main problem is that u can't create cell inside of it to define the identifie...

stackoverflow.com

 

TableView Cell의 automaticDimension과 estimated : 

m.blog.naver.com/PostView.nhn?blogId=jdub7138&logNo=220963701224&proxyReferer=https:%2F%2Fwww.google.co.kr%2F

 

[iOS Swift] TableView 유동적인 행 높이 지정하기

같은 Cell - 다른 높이같은 Prototype Cell을 사용하지만 각 Row마다 높이가 다르도록 적용시키고 싶을...

blog.naver.com

 

automaticDimension과 estimated를 알게된 사이트 : 

stackoverflow.com/a/36574367/13065642

 

Swift dynamic table cell height

I have tableview with prototype cell, in the cell I have imageview and some text under. The text label is one laine in the prototype cell, but sometimes it is more than one line, I'm loading the ...

stackoverflow.com

 

Autolayout 적용으로 Contraint 충돌이 발생 했을 때 확인하는 방법 :

littleshark.tistory.com/50

 

빠르게 레이아웃 깨지는 이슈를 파악하는 방법

iOS 앱을 개발하다보면 몇번 겪어보셨을 만한 레이아웃 깨지는 이슈를 빠르게 확인하는 방법을 공유하고자 합니다. 보통 앱을 개발할 때 기준단말을 정해놓고 나중에 그 이외의 단말에 대해 UI를

littleshark.tistory.com

 

728x90
반응형

댓글