[SwiftUI] ScrollView 중앙에 위치 시키기
- 언어/스위프트 UI
- 2024. 2. 16.
스크롤 뷰 scrollTo 이동
SwiftUI 에서 스크롤 뷰를 강제 이동시키려면 ScrollViewReader를 통해 값을 가져와서 scrollTo 메서드를 사용해서 이동시켜야 합니다. 다음과 같이 횡으로 스크롤 되는 캘린더에서 오늘 날짜에 중앙으로 위치 시킬 수 있는 예제로 공부해 보겠습니다.
우선 화면을 드로잉 해보겠습니다. 필요한 데이터를 간단히 정의해보겠습니다. 우선 선택한 날짜가 필요하고 캘린더 객체 그리고 시작일이 필요합니다. 이번달 전체 날짜를 그리기 위해서 components 라는 변수를 선언해 해당 달의 모든 날짜를 map을 사용해서 배열 형태로 가져옵니다.
struct HCalView: View {
@State private var selectedDate = Date.now
private let calendar = Calendar.current
var body: some View {
let startDate = calendar.date(from: Calendar.current.dateComponents([.year, .month], from: selectedDate))!
let components = (0..<calendar.range(of: .day, in: .month, for: startDate)!.count)
.map {
calendar.date(byAdding: .day, value: $0, to: startDate)!
}
}
/// 요일 추출
func day(from date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate("E")
return dateFormatter.string(from: date)
}
}
이제 날짜 컴포넌트를 만들어서 그려주겠습니다. 스크롤 뷰를 만들고 LazyHStack으로 감싸줍니다. ForEach를 통해서 components 를 나열해줍니다. 위에서 선언한 요일을 추출하는 함수 day(from date: Date)로 요일을 가져와줍니다.
var body: some View {
let startDate = calendar.date(from: Calendar.current.dateComponents([.year, .month], from: selectedDate))!
let components = (0..<calendar.range(of: .day, in: .month, for: startDate)!.count)
.map {
calendar.date(byAdding: .day, value: $0, to: startDate)!
}
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 10) {
ForEach(components, id: \.self) { date in
VStack {
Text(day(from:date))
.font(.caption)
Text("\(calendar.component(.day, from: date))")
}
}
}
}
}
각 컴포넌트에 스타일과 이벤트를 정의해줍니다.
- frame : 40 x 40 으로 만들어줍니다.
- padding : 각 요소의 패딩 값을 5로 지정합니다.
- background : 선택된 날짜면 gray 색으로 선택되지 않은 날짜는 투명으로 지정합니다.
- foregroundColor : 선택된 날짜면 화이트 아니면 블랙으로 지정합니다.
- onTapGesture : 각 요소들을 선택하면 선택된 날짜(selectedDate)에 현재 date값을 지정해줍니다.
ForEach(components, id: \.self) { date in
VStack {
Text(day(from:date))
.font(.caption)
Text("\(calendar.component(.day, from: date))")
}
.frame(width: 40, height: 40)
.padding(5)
.background(calendar.isDate(selectedDate, equalTo: date, toGranularity: .day) ? Color.gray : Color.clear)
.cornerRadius(10)
.foregroundColor(calendar.isDate(selectedDate, equalTo: date, toGranularity: .day) ? .white : .black)
.onTapGesture {
selectedDate = date
}
}
스크롤 뷰를 ScrollViewReader로 감싸줍니다. 그리고 각 요소마다. id 값을 지정합니다.
ScrollViewReader { scrView in
ScrollView(.horizontal, showsIndicators: false) {
...
.onTapGesture {
selectedDate = date
}
.id(date)
}
}
마지막으로 ScrollView에 onAppear 이벤트를 추가해줍니다. 화면이 시작될 때 해당 컴포넌트로 위치시킵니다. scrollTo 메서드를 사용하기 위해서는 id값이 필요하고 해당 값으로 위치시키게 됩니다. 그런데 스크롤 뷰에 해당 위치를 중앙에 위치 시키려면 anchor 값을 .center로 변경해줘야 합니다. date 값이 될 수도 있지만 보통 int 값이나 문자열이 될 수도 있습니다. 해당 요소의 고유값을 적용하면 됩니다.
.onAppear {
// Find the index of the selected date in the components array
if let selectedIndex = components.firstIndex(where: { calendar.isDate(selectedDate, equalTo: $0, toGranularity: .day) }) {
// Scroll to the selected date
scrView.scrollTo(components[selectedIndex], anchor: .center)
}
}
전체코드
struct HCalView: View {
@State private var selectedDate = Date.now
private let calendar = Calendar.current
var body: some View {
let startDate = calendar.date(from: Calendar.current.dateComponents([.year, .month], from: selectedDate))!
let components = (0..<calendar.range(of: .day, in: .month, for: startDate)!.count)
.map {
calendar.date(byAdding: .day, value: $0, to: startDate)!
}
ScrollViewReader { scrView in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 10) {
ForEach(components, id: \.self) { date in
VStack {
Text(day(from:date))
.font(.caption)
Text("\(calendar.component(.day, from: date))")
}
.frame(width: 40, height: 40)
.padding(5)
.background(calendar.isDate(selectedDate, equalTo: date, toGranularity: .day) ? Color.gray : Color.clear)
.cornerRadius(10)
.foregroundColor(calendar.isDate(selectedDate, equalTo: date, toGranularity: .day) ? .white : .black)
.onTapGesture {
selectedDate = date
}
.id(date)
}
}
}
.onAppear {
// Find the index of the selected date in the components array
if let selectedIndex = components.firstIndex(where: { calendar.isDate(selectedDate, equalTo: $0, toGranularity: .day) }) {
// Scroll to the selected date
scrView.scrollTo(components[selectedIndex], anchor: .center)
}
}
}
}
/// 요일 추출
func day(from date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate("E")
return dateFormatter.string(from: date)
}
}
'언어 > 스위프트 UI' 카테고리의 다른 글
[SwiftUI] 스위프트 UI가 Struct를 사용하는 이유 (0) | 2024.02.24 |
---|---|
[SwiftUI] 버튼 활성 / 비활성 처리하기 (0) | 2024.02.22 |
[SwiftUI] ScrollView 스크롤 오프셋 값 PreferenceKey 통해서 가져오기 (0) | 2024.02.15 |
[SwiftUI] URLSession 통신 await async JSON 데이터 로딩 처리하기 (0) | 2024.01.18 |
[SwiftUI] 네비게이션 바 버튼 만들기 (0) | 2023.07.10 |