ScrollViewReader/scrollTo(_:anchor:) 无法可靠工作

问题描述 投票:0回答:3

我构建了一个

ScrollView
,其中包含 0-3 个图像和
VStack
中的多行文本字段。我还在滚动视图内添加了一个
ScrollViewReader
,并在某些事件(用户开始输入、图像集合更改)时使用它滚动到文本字段的底部。

关键是:有时有效,有时无效。当它不起作用时,我意识到,当我手动滚动一点然后重试(例如打字)时,它就起作用了。

不确定这是否相关,但是只要

ImageOrPlaceholderComponent
内的图像为零,
currentEntryImages
首先显示一个占位符,之后的图像(两种状态都意味着对
currentEntryImages
的更改,因此应该滚动到文本字段的底部)。

        NavigationStack {
            ScrollView {
                ScrollViewReader { scrollview in
                    VStack {
                        // Attached images.
                        AnyLayout(VStackLayout(spacing: 2.5)) {
                            ForEach(values: currentEntryImages) { entryImage in
                                ImageOrPlaceholderComponent(image: entryImage)
                                    .clipped()
                            }
                        }

                        // Text field for the entry with toolbar.
                        TextField("...", text: $entryDTO.text, axis: .vertical)
                            .id(entryTextFieldAnchor)
                            .multilineTextAlignment(.leading)
                            .padding()
                            .focused($mainTextFieldFocused)
                            .onAppear { mainTextFieldFocused = true }

                            // Scroll to the bottom of the text field, when the user is typing ...
                            .onChange(of: entryDTO.text) { _ in
                                withAnimation {
                                    scrollview.scrollTo(entryTextFieldAnchor, anchor: .bottom)
                                }
                            }

                            // ... or the entry images have changed.
                            .onChange(of: currentEntryImages) { _ in
                                withAnimation {
                                    scrollview.scrollTo(entryTextFieldAnchor, anchor: .bottom)
                                }
                            }
                    }
      
                }
            }
        }
swiftui scrollview scrollviewreader
3个回答
1
投票

面临类似的问题。虽然这不是一个完美的解决方案,但我注意到对scrollTo函数进行速率限制解决了这个问题。

一旦我更新,比如说,每 5 次更新一次,它就会按预期滚动,不需要先手动滚动。

我的猜测是内部滚动逻辑出现了问题,从我看来,ScrollViewReader 没有公开任何内容来帮助我们调节使用情况。


0
投票

发现了另一种黑客,似乎比速率限制的更可靠(留下答案,因为这是一种有时有效的快速而肮脏的解决方案)

假设我们看到的行为是 ScrollView 或 ScrollViewReader 中的某些内部状态中断,我想出了一种“帮助”内部状态机的方法。

这是超级黑客,但似乎对我来说非常有效。技巧是在滚动内容的底部有两个不同的空视图,然后间歇性地滚动到每个视图的底部。

所以我的滚动内容以:

结尾
Text("")
    .frame(height: 0.0)
    .id(2)
Text("")
    .frame(height: 0.0)
    .id(3)

在我的测试中,这些黑客观点比

EmptyView()
的观点更好。再次强调,这里是黑匣子领域,所以将允许它:)

然后在滚动代码中,您根据自己的计数逻辑执行此操作:

if (newStepCount % 3 == 0) {
    DispatchQueue.main.async {
        proxy.scrollTo(newStepCount % 2 == 0 ? 2 : 3, anchor: .bottom)
    }
}

0
投票

我遇到了这个问题,并通过在滚动视图顶部放置另一个标记并按顺序调用来解决它:

DispatchQueue.main.async {
    reader.scrollTo(topID, anchor: .top)
    reader.scrollTo(bottomID, anchor: .bottom)
}

我不知道为什么会这样。

尺寸变化时保持底部滚动的 ScrollView 示例:

struct ScrollViewPinnedBottom<Content:View>: View {

    @Namespace var bottomID
    @Namespace var topID

    @ViewBuilder var content: () -> Content

    @StateObject private var model = ScrollViewPinnedBottomModel()
    
    var body: some View {
        ScrollViewReader { reader in
            ScrollView(.vertical) {
                VStack {
                    Color.clear
                        .frame(height: .zero)
                        .id(topID)

                    content()
                    
                    Color.clear
                        .frame(height: .zero)
                        .id(bottomID)
                }
                .readSize { size in
                    if model.shouldScroll(newSize: size) {
                        DispatchQueue.main.async {
                            reader.scrollTo(topID, anchor: .top)
                            reader.scrollTo(bottomID, anchor: .bottom)
                        }
                    }
                }
            }
        }
    }

}


class ScrollViewPinnedBottomModel: ObservableObject {
    
    var lastSize: CGSize?

    func shouldScroll(newSize: CGSize) -> Bool {
        guard newSize.height != lastSize?.height else { return false }
        lastSize = newSize
        return true
    }

}

此示例使用此 readSize 扩展。

© www.soinside.com 2019 - 2024. All rights reserved.