UIScrollView는 iOS에서 스크롤 가능한 영역을 제공하는 컴포넌트이다. 화면에 표시되는 영역보다 큰 콘텐츠를 표시할 때 사용된다. 수직, 수평 또는 두 방향 모두 스크롤할 수 있다.
스크롤이 가능한 여러 UIKit의 클래스들은 UIScrollView를 슈퍼클래스로 가지고 있다.
(UITableView, UITextView, UICollectionView 등이 있음)
스크롤뷰의 메커니즘
콘텐츠 뷰 위에서 조절 가능한 원점을 가진 뷰이다. 사용자의 터치 움직임을 추적하고 원점을 그에 맞게 조절한다.
콘텐츠 뷰의 크기를 알아야 스크롤을 언제 멈출지 알아야 한다. 콘텐츠의 경계를 초과하면 스크롤이 바운스 된다.
최적화를 위해 타일링 한다. 화면에 보이는 부분만 작은 타일로 나누어 렌더링 하고, 사용자가 스크롤하면 필요한 새로운 타일이 렌더링 된다, 더 이상 보이지 않는 타일은 제거된다.
확대/축조 동작은 사용자가 핀치 인/아웃 제스처를 할 때, 스크롤뷰는 콘텐츠의 Offset과 Scale을 조정한다.
UIScrollViewDelegate 프로토콜에 의해 선언된 델리게이트 메소드는 UIScrollView의 여러 메시지에 응답한다. 특히 viewForZooming(in:) 같은 메서드를 구현하지 않으면 콘텐츠를 스크롤할 수 없다.
사용자 입력이 스크롤인지 다른 터치 이벤트인지 타이머 설정, 관찰에 의해 확인하고 동작한다. 짧은 타이머를 시작하고 그 시간동안 손가락이 크게 움직이는지 아닌지 확인하여 스크롤과 터치 이벤트를 구분하고 적절하게 처리한다.
restorationIdentifier 속성에 값을 할당하면 앱 실행 사이에 스크롤 관련 정보를 보존하려고 시도한다.
restorationIdentifier?
UIView의 속성 중 하나로, 이 속성에 값을 할당하면 해당 뷰의 상태를 보존하려고 한다. 즉 백그라운드로 이동하거나, 종료된 후에도 상태를 유지하려는 시도를 의미한다. 즉 다시 포그라운드로 돌아오거나 재시작될 때 스크롤의 위치나, 확대, 축소 값들을 유지하려고 한다.(UIScrollView의 예시)
UIScrollView의 동작 방식을 알아보았다. UIScrollView는 관련 메서드나 프로퍼티가 굉장히 많다. 그래서 이것들을 한번씩 확인하는 것만으로도 UIScrollView를 이해하고 사용하는데 도움이 된다. 관련 메서드와 프로퍼티는 공식문서에 잘 나와있고, 네이버부스트코스 iOS프로그래밍에 잘 정리되어 있다. 내용을 참고하여 정리해 보겠다.
프로퍼티
// contentSize: 콘텐츠뷰의 크기
var contentSize: CGSize { get set }
// contentOffset: 콘텐츠뷰의 오프셋(원점으로부터의 간격)
var contentOffset: CGPoint { get set }
메서드
// contentSize: 콘텐츠뷰의 크기
var contentSize: CGSize { get set }
// contentOffset: 콘텐츠뷰의 원점이 스크롤뷰의 원점에서 오프셋 된 지점
var contentOffset: CGPoint { get set }
프로퍼티
// contentInset: 콘텐츠뷰와 안전 영역 또는 스크롤뷰 가장자리 간격
var contentInset: UIEdgeInsets { get set }
: 주로 스크롤뷰의 내용이 상태 바, 탭 바, 내비게이션 바와 같은 UI 요소 아래나 위에 숨겨지지 않도록 상, 하, 좌, 우 간격 설정
프로퍼티
// 스크롤 기능 활성화 비활성화 결정
var isScrollEnabled: Bool { get set }
// 한 번에 하나의 방향으로만 스크롤하는 것을 결정 (true: 수평 또는 수직 유지하면서 이동)
var isDirctionalLockEnabled: Bool { get set }
// 페이지처럼 스크롤뷰의 내용을 스크롤하게 됨
var isPagingEnabled: Bool { get set }
// 상태 탭바 부분은 탭할 때 스크롤 뷰의 내용이 최상단으로 자동 스크롤 되는지 결정
var scrollsToTop: Bool { get set }
// 내용이 경계를 넘었을 때 바운스효과 여부 결정
var bounces: Bool { get set }
// 수직 방향으로 스크롤 시 항상 바운스 효과 발생할지 결정
var alwaysBounceVertical: Bool { get set }
// 수평 방향으로 스크롤 시 항상 바운스 효과 발생할지 결정
var alwaysBounceHorizontal: Bool { get set }
프로퍼티
// 사용자가 스크롤 뷰의 콘텐츠를 터치하여 스크롤을 시작했는지 여부
var isTracking: Bool { get }
// 사용자가 콘텐츠를 스크롤하고 있는지 여부
var isDragging: Bool { get }
// 사용자가 손가락을 화면에서 뗀 후 아직 스크롤이 움직이고 있는지 여부
var isDecelerating: Bool { get }
// 사용자가 손가락을 뗀 후 스크롤 감속 속도를 나타내는 부동소수 값
var decelerationgRate: CGFloat { get set }
타입
// 스크롤 뷰가 감속하는 속도를 나타내는 상수값이 정의되어 있음(.normal, .fast)
struct UIScrollView.DecelerationRate
프로퍼티
// 스크롤 인디케이터 스타일
var indicatorStyle: UIScrollViewIndicatorStyle { get set }
// 가로 스크롤 인디케이터 표시 여부를 제어하는 부울 값
var showsHorizontalScrollIndicator: Bool { get set }
// 세로 스크롤 인디케이터 표시 여부를 제어하는 부울 값
var showsVerticalScrollIndicator: Bool { get set }
// 콘텐츠의 특정 위치로 스크롤하여 화면에 표시
func scrollRectToVisible(_ rect: CGRect, animated: Bool)
프로퍼티
// 패닝 제스처에 대한 기본 제스처 인식기(두 손가락으로 스크린을 드래그)
var panGestureRecognizer: UIPanGestureRecognizer { get }
// 핀치 제스처에 대한 기본 제스처 인식기(두 손가락으로 확대/축소)
var pinchGetsureRecognizer: UIPinchGestureRecognizer? { get }
// 콘텐츠에 현재 적용된 확대/축소 비율을 나타내는 값
var zoomScale: CGFloat { get set }
// 콘텐츠에 적용할 수 잇는 최대 확대 비율을 나타내는 값
var maximumZoomScale: CGFloat { get set }
// 콘텐츠에 적용할 수 있는 최소 확대 비율을 나타내는 값
var minimumZoomScale: CGFloat { get set }
// 확대/축소가 지정된 확대/축소 한도를 초과했는지 여부를 나타내는 값
var isZoomBouncing: Bool { get }
// 콘텐츠 뷰가 현재 확대, 축소 중인지 여부를 나타내는 값 (확대 중이거나 축소 중이면 true)
var isZooming: Bool { get }
// 확대/축소가 지정된 확대/축소 한도를 초과했을 때 바운스 효과를 적용할지 나타내는 값
var bouncesZoom: Bool { get set }
메서드
// 특정 영역으로 확대하여 그 영역이 보이게 함
func zoom(to rect: CGRect, animated: Bool)
// 현재 확대/축소 비율을 설정
func setZoomScale(_ scale: CGFloat, animated: Bool)
// 스크롤뷰에서 드래그가 시작될 때 키보드가 해제
var keyboardDismissMode: UIScrollViewKeyboardDismissMode { get set }
// 사용자가 콘텐츠를 스크롤할 때마다 호출
optional func scrollViewDidScroll(_ scrolView: UIScrollView)
// 콘텐츠를 스크롤하기 시작할 때 호출
optional func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
//사용자가 스크롤 동작을 끝냈을 때 호출. 스크롤의 속도와 스크롤이 멈추게 될 예상 위치를 파라미터로 제공
optional func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>)
// 드래그 동작이 스크롤 뷰에서 끝났을 때 호출. willDecelerate 파라미터는 스크롤 동작이 점점 느려져서 멈출 것인지 여부
optional func scrollViewDidEndDragging(_ scrollView: UIScrollView,
willDecelerate decelerate: Bool)
// 스크롤 뷰가 최상단으로 스크롤되기 직전(상태바 탭)에 호출. true반환 시 최상단스크롤 됨, false반환 시 안됨
optional func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool
// 콘텐츠의 최상단으로 스크롤된 후 호출
optional func scrollViewDidScrollToTop(_ scrollView: UIScrollView)
// 스크롤링 동작이 감속되기 시작할 때 호출
optional func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView)
// 스크롤링 동작이 감속이 끝나고 완전히 멈췄을 때 호출
optional func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
// 스크롤뷰에서 확대 및 축소를 할 때 확대 및 축소할 뷰 인트턴스 요청(구현 안 하거나 nil 반환 시 확대 축소 못함)
optional func viewForZooming(in scrollView: UIScrollView) -> UIView?
// 스크롤뷰의 콘텐츠 확대, 축소가 시작될 때 호출
optional func scrollViewWillBeginZooming(_ scrollView: UIScrollView,
with view: UIView?)
// 스크롤뷰의 콘텐츠 확대가 완료될 때 호출
optional func scrollViewDidEndZooming(_ scrollView: UIScrollView,
with view: UIView?,
atScale scale: CGFloat)
// 스크롤뷰의 확대 및 축소 배율이 변경될 때마다 호출
optional func scrollViewDidZoom(_ scrollView: UIScrollView)
// 스크롤 애니메이션이 완료될 때 호출. setContentOffset(_:animated:)의 animated 파라미터에 true 값을 전달한 경우 애니메이션이 완료되면 호출
optional func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView)
// inset값이 변경될 때 호출
func scrollViewDidChangeAdjustedContentInset(UIScrollView)
정확한 사이즈 설정: 콘텐츠 사이즈가 뷰의 크기와 같거나 더 작게 설정되면 스크롤을 할 수 없다. 반대로 너무 스케 설정하면 빈 공간을 스크롤해야 하는 문제가 발생한다.
사이즈 동적 변경: 앱의 콘텐츠가 동적으로 변경되는 경우 콘텐츠의 크기나 레이아웃이 바뀔 때마다 'contentSize'를 적절히 업데이트해야 한다.
Zoom 요구 사항: Delegate 메서드 구현, 적절한 최소 및 최대 줌 스케일 설정 필요
UIScrollView는 복잡한 UI레이아웃이나 큰 이미지, 긴 텍스트 등을 표시할 때 유용하다. 특히 사이즈가 비교적 크지 않은 모바일 기기의 경우 큰 콘텐츠를 화면에 모두 담기 어렵다. 이런 경우에 UIScrollView를 활용하여 문제를 극복할 수 있다.
UIScrollView와 관련된 프로퍼티와 메서드가 상당히 많았는데 하나하나 살펴보면 자연스럽게 스크롤뷰에 대한 이해가 가능하다. 다양한 예제를 통해 익혀보도록 하겠다.