개발/Swift

UIKit SwiftUI에서 자동 스크롤

덤벨로퍼 2025. 4. 8. 15:24

UIKit 자동 스크롤

자동(동적) 스크롤 하기 위해 uikit 에서도 scrollRectToVisible 제공해줌

  layoutIfNeeded() // Init 직후에 아직 내부 레이아웃이 안잡혀서 호출 필요
  scrollRectToVisible(button.frame, animated: true)  // ScrollView 기능

근데 이게 스유 처럼 가운데로 보내주는게 아니라

그냥 보이게만 해준다.

스크롤 하려는 저 button 이 화면에 아예 안보이면 잘 동작하는데

만약 왼쪽 찔끔 나온 상태라면 움직이지 않는 문제가 있음

따라서 별도로 스크롤을 계산하여 setContentOffset 호출이 필요함

layoutIfNeeded()

let buttonCenterX = button.center.x 

DispatchQueue.main.async { // view 렌더링이 끝난 이후에 동작해야 알맞은 self.bounds 가 받아와짐
    var targetOffsetX = buttonCenterX - self.bounds.width / 2 // 버튼이 스크롤 영역 가운데보다 왼쪽이면 음수
    targetOffsetX = max(0, min(targetOffsetX, self.contentSize.width - self.bounds.width)) // 컨텐츠 사이즈 작으면 음수
    self.setContentOffset(CGPoint(x: targetOffsetX, y: 0), animated: true)
}

 

SwiftUI 자동 스크롤

ScrollViewReader 를 사용하면 쉽게 동적 스크롤 기능 구현가능

ScrollView(.horizontal, showsIndicators: false) {
    ScrollViewReader { proxy in
			ForEach(categories, id: \\.id) { category in
          Button(action: {
              onSelectCategory(category.id)
          }) {
              Text(category.title)
					}.id(category.id) // id 지정
			}
	}
}

버튼에 지정된 id 를 기준으로 스크롤이 가능함

.onChange(of: selectedCategoryId, perform: { id in
      withAnimation {
          proxy.scrollTo(id, anchor: .center)
      }
  })

selectedCategoryId 값이 바뀔때 마다 onChange 블록이 호출되고

proxy.scrollTo 를 활용해 스크롤 이 가능함

 

anchor: .center 를 통해 최대한 가운데에 위치하도록 쉽게 구현할수 있음 ( 이외 .leading .trailing 이 있음)

스크롤이 끝나서 가운데로 갈수없으면 갈수 있는 만큼만 스크롤 됨

 

onChange 의 특성상 selectedCategoryId 이 바뀌는 경우에만 호출되는데

만약 init 시점에 selectedCategoryId 지정되었고 바뀌지 않는다면 view 가 렌더링 된 이후에 값이 바뀌지 않았으므로

진입과 동시에 스크롤 되지는 않는다.

만약 따로 처리를 하려면 onAppear 에서 같은 방식으로 scroll 해야함

.onAppear {
	withAnimation {
    proxy.scrollTo(selectedCategoryId, anchor: .center)
	}
}