개발 - iOS SwiftUI

iOS SwiftUI List Scroll 맨 아래로 이동 방법

개미v 2023. 11. 6. 19:13

SwiftUI에서 스크롤을 맨 아래로 이동하는 방법 입니다.

 

일반적인 방법

ScrollViewReader를 사용해서 proxy.scrollTo(list.last, anchor: .bottom)로 이동하면 스크롤이 맨 아래로 이동 합니다.

 

그런데 가장 마지막 row의 높이가 다른 경우 row의 높이를 계산하지 못해서 제대로 이동되지 않습니다.

이 문제는 에뮬레이터에서는 이상 없었는데 아이폰 실제 기기에서 테스트 하니 문제가 발생했습니다.

import SwiftUI

struct TestView: View {
    
    var list: [String] = ["1111", "1111", "1111", "1111", "1111", "1111", "1111"
                                     , "1111", "1111", "1111", "1111", "1111", "1111"
                                     , "1111", "1111", "1111", "1111", "1111", "1111"
                                     , "여러 줄\n1111111\n111111111\n11111111\n111111111111\n1111111111\n1111111111111"]
    
    @State var scrollBottom = false
    
    var body: some View {
        VStack {
            ScrollViewReader { proxy in
                List(list.indices, id: \.self) { index in
                    let item = list[index]
                    
                    VStack {
                        Text(list[index])
                    }
                    .listRowSeparator(.hidden)
                    .id(item)
                }
                .onChange(of: scrollBottom) { newValue in
                    if newValue == true  {
                        DispatchQueue.main.async {
                            proxy.scrollTo(list.last, anchor: .bottom)
                        }
                        scrollBottom = false
                    }
                }
            }
            .onAppear() {
                DispatchQueue.main.async {
                    // 스크롤 맨 아래로 이동
                    self.scrollBottom = true
                }
            }
        }
    }
}

 

개선 방법

가장 마지막줄에 dummy의 row를 하나 추가하였습니다.

그리고 dummy row의 높이는 0으로 설정 하였습니다.

import SwiftUI

struct TestView: View {
    
    var list: [String] = ["1111", "1111", "1111", "1111", "1111", "1111", "1111"
                                     , "1111", "1111", "1111", "1111", "1111", "1111"
                                     , "1111", "1111", "1111", "1111", "1111", "1111"
                                     , "여러 줄\n1111111\n111111111\n11111111\n111111111111\n1111111111\n1111111111111"]
    
    @State var scrollBottom = false
    
    var body: some View {
        VStack {
            ScrollViewReader { proxy in
                List(list.indices, id: \.self) { index in
                    let item = list[index]
                    
                    VStack {
                        Text(list[index])
                    }
                    .listRowSeparator(.hidden)
                    .id(item)
                                        
                    // 가장 마지막에 Empty 넣어줌
                    if item == list.last {
                        VStack {
                            Text("")
                        }
                        .padding(.top, 0)
                        .padding(.bottom, 0)
                        .frame(height: 0.0)
                        .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
                        .listRowSeparator(.hidden)
                        .id("BOTTOM_ID")
                    }
                }
                .environment(\.defaultMinListRowHeight, 0)
                .onChange(of: scrollBottom) { newValue in
                    if newValue == true  {
                        DispatchQueue.main.async {
                            proxy.scrollTo("BOTTOM_ID", anchor: .bottom)
                        }
                        scrollBottom = false
                    }
                }
            }
            .onAppear() {
                DispatchQueue.main.async {
                    // 스크롤 맨 아래로 이동
                    self.scrollBottom = true
                }
            }
        }
    }
}