📱 iOS 플젝 개발일지

[iOS/Swift] UIScrollview 원리 / 코드로 구현하기 (UIScrollView Programatically/snapkit)

dev_zoe 2022. 11. 21. 17:39
반응형

(사이드 프로젝트에서 스크롤뷰로 삽질을 정말 많이해서 정리해보는 포스트....😠)

 

스크롤뷰 구현의 핵심을 정리해보면 다음과 같습니다.

 

1. contentView의 constraint는 scrollView 사면에 맞추기 (top, leading, trailing, bottom -> equal to scrollView)

2. view에 scrollView를 추가하고, scrollView에 contentView 추가 및 contentView에 뷰를 구성하는 요소들을 추가한다.

3. 세로 스크롤뷰의 경우 contentView의 width는 scrollView의 width와 동일하게, 가로 스크롤뷰의 경우 contentView의 height을 scrollView의 height과 동일하게 맞춘다.

4. contentView 안에 있는 구성요소들의 constraint는 superview인 contentView에 맞춘다.

5. 레이아웃 상 스크롤뷰의 맨 마지막에 위치하는 뷰는 반드시 contentView의 bottom에 constraint를 주어야한다.

6. 이게 정말 중요한데, 1-4번을 다 했음에도 스크롤이 되지 않다면 scrollView가 스크롤할 수 있는 size(=contentSize)가 명확한지를 검토해볼 필요가 있다.

필자는 이부분에서 삽질을 정말 많이했는데, 이해하기 쉽게 공식문서 및 그림으로 설명해보겠습니다.

 

https://developer.apple.com/documentation/uikit/uiscrollview

해당 공식문서를 보면, 이러한 내용이 나와있습니다.

그림이 구린건... 양해 부탁드립니다 ^^..

✅ 스크롤뷰란, 컨텐츠 뷰에서 조정할 수 있는 원점이 있는 보기(액자라고 생각하면 쉬울듯)입니다.

그림에서 그린것같이 세로 스크롤뷰의 경우, 세로로 긴 뷰가 있으면 터치에 따라 움직이면서 내용을 보여주는 액자?같은 친구가 스크롤뷰입니다.

✅ 스크롤뷰는 반드시 스크롤을 멈추는 시점에 대해서 알아야하기 때문에, 스크롤뷰 자신의 컨텐츠의 사이즈(contentSize)를 알아야합니다. <- 이게 중요

즉, 스크롤뷰의 contentSize를 절댓값으로 주든 autolayout으로 하든간에 스크롤을 어디까지 할 수 있는지?에 대한 부분이 명확해야합니다.

 

제가 사이드프로젝트 했을 때의 내용을 예를들자면

여기서 다음 버튼을 위 '개인정보처리방침' 라벨과 constraint를 맞추지 않고,

contentView의 leading, trailing, bottom으로만 맞추었더니 스크롤이 되지 않았습니다.

 

원인은, 회색 다음 버튼을 아래에만 지정하면 스크롤뷰 contentSize의 세로 길이가 모호하다는 점입니다.
'어디까지 스크롤해야되는데?, 스크롤해야되는 영역이 어디까지인데?' 가 명확하지 않습니다.

(라벨과 버튼간의 간격을 모르기때문에, 전체 스크롤할 수 있는 세로의 길이가 모호하게됨)

 

반면에 다음버튼을 개인정보 처리방침 약관 동의의 bottom에 다음버튼의 top을 제약을 주었더니, 스크롤이 되었습니다.

스크롤할 수 있는 전체 사이즈가 명확해졌기 때문입니다.

 

이제 핵심을 정리했다면, 또 케이스 별로 나눌 수 있습니다.

테이블뷰/컬렉션뷰는 내용물에 따라 크기가 달라지므로, 이에 따라 scrollView의 contentSize가 매우 달라지기때문에

이부분에 대해서 별도로 처리할 필요가 있습니다.

 

1. 스크롤뷰 안에 테이블뷰/컬렉션뷰가 없는 경우

    private var scrollView = UIScrollView(frame: .zero).then { view in
        view.showsHorizontalScrollIndicator = false
        view.showsVerticalScrollIndicator = false
        view.translatesAutoresizingMaskIntoConstraints = false //auto로 뷰의 크기와 위치를 지정하는 것을 방지하여 직접 constraint를 지정하도록하기 위함!
    }
    
    private var contentView = UIView().then { view in
        view.translatesAutoresizingMaskIntoConstraints = false
    }
        view.addSubview(scrollView)
        
        scrollView.addSubview(contentView)
        
        contentView.addSubView(뷰 구성요소들 다 추가)
        scrollView.snp.makeConstraints { make in
            make.top.equalTo(view.snp.top))
            make.leading.equalTo(view.snp.leading)
            make.trailing.equalTo(view.snp.trailing)
            make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
        }
        
        //scrollView의 경우 꼭 화면에 꽉차게 하지 않아도 됩니다! 화면에 따라서 유동적으로 constraint를 맞춰주세요!
        
        contentView.snp.makeConstraints{ make in
            make.top.equalTo(scrollView.snp.top)
            make.leading.equalTo(scrollView.snp.leading)
            make.trailing.equalTo(scrollView.snp.trailing)
            make.bottom.equalTo(scrollView.snp.bottom)
            make.width.equalTo(scrollView.snp.width) //스크롤뷰를 vertical로 만들고싶을때, 가로 고정
        }
...생략
//화면의 맨 아래 요소는 반드시 bottom constraint를 주어야합니다.
	nextButton.snp.makeConstraints{ make in
            make.top.equalTo(seePrivacyLabel.snp.bottom).offset(57)
            make.leading.equalTo(contentView.snp.leading)
            make.trailing.equalTo(contentView.snp.trailing)
            make.bottom.equalTo(contentView.snp.bottom).offset(-42) //이부분이 중요 -> 스크롤이 끝나는 부분을 알려주어야함
        }

 

2. 스크롤뷰 안에 테이블뷰/컬렉션뷰가 있는 경우

a. 스크롤뷰 사용 X

-> 테이블뷰 헤더 활용

 

tableview/collectionview가 있으면서 위에 네비게이션 바나 다른 뷰가 있는데, 한꺼번에 스크롤이 되게하고싶다면 tableview header를 활용하면 간단하게 테이블뷰/컬렉션뷰를 포함한 뷰 전체가 스크롤되는 것처럼 구현할 수 있습니다.

 

reference : https://stackoverflow.com/questions/43314864/i-want-a-uitableview-inside-of-a-uiscrollview-to-extend-to-the-bottom-of-the-uis

 

 

b. 스크롤뷰 사용 O

 

1) 값이 미리 정해져있는 경우 (tableView의 컨텐츠 사이즈가 정적임) : 기존과 동일

2) tableview/collectionview의 contentSize가 동적일 경우 (셀의 갯수가 유동적일 경우)

내용이 바뀔때마다 TableView/CollectionView의 높이를 contentSize.height으로 업데이트해주어야합니다.

(self.view.layoutIfNeeded() 메소드는 뷰의 변화가 생겼을 때 이를 명시적으로 반영해주는 메소드)

 

반응형