ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • UIScrollView의 contentInsetAdjustmentBehavior
    iOS 2024. 7. 18. 08:15

    안녕하세요. 그린입니다 🍏

    이번 포스팅에서는 UIScrollView의 contentInsetAdjustmentBehavior에 대해 알아보고 적용해보려 합니다 🙋🏻

     


    contentInsetAdjustmentBehavior?

    우선 UIKit 프레임워크에서 제공하는 UIScrollView의 인스턴스 속성인 contentInsetAdjustmentBehavior를 공식문서를 살펴보면 조정된 컨텐츠 오프셋을 결정하기 위한 동작이라고 소개하고 있어요 🤔

     

    조정된 컨텐츠 오프셋??

     

    이게 대체 뭔말일까요 🤔

     

    해당 인스턴스 속성은 UIScrollView의 get set 프로퍼티로 아래와 같이 정의되어 있습니다.

     

    var contentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior { get set }

     

    즉, 해당 값을 우리가 지정해서 사용해줄 수 있다는 소리겠죠?

     

    타입은 ContentInsetAdjustmentBehavior이라는 열거 타입을 가집니다.

     

    enum ContentInsetAdjustmentBehavior : Int, @unchecked Sendable

     

    조정된 컨텐츠 인셋에 safeArea 인셋이 추가되는 방식에 대해 케이스로 정의해뒀다고 볼 수 있죠.

     

    즉, 조금씩 단어들이 추가되고 알 수 있네요?

     

    스크롤 뷰를 사용하여 컨텐츠를 담을때 디바이스별 세이프 에어리어 인셋이 존재하는데, 이것에 대해 조정을 할지 한다면 어떻게 할지에 대해 말하는 느낌이군요.

     

    해당 케이스는 4가지가 존재합니다.

     

    1️⃣ automatic - 스크롤 뷰 인셋을 자동으로 조정

    2️⃣ scrollableAxes - 스크롤 가능한 방향으로만 인셋을 조정

    3️⃣ never - 스크롤 뷰 인셋을 조정하지 않음

    4️⃣ always - 컨텐츠 조정에 항상 세이프 에어리어 인셋을 포함

     

    기본값은 automatic이에요.

     

    즉, 우리가 앞으로 실습해볼것에서도 이 4가지의 케이스를 다뤄 차이를 볼 예정입니다 😃

     

    실제 코드로 보기전에, contentInsetAdjustmentBehavior을 몇마디로 정리해보고 넘어갈께요 🙋🏻

     

    contentInsetAdjustmentBehavior는 스크롤 뷰에서 컨텐츠 인셋 조정을 제어하는데 사용되는 속성입니다.

    해당 속성 자체는 뷰 컨트롤러의 safeAreaInsets나 스크롤 뷰 주변의 다른 레이아웃 가이드에 맞춰 컨텐츠 인셋을 자동으로 조정할지 여부와 방식을 결정하는 속성이에요.

     

    해당 문장 이해를 위해서 contentInset 공식문서에서는 스크롤 뷰가 오버래핑되는 바 (위 네비게이션이나 상태바 등)를 계산해 컨텐츠 인셋을 조정하는데 그때 사용되는것이 adjustedContentInset이라는것이고 해당 속성을 조절할 수 있는 속성이 우리가 지금 하고 있는 이 contentInsetAdjustmentBehavior이라고 합니다.

     

    즉, 쉽게 말해 스크롤의 컨텐츠가 상태바 등 세이프 에어리어를 넘어 있는 공간까지 침범하지 않도록 시스템으로 자동 조정을 해줄것인지, 어떻게 해줄것인지 속성을 제공하는게 바로 contentInsetAdjustmentBehavior입니다 😃

     

    후..... 개념이 어려운건 아닌데 문장 해석이 난해해서 구구절절 풀어썼네요 🥲

     

    그럼 실제 코드로 한번 체감해볼까요?

     


    contentInsetAdjustmentBehavior 코드로 알아보기

    저는 SwiftUI 컨텐츠를 UIKit의 스크롤 뷰에 넣어서 예시 코드를 구현해보려 합니다.

    수직으로 스크롤되며, 컨텐츠들은 간단한 영상들을 넣어 릴스나 숏폼 같은 형태로 만들거에요 🙋🏻

     

    먼저 UIKit의 ScrollView를 UIViewRepresentable을 통해 SwiftUI에서 사용하도록 커스텀한 스크롤 뷰를 만들어볼까요?

     

    ScrollView 코드

    public struct PagingScrollView<Content: View>: UIViewRepresentable {
      var content: () -> Content
      
      init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
      }
      
      public func makeCoordinator() -> Coordinator {
        Coordinator(self)
      }
      
      public func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()
        scrollView.isPagingEnabled = true
        scrollView.showsVerticalScrollIndicator = false
        scrollView.showsHorizontalScrollIndicator = false
        🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻
        scrollView.contentInsetAdjustmentBehavior = .never
        scrollView.delegate = context.coordinator
        
        let hostingController = context.coordinator.hostingController
        hostingController.rootView = content()
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        hostingController.view.backgroundColor = .green
        
        scrollView.addSubview(hostingController.view)
        
        NSLayoutConstraint.activate([
          hostingController.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
          hostingController.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
          hostingController.view.topAnchor.constraint(equalTo: scrollView.topAnchor),
          hostingController.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
          hostingController.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
        ])
        
        return scrollView
      }
      
      public func updateUIView(_ uiView: UIScrollView, context: Context) {
        if let hostingView = uiView.subviews.first {
          hostingView.setNeedsLayout()
          hostingView.layoutIfNeeded()
        }
      }
      
      public class Coordinator: NSObject, UIScrollViewDelegate {
        var parent: PagingScrollView
        var hostingController = UIHostingController<Content?>(rootView: nil)
        
        init(_ parent: PagingScrollView) {
          self.parent = parent
        }
      }
    }

     

    평범한 UIScrollView를 사용하는 코드입니다.

    저기 이모지 부분을 보시면, contentInsetAdjustmentBehavior를 적용하고 있어요.

    저 부분을 이제 케이스로 바꿔보면서 볼꺼에요!

    아..! 해당 스크롤뷰의 백그라운드를 그린으로 설정해줬어요.

    이유는, 스크롤 뷰의 영역을 쉽게 확인할 수 있기 위함입니다.

     

    그전에, 아래와 같이 SwiftUI body에 UIKit으로 래핑한 스크롤 뷰를 사용해 컨텐츠를 담아줍니다.

     

    struct ContentView: View {
      let videoURLs: [String] = [
        "https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4",
        "https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4",
        "https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
        "https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"
      ]
      
      @State private var currentVisibleIndex: Int = 0
      
      var body: some View {
        PagingScrollView {
          LazyVStack(spacing: 0) {
            ForEach(videoURLs.indices, id: \.self) { index in
              VStack(spacing: 0) {
                Spacer()
                  .frame(minHeight: 0)
                
                ShortsView(
                  viewModel: ShortsViewModel(contentURL: videoURLs[index]),
                  isPlaying: Binding<Bool>(
                    get: { self.currentVisibleIndex == index },
                    set: { newValue in
                      if newValue {
                        self.currentVisibleIndex = index
                      }
                    }
                  )
                )
                
                Spacer()
                  .frame(minHeight: 0)
              }
              .frame(height: UIScreen.main.bounds.height)
            }
          }
        }
        .ignoresSafeArea()
      }
    }

     

    이제 그럼, 수직 스크롤 뷰안에 영상들이 담겨지게 되는것이겠죠?

    스크롤을 내리면 해당 스크롤 뷰의 페이징 방식으로 처리를 해놔서 우리가 익숙한 릴스나 숏폼 형태로 페이징처럼 넘어갈거에요.

     

    그럼 한번 확인해볼까요?

     


    1️⃣ automatic

     

    보시면, automatic이라 자동으로 인셋 조정이 적용된것입니다.

    스크롤 뷰의 맨 위와 맨 아래에서 한번 더 스크롤을 하면 인셋 공간이 생기는걸 볼 수 있어요.

    스크롤 뷰가 꽉 차게 ignoreSafeArea로 두었어도 해당 영역이 조정되지 않음을 알 수 있죠.

     

    현재는, 스크롤 자체를 수직으로만 제한해뒀기에 가로 모드 테스트는 생략하는데, 가로 모드에서도 자동적으로 인셋을 가져갑니다.

     

    또한, automatic과 always는 거의 동일한 동작을 합니다.

     

    scrollableAxes는 스크롤 가능한 방향에서 자동 조정인데, 해당 코드는 수직만 스크롤이 되도록 레이아웃을 잡아두었기에 테스트는 생략할께요!

     

    모두 위 샘플 영상과 같은 동작을 가집니다.

     


    2️⃣ never

     

    차이가 확연히 보이시나요?

     

    스크롤 뷰 최상단, 최하단에서 스크롤을 하여도 자동 인셋 조정을 하지 않고 디바이스에 꽉 차게 나올 수 있습니다.

    구현하고자하는 목표에서 자동으로 인셋 조정을 원치않는것이라면 해당 방법이 적절하겠죠?

     


    마무리

    이렇게 contentInsetAdjustmentBehavior를 학습해봤습니다ㅎㅎ

    시작은 SwiftUI의 스크롤 뷰의 기능이 17은 되야지 좀 적절히 쓸만해서 UIKit의 ScrollView를 끌어들이다가 요기까지 왔네요!

     


    레퍼런스

     

    contentInsetAdjustmentBehavior | Apple Developer Documentation

    The behavior for determining the adjusted content offsets.

    developer.apple.com

     

     

    UIScrollView.ContentInsetAdjustmentBehavior | Apple Developer Documentation

    Constants indicating how safe area insets are added to the adjusted content inset.

    developer.apple.com

     

     

    contentInset | Apple Developer Documentation

    The custom distance that the content view is inset from the safe area or scroll view edges.

    developer.apple.com

    'iOS' 카테고리의 다른 글

    Custom Font Dynamic Type  (6) 2024.09.02
    Get started with Dynamic Type (feat. WWDC 2024)  (11) 2024.08.26
    View Snapshot Capture  (55) 2024.06.18
    Haptic Feedback  (58) 2024.06.11
    UIPasteboard를 통한 클립보드 사용하기  (66) 2024.05.07
Designed by Tistory.