인디노트

SwiftUI 에서 ScrollViewReader 를 사용하지 않고 List 의 맨 아래로 가는 방법은 없나? 본문

소스 팁/Objective C, Swift, iOS, macOS

SwiftUI 에서 ScrollViewReader 를 사용하지 않고 List 의 맨 아래로 가는 방법은 없나?

인디개발자 2023. 4. 5. 12:42

ScrollViewReader 사용의 경우이고

import SwiftUI

struct ContentView: View {
    @State var items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    var body: some View {
        VStack {
            ScrollViewReader { proxy in
                List(items, id: \.self) { item in
                    Text("Item \(item)")
                        .id(item)
                }
                .onAppear {
                    // Scroll to the last item when the list appears
                    withAnimation {
                        proxy.scrollTo(items.last, anchor: .bottom)
                    }
                }
            }
            Button(action: {
                // Add an item to the list and scroll to the bottom
                let newItem = items.count + 1
                items.append(newItem)
                withAnimation {
                    proxy.scrollTo(newItem, anchor: .bottom)
                }
            }, label: {
                Text("Add Item")
            })
        }
    }
}

 

이런 방법?

import SwiftUI

struct ContentView: View {
    @State private var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]

    var body: some View {
        VStack {
            List(items, id: \.self) { item in
                Text(item)
            }
            .onAppear {
                // Scroll to the last item when the list appears
                withAnimation {
                    scrollToLastItem()
                }
            }
            Button("Add Item") {
                // Add a new item to the list and scroll to the bottom
                let newItem = "Item \(items.count + 1)"
                items.append(newItem)
                withAnimation {
                    scrollToLastItem()
                }
            }
        }
    }

    func scrollToLastItem() {
        // Scroll to the last item in the list
        guard let lastItem = items.last else { return }
        withAnimation {
            let index = items.index(of: lastItem)!
            let lastIndex = items.index(before: items.endIndex)
            UITableView.appearance().separatorStyle = .none
            UITableView.appearance().backgroundColor = .clear
            UITableView.appearance().separatorColor = .clear
            UITableView.appearance().showsVerticalScrollIndicator = false
            UITableView.appearance().showsHorizontalScrollIndicator = false
            UITableViewCell.appearance().backgroundColor = .clear
            UITableView.appearance().isScrollEnabled = false
            UITableView.appearance().allowsSelection = false
            UITableView.appearance().isHidden = true
            List(selection: .constant("")) {
                ForEach(items, id: \.self) { item in
                    Text(item)
                }
            }
            .frame(maxHeight: .infinity)
            .onAppear {
                UITableView.appearance().isScrollEnabled = true
                UITableView.appearance().allowsSelection = true
                UITableView.appearance().isHidden = false
            }
            .onChange(of: items) { newValue in
                UITableView.appearance().isScrollEnabled = true
                UITableView.appearance().allowsSelection = true
                UITableView.appearance().isHidden = false
            }
            .introspectTableView(customize: { tableView in
                let indexPath = IndexPath(row: index, section: 0)
                tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
            })
        }
    }
}

extension View {
    func introspectTableView(customize: @escaping (UITableView) -> ()) -> some View {
        return introspect(selector: TargetViewSelector.ancestorOrSibling(isTableCell).isTableView, customize: customize)
    }
}

func isTableCell(_ element: AnyViewType.Type) -> Bool {
    let className = NSStringFromClass(element)
    return className == "_UITableViewCellContentView" || className == "UITableViewCellContentView"
}

 

또다른 방법...

import SwiftUI

struct ContentView: View {
    @State var items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    var body: some View {
        VStack {
            List(items, id: \.self) { item in
                Text("Item \(item)")
                    .id(item)
            }
            .onAppear {
                // Scroll to the last item when the list appears
                scrollToLastItem()
            }
            Button(action: {
                // Add an item to the list and scroll to the bottom
                let newItem = items.count + 1
                items.append(newItem)
                scrollToLastItem()
            }, label: {
                Text("Add Item")
            })
        }
    }
    
    func scrollToLastItem() {
        withAnimation {
            guard let lastItem = items.last else { return }
            let lastIndex = items.count - 1
            let lastItemID = AnyHashable(lastItem)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                // Add a small delay to make sure the list is fully rendered
                // before scrolling to the last item
                if let lastItemRect = getRectForLastItem() {
                    // Scroll to the last item using scrollTo method
                    UITableView.appearance().scrollToRow(
                        at: IndexPath(row: lastIndex, section: 0),
                        at: .bottom, animated: true)
                }
            }
        }
    }
    
    func getRectForLastItem() -> CGRect? {
        guard let lastItem = items.last else { return nil }
        let lastItemID = AnyHashable(lastItem)
        return UITableView.appearance().rectForRow(
            at: IndexPath(row: items.count - 1, section: 0))
    }
}

 

이런 방법? iOS 에서는 ScrollViewReader 가 사용할 수 없다는데... 이게 뭐지?

Type '()' cannot conform to 'View'

scrollTo 메소드는 ScrollView 내부의 모든 뷰에 고유한 ID를 제공하므로, List의 각 요소에 ID를 부여할 필요가 없습니다. 대신, List의 마지막 요소를 scrollTo 메소드로 전달하고, anchor 매개 변수를 .bottom으로 설정하여 List의 맨 아래쪽으로 스크롤합니다.

struct ContentView: View {
    @State private var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]

    var body: some View {
        VStack {
            List(items, id: \.self) { item in
                Text(item)
            }
            Button("Scroll to Bottom") {
                withAnimation {
                    scrollToBottom()
                }
            }
        }
    }

    private func scrollToBottom() {
        guard let lastItem = items.last else { return }
        withAnimation {
            // The anchor parameter determines where the item is positioned after scrolling
            // .bottom means the item will be positioned at the bottom of the visible area
            ScrollViewReader { scrollView in
                scrollView.scrollTo(lastItem, anchor: .bottom)
            }
        }
    }
}

 

 

반응형
Comments