[SwiftUI] ScrollView 중앙에 위치 시키기

반응형

     스크롤 뷰 scrollTo 이동 

     

    SwiftUI 에서 스크롤 뷰를 강제 이동시키려면 ScrollViewReader를 통해 값을 가져와서 scrollTo 메서드를 사용해서 이동시켜야 합니다. 다음과 같이 횡으로 스크롤 되는 캘린더에서 오늘 날짜에 중앙으로 위치 시킬 수 있는 예제로 공부해 보겠습니다.

     

    ScrollView

     

     

     

    우선 화면을 드로잉 해보겠습니다. 필요한 데이터를 간단히 정의해보겠습니다. 우선 선택한 날짜가 필요하고 캘린더 객체 그리고 시작일이 필요합니다. 이번달 전체 날짜를 그리기 위해서 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)
        }
        
    }


     

     
    반응형

    댓글

    Designed by JB FACTORY