ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • UICollectionView서 헤더로 다이나믹하게 높이 계산하는 로직 (UITextView) 넣기
    iOS 2018. 7. 25. 15:09


    개인적으로 파는 소스에서 콜렉션뷰를 짜는데, 헤더 부분에 텍스트뷰가 있어서 글을 쓰면 쓰는 만큼 늘어나는 레이아웃이 필요하게 되었습니다.


    안드로이드라면 퍽 쉽게 가능한데... iOS는 굉장히 복잡하단 말이죠..


    몇 번의 검색과 삽질, 연구 끝에 나름대로 구현.


    기본 높이 (50)을 주되, 그 이상 늘어나면 헤더의 높이가 리사이징됩니다.



    이게 예제.






    import Foundation

    import UIKit

    import SnapKit


    class TestEditorViewController  : UIViewController, UICollectionViewDelegateFlowLayout{

        

        lazy var collectionView : UICollectionView = {

            let layout = UICollectionViewFlowLayout()

            layout.headerReferenceSize = CGSize(width: view.frame.width, height: 50)

            let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)

            cv.backgroundColor = UIColor.white

            cv.dataSource = self

            cv.delegate = self

            return cv

        }()

        let cellId = "cellId"

        let headerId = "headerId"

        func setUpView(){

            view.addSubview(collectionView)

            collectionView.snp.makeConstraints { (make) in

                make.top.equalTo(topLayoutGuide.snp.bottom)

                make.leading.equalTo(view)

                make.trailing.equalTo(view)

                make.bottom.equalTo(bottomLayoutGuide.snp.top)

            }

            collectionView.register(TextEditCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId)

            collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)

        }

        override func viewDidLoad() {

            setUpView()

            setUpNavigationBar()

        }

        func setUpNavigationBar(){

            self.navigationController?.navigationBar.isTranslucent = false

            self.navigationItem.title = "글쓰기"

            let dissmissButon = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.cancel, target: self, action: #selector(actionDismiss))

            self.navigationItem.leftBarButtonItem = dissmissButon

        }

        @objc func actionDismiss(sender : Any){

            self.dismiss(animated: true, completion: nil)

        }

    }

    extension TestEditorViewController : UICollectionViewDelegate {

        

        

        

        func resizeCollectionView(height : CGFloat){

            if let layout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {

                //

                layout.headerReferenceSize = CGSize(width: view.frame.width, height: max(height, 200))

                self.collectionView.layoutIfNeeded()

                

                

            }

        }

    }


    extension TestEditorViewController : UICollectionViewDataSource {

        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

            return 5

        }

        

        

        func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

            let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! TextEditCell

            header.textView.delegate = self

            

            return header

        }

        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)

            cell.backgroundColor = UIColor.red

                return cell

           

        }

    }


    extension TestEditorViewController : UITextViewDelegate {

        func textViewDidChange(_ textView: UITextView) {

            print(textView.text)

            let size = CGSize(width: view.frame.width, height: CGFloat.infinity)

            let estimatedSize = textView.sizeThatFits(size)

            if let layout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {

                layout.headerReferenceSize = CGSize(width: view.frame.width, height: max(estimatedSize.height, 50))

                self.collectionView.layoutIfNeeded()

            }

        }

    }


    뷰컨트롤러가  UICollectionViewDelegateFlowLayout
    을 상속하는 것에 주목합니다.

      
    layout.
    headerReferenceSize = CGSize(width: view.frame.width, height: 50)

    에서 헤더뷰의 사이즈를 직접 정해주는 것에 주의합니다.

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {

            <#code#>

        }


    을 구현해서 여기서 헤더뷰의 사이즈를 정해주면, 다이나믹한 헤더 변경이 가능하지 않으니

    오버라이딩 하지 않도록 주의합니다.


    /**

     텍스트 에디팅

     */

    class TextEditCell : BaseCell{

        let placeholder = "무슨 일이 일어나고 있나요?"

        let textView : UITextView = {

            let tv = UITextView()

            tv.isScrollEnabled = false

            tv.font = UIFont.systemFont(ofSize: 16)

            tv.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10)

            return tv

        }()

        let placeHolderLabel : UILabel = {

            let label = UILabel()

            label.text = "무슨 일이 일어나고 있나요?"

            label.font = UIFont.systemFont(ofSize: 16)

            label.textColor = UIColor.lightGray

            label.sizeToFit()

            return label

        }()

        var content : TextEditContent? {

            didSet {

                textView.text = content?.text

            }

        }

        override func setupViews() {

            self.addSubview(textView)

            textView.delegate = self

            textView.snp.makeConstraints { (make) in

                make.top.equalTo(self)

                make.trailing.equalTo(self)

                make.leading.equalTo(self)

                make.bottom.equalTo(self)

            }

            textView.addSubview(placeHolderLabel)

            placeHolderLabel.frame.origin = CGPoint(x: 15, y: 10 )

            self.textViewDidChange(textView)

            

        }

        

    }


    해당 콜렉션 셀입니다. 제약 조건에 높이를 달지 않고, 그냥 뷰에 꽉 텍스트뷰를 채우도록 했습니다.


     func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

            let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) asTextEditCell

            header.textView.delegate = self

            

            return header

        }


    에서 헤더뷰의 텍스트뷰의 델리게이션을 구현합니다.

    extension TestEditorViewController : UITextViewDelegate {

        func textViewDidChange(_ textView: UITextView) {

            print(textView.text)

            let size = CGSize(width: view.frame.width, height: CGFloat.infinity)

            let estimatedSize = textView.sizeThatFits(size)

            if let layout = self.collectionView.collectionViewLayout asUICollectionViewFlowLayout {

                layout.headerReferenceSize = CGSize(width: view.frame.width, height: max(estimatedSize.height50))

                self.collectionView.layoutIfNeeded()

            }

        }

    }


    텍스트의 크기가 바뀔 때마다 알맞은 하이트를 계산하고, 레이아웃의 헤더 레퍼런스 사이즈를 수정합니다.

    그리고 self.collectionView.layoutIfNeeded() 을 호출하면 텍스트뷰의 사이즈가 알맞게 늘어나는 것을 확인할 수가 있습니다.


    여기선 편의상 하이트를   max(estimatedSize.height, 50) 로 두어서, 아무리 텍스트 사이즈가 적어도 높이가 50보단 줄어들진 않습니다.




Designed by Tistory.