SwiftUI - 如何在水平滚动视图中居中组件?(当点击时)

3

我有一个水平的ScrollView,并且其中包含一个HStack。它包含多个子视图,通过ForEach渲染。我想使得当这些子视图被点击时,它们在视图中垂直居中。例如,我有以下代码:

ScrollView(.horizontal) {
    HStack(alignment: .center) {
        Circle() // for demonstration purposes, let's say the subviews are circles
            .frame(width: 50, height: 50)
        Circle()
            .frame(width: 50, height: 50)
        Circle()
            .frame(width: 50, height: 50)
    }
    .frame(width: UIScreen.main.bounds.size.width, alignment: .center)
}

我尝试了这段代码:

ScrollViewReader { scrollProxy in
    ScrollView(.horizontal) {
        HStack(alignment: .center) {
            Circle()
                .frame(width: 50, height: 50)
                .id("someID3")
                .frame(width: 50, height: 50)
                .onTapGesture {
                    scrollProxy.scrollTo(item.id, anchor: .center)
                }
            Circle()
                .frame(width: 50, height: 50)
                .id("someID3")
                .frame(width: 50, height: 50)
                .onTapGesture {
                    scrollProxy.scrollTo(item.id, anchor: .center)
                }

            ...
        }
    }

但它似乎没有效果。有人知道如何正确地做到这一点吗?


这不是MRE。我们应该能够复制您的代码并在Xcode中自己运行它。 - Yrb
我已经修改了它。现在可以复制粘贴了。 - Nicolas Gimelli
嗨@NicolasGimelli,你能详细说明一下问题吗?子视图目前是水平居中的(HStack(alignment: .center)),但是当其中任何一个被点击时,你想让它们在垂直方向上居中(VStack(alignment: .center))?我理解得对吗?不过我不确定你代码中的scrollTo的作用是什么。 - Cuneyt
嗨,@Cuneyt,是的,那是正确的。scrollTo 尝试“滚动”到被点击的项目,从而将其居中显示。正如我所说,这并没有起作用。 - Nicolas Gimelli
谢谢@NicolasGimelli,但是我仍然不清楚。您是否仍希望在点击后滚动视图水平滚动?您是否希望在点击后垂直绘制所有视图(全部)?您是否只想将被点击的视图居中?如果是这样,您是否希望特定视图水平绘制?如果是这样,您希望该视图向哪个方向居中?如果您可以绘制出点击前/点击后的视图,那会很有帮助。根据您的问题,我的理解如下: 点击前: | 行 --- 行 --- 行 | 点击后: |行| |行| |行| - Cuneyt
显示剩余2条评论
2个回答

3
你可以使用 ScrollViewScrollViewReader 来实现这个功能。但是,我在你的代码示例中发现了一些可能会引起问题的地方:
  • 你两次使用了相同的id "someID3"
  • 我看不到你的 item.id 是从哪里来的,所以我无法确定它是否包含相同的id ("someID3")。
  • 我不知道为什么你在同一个视图区域上有两个具有相同边界的框架。虽然这不应该是问题,但最好保持简单。
这是一个可行的示例:
import SwiftUI

@main
struct MentalHealthLoggerApp: App {
    var body: some Scene {
        WindowGroup {
            ScrollViewReader { scrollProxy in
                ScrollView(.horizontal) {
                    HStack(alignment: .center, spacing: 10) {
                        Color.clear
                            .frame(width: (UIScreen.main.bounds.size.width - 70) / 2.0)
                        ForEach(Array(0..<10), id: \.self) { id in
                            ZStack(alignment: .center) {
                                Circle()
                                    .foregroundColor(.primary.opacity(Double(id)/10.0))
                                Text("\(id)")
                            }
                            .frame(width: 50, height: 50)
                            .onTapGesture {
                                withAnimation {
                                    scrollProxy.scrollTo(id, anchor: .center)
                                }
                            }
                            .id(id)
                        }
                        Color.clear
                            .frame(width: (UIScreen.main.bounds.size.width - 70) / 2.0)
                    }
                }
            }
        }
    }
}

以下是演示:

[编辑:如果GIF无法自动播放,请单击它。]

您可以看到滚动动画效果。

请注意,我在 ScrollView 的两端添加了一些空白空间,因此实际上可以将第一个和最后一个元素居中,因为 ScrollViewProxy 永远不会滚动超出限制。


你能建议如何避免最后一个元素滚动到中心位置,或者保持最后一个元素在尾部位置吗? - Hitesh Patil
1
@Hitesh Patil 只需将底部 Color.clear 的框架宽度减小到您喜欢的大小,或者完全删除 Color.clear。 - theMomax

0

创建了一个自定义的ScrollingHStack,并使用geometry reader和一些计算,这是我们得到的结果:

struct ContentView: View {

    var body: some View {
        ScrollingHStack(space: 10, height: 50)
    }
    
}

struct ScrollingHStack: View {
    
    var space: CGFloat
    var height: CGFloat
    var colors: [Color] = [.blue, .green, .yellow]
    
    @State var dragOffset = CGSize.zero
    
    var body: some View {
        GeometryReader { geometry in
        HStack(spacing: space) {
            ForEach(0..<15, id: \.self) { index in
                
                Circle()
                    .fill(colors[index % 3])
                    .frame(width: height, height: height)
                    .overlay(Text("\(Int(dragOffset.width))"))
                    .onAppear {
                        dragOffset.width = geometry.size.width / 2  - ((height + space) / 2)
                    }
                    .onTapGesture {
                        let totalItems = height * CGFloat(index)
                        let totalspace = space * CGFloat(index)
                        withAnimation {
                            dragOffset.width = (geometry.size.width / 2) - (totalItems + totalspace) - ((height + space) / 2)
                        }
                    }
            }
        }
        .offset(x: dragOffset.width)
        .gesture(DragGesture()
                    .onChanged({ dragOffset = $0.translation})
        
        )
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接