ScrollView 或 List,如何制作特定的可滚动视图 (SwiftUI)

Posted

技术标签:

【中文标题】ScrollView 或 List,如何制作特定的可滚动视图 (SwiftUI)【英文标题】:ScrollView or List, How to make a particular scrollable view (SwiftUI) 【发布时间】:2021-01-15 03:05:20 【问题描述】:

所以我试图在 SwiftUI 中创建这个 List 或 ScrollView(还没有完全准备好 ios 14,我必须使用旧的 api,所以我不能使用 ScrollViewReader 或来自 iOS 14+ SwiftUI api 的其他幻想) . 目标是在视图中仅显示特定数量的行,并能够将最后一行居中。

图片总是更容易解释。所以它应该看起来像这样。

前两行应用了特定颜色,中间行也应用了特定颜色,但必须垂直居中。那么最终下面还有 2 行,但它们目前是不可见的,并且通过向下滚动并使其显示可见。

我最接近的例子是 Apple Music Lyrics UI/UX,如果你熟悉的话。

我不知道如何在这里解决这个问题。我想过创建一个List,它将有一个Text,其框架高度由视图的高度除以5定义。但是我不确定如何为每一行定义一个特定的颜色,具体取决于哪一个是它现在在视图中。

另外,如果我可以将所选行居中并让另一行有自己的大小而不设置任意大小,那将是更好的选择。

把我的头撞在墙上的自动取款机上,欢迎任何帮助!谢谢你们。

编辑 1: 作为@nicksarno 回答的结果添加的屏幕截图。绝对好看。唯一注意到的问题 atm 是它突出显示具有相同颜色而不是一个颜色的多行。

编辑 2: 我对@nicksarno 发布的代码做了一些调整。

        let middleScreenPosition = geometryProxy.size.height / 2

        return ScrollView 
            VStack(alignment: .leading, spacing: 20) 
                Spacer()
                    .frame(height: geometryProxy.size.height * 0.4)
                ForEach(playerViewModel.flowReaderFragments, id: \.id)  text in
                    Text(text.content) // Outside of geometry ready to set the natural size
                        .opacity(0)
                        .overlay(
                            GeometryReader  geo in
                                let midY = geo.frame(in: .global).midY

                                Text(text.content) // Actual text
                                    .font(.headline)
                                    .foregroundColor( // Text color
                                        midY > (middleScreenPosition - geo.size.height / 2) && midY < (middleScreenPosition + geo.size.height / 2) ? .white :
                                            midY < (middleScreenPosition - geo.size.height / 2) ? .gray :
                                            .gray
                                    )
                                    .colorMultiply( // Animates better than .foregroundColor animation
                                        midY > (middleScreenPosition - geo.size.height/2) && midY < (middleScreenPosition + geo.size.height/2) ? .white :
                                            midY < (middleScreenPosition - geo.size.height/2) ? .gray :
                                            .clear
                                    )
                                    .animation(.easeInOut)
                            
                        )
                
                Spacer()
                    .frame(height: geometryProxy.size.height * 0.4)

            
            .frame(maxWidth: .infinity)
            .padding()
        
        .background(
            Color.black
                .edgesIgnoringSafeArea(.all)
        )

现在它更接近我正在寻找的东西。另外,我添加了一些Spacer(还不是动态的),如果需要的话,可以让文本居中。仍然需要弄清楚如何在选中一个元素时将文本放在中间。

【问题讨论】:

对于颜色,我想我的数据类型可以添加一个类似var relativePositionToMiddle: Int 的值,并在设置选择时将其设置为 0、-1、-2、+1、+2 .这也将允许我以某种方式将所选视图也居中。我不知道。我还在想。 我的意思是我的数组使用的数据结构* 其实,我大概可以使用相同的 relativePositionToMiddle 变量来确定所有不是中间元素和上面两个元素为不可见的。然后不必再管理单元格的大小了。只需要弄清楚如何设法将特定单元格放在中间。 你可以使用 GeometryReader (iOS 13+) 吗? 是的!您可以在任何版本的 swiftui 中使用 GeomertyReader 【参考方案1】:

我会这样做。首先,添加一个 GeometryReader 来获取整个屏幕的大小(screenGeometry)。使用它,您可以设置文本修饰符的边界(上/下)。最后,在每个 Text() 后面添加一个 GeoemteryReader 并且(这就是魔法)您可以使用 .frame(in: global) 函数在全局坐标空间中找到它的当前位置。然后我们可以将帧坐标与我们的上/下边界进行比较。

几个注意事项:

当前边界位于屏幕的 40% 和 60%。 我目前使用的是文本几何的 .midY 坐标,但您也可以使用 .minY 或 .maxY 来获得更精确的坐标。 GeometryReader 的自然行为将大小调整为适合的最小大小,因此为了保持自然文本大小,我添加了第一个不透明度为 0% 的 Text()(用于框架),然后添加了 GeometryReader + 实际文本() 作为叠加层。 最后,.foregroundColor 并没有真正的动画,所以我还在顶部添加了一个 .colorMultiply,它确实动画。

代码:

 struct ScrollViewPlayground: View 
        
        let texts: [String] = [
            "So I am trying to make this List or ScrollView in SwiftUI (not fully iOS 14 ready yet, I have to use the old api, so I can't use ScrollViewReader or other fantasy from iOS 14+ SwiftUI api). The goal is to have only a specific number of rows visible on the view and be able to center the last one.",
            "Images always make it easier to explain. So it should look somewhat like this.",
            "the first two rows have a specific color applied to them, the middle one also but has to be center vertically. then there are eventually 2 more rows under but they are invisible at the moment and will be visible by scrolling down and make them appear.",
            "The closest example i have of this, is the Apple Music Lyrics UI/UX if you are familiar with it.",
            "I am not sure how to approach this here. I thought about create a List that will have a Text with a frame height defined by the height of the view divided by 5. But then I am not sure how to define a specific color for each row depending of which one is it in the view at the moment.",
            "Also, It would be preferable if I can just center the selected row and let the other row have their own sizes without setting one arbitrary.",
            "Banging my head on the walls atm, any help is welcomed! thank you guys."
        ]
        
        var body: some View 
            GeometryReader  screenGeometry in
                let lowerBoundary = screenGeometry.size.height * 0.4
                let upperBoundary = screenGeometry.size.height * (1.0 - 0.4)
                
                ScrollView 
                    VStack(alignment: .leading, spacing: 20) 
                        ForEach(texts, id: \.self)  text in
                            Text(text) // Outside of geometry ready to set the natural size
                                .opacity(0)
                                .overlay(
                                    GeometryReader  geo in
                                        let midY = geo.frame(in: .global).midY
    
                                        Text(text) // Actual text
                                            .font(.headline)
                                            .foregroundColor( // Text color
                                                midY > lowerBoundary && midY < upperBoundary ? .white :
                                                midY < lowerBoundary ? .gray :
                                                .gray
                                            )
                                            .colorMultiply( // Animates better than .foregroundColor animation
                                                midY > lowerBoundary && midY < upperBoundary ? .white :
                                                midY < lowerBoundary ? .gray :
                                                .clear
                                            )
                                            .animation(.easeInOut)
                                    
                                )
                        
    
                    
                    .frame(maxWidth: .infinity)
                    .padding()
                
                .background(
                    Color.black
                        .edgesIgnoringSafeArea(.all)
                )
            
        
    
    
    struct ScrollViewPlayground_Previews: PreviewProvider 
        static var previews: some View 
            ScrollViewPlayground()
        
    

【讨论】:

哦,太好了,它肯定离目标更近了!谢谢!一点点注意,它似乎同时突出显示了多行,而不仅仅是中间的一行。我猜这是由于通过大小而不是相关单元格设置的界限。你对此有什么想法吗? 即使通过玩边界,它似乎也不匹配。它需要是动态的。目前,正在寻找方法另外,有3种颜色,白色,灰色和深灰色,这里处理2。只是做笔记^^

以上是关于ScrollView 或 List,如何制作特定的可滚动视图 (SwiftUI)的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUI 中制作聊天应用程序:如何让 ScrollView 在键盘出现时保持原位?

SwiftUI:在 ScrollView 或 List 中时,PageTabViewStyle() 新损坏?

使用 List/ScrollView 与 SFSafariViewController 全屏工作表发生冲突

如何在特定位置将视图添加到 ScrollView?

将多个 ContentViews 放入 ScrollView

如何滚动到scrollview内recyclerview中的特定位置?