SwiftUI - 淡出 ScrollView

Posted

技术标签:

【中文标题】SwiftUI - 淡出 ScrollView【英文标题】:SwiftUI - fade out a ScrollView 【发布时间】:2020-09-13 16:28:42 【问题描述】:

我有一个生成的超大图表,我将它放入一个 ScrollView 中,以便用户可以向右滚动并查看所有值。我想通过淡出 ScrollView 向用户表明右侧还有“更多内容”。通过应用 CAGradientLayer,Swift 中的某些事情变得简单。

我的方法是应用从透明(从 80% 开始)到系统背景颜色(以 100% 结束)渐变的叠加层。结果见附件截图。

问题编号。 1:看起来不像它应该的样子。

问题编号。 2:尽管应用了 -1 的 zIndex 到叠加层,但一旦应用了叠加层,ScrollView 就不会再滚动了。

知道如何实现这一目标吗?谢谢!

这是我的代码:

struct HealthExportPreview: View 
    @ObservedObject var carbsEntries: CarbsEntries
    
    var body: some View 
        ScrollView(.horizontal) 
            HStack 
                ForEach(0..<self.carbsEntries.carbsRegime.count, id: \.self)  index in
                    ChartBar(carbsEntries: self.carbsEntries, entry: self.carbsEntries.carbsRegime[index], requiresTimeSplitting: index == self.carbsEntries.timeSplittingAfterIndex)
                
            
            .padding()
            .animation(.interactiveSpring())
        
        .overlay(Rectangle()
            .fill(
                LinearGradient(gradient: Gradient(stops: [
                    .init(color: .clear, location: 0.8),
                    .init(color: Color(UIColor.systemBackground), location: 1.0)
                ]), startPoint: .leading, endPoint: .trailing)
            )
            .zIndex(-1)
        )
        .frame(height: CGFloat(carbsEntries.previewHeight + 80))
        .onAppear() 
            self.carbsEntries.fitCarbChartBars()
        
    


struct ChartBar: View 
    var carbsEntries: CarbsEntries
    var entry: (date: Date, carbs: Double)
    var requiresTimeSplitting: Bool
    
    static var timeStyle: DateFormatter 
        let formatter = DateFormatter()
        formatter.timeStyle = .short
        return formatter
    
    
    var body: some View 
        VStack 
            Spacer()
            Text(FoodItemViewModel.doubleFormatter(numberOfDigits: entry.carbs >= 100 ? 0 : (entry.carbs >= 10 ? 1 : 2)).string(from: NSNumber(value: entry.carbs))!)
                .font(.footnote)
                .rotationEffect(.degrees(-90))
                .offset(y: self.carbsEntries.appliedMultiplier * entry.carbs <= 40 ? 0 : 40)
                .zIndex(1)
            
            if entry.carbs <= self.carbsEntries.maxCarbsWithoutSplitting 
                Rectangle()
                    .fill(Color.green)
                    .frame(width: 15, height: CGFloat(self.carbsEntries.appliedMultiplier * entry.carbs))
             else 
                Rectangle()
                    .fill(Color.green)
                    .frame(width: 15, height: CGFloat(self.carbsEntries.getSplitBarHeight(carbs: entry.carbs)))
                    .overlay(Rectangle()
                        .fill(Color(UIColor.systemBackground))
                        .frame(width: 20, height: 5)
                        .padding([.bottom, .top], 1.0)
                        .background(Color.primary)
                        .rotationEffect(.degrees(-10))
                        .offset(y: CGFloat(self.carbsEntries.getSplitBarHeight(carbs: entry.carbs) / 2 - 10))
                )
            
            
            if self.requiresTimeSplitting 
                Rectangle()
                    .fill(Color(UIColor.systemBackground))
                    .frame(width: 40, height: 0)
                    .padding([.top], 2.0)
                    .background(Color.primary)
                    .overlay(Rectangle()
                        .fill(Color(UIColor.systemBackground))
                        .frame(width: 20, height: 5)
                        .padding([.bottom, .top], 1.0)
                        .background(Color.black)
                        .rotationEffect(.degrees(80))
                        .offset(x: 20)
                        .zIndex(1)
                    )
             else 
                Rectangle()
                    .fill(Color(UIColor.systemBackground))
                    .frame(width: 40, height: 0)
                    .padding([.top], 2.0)
                    .background(Color.primary)
            
            
            Text(ChartBar.timeStyle.string(from: entry.date))
                .fixedSize()
                .layoutPriority(1)
                .font(.footnote)
                .rotationEffect(.degrees(-90))
                .offset(y: 10)
                .frame(height: 50)
                .lineLimit(1)
        .frame(width: 30)
    

【问题讨论】:

【参考方案1】:

使用 alpha .mask 怎么样?

您可以像这样使用 alpha 掩码来代替您的 .overlay:

.mask(
    HStack(spacing: 0) 

        // Left gradient
        LinearGradient(gradient: 
           Gradient(
               colors: [Color.black.opacity(0), Color.black]), 
               startPoint: .leading, endPoint: .trailing
           )
           .frame(width: 50)

        // Middle
        Rectangle().fill(Color.black)

        // Right gradient
        LinearGradient(gradient: 
           Gradient(
               colors: [Color.black, Color.black.opacity(0)]), 
               startPoint: .leading, endPoint: .trailing
           )
           .frame(width: 50)
    
 )

【讨论】:

【参考方案2】:

好的,已知 SwiftUI 问题是它不会通过覆盖传递一些手势,甚至是透明的。

这是解决这个问题的可能方法 - 想法是让渐变仅覆盖小的边缘位置,因此可以直接访问滚动视图的其他部分(是的,在渐变下它仍然不可拖动,但它是一小部分)。

使用 Xcode 11.7 / ios 13.7 准备和测试的演示

(您的视图的简化变体)

struct HealthExportPreview: View 
    var body: some View 
        GeometryReader  gp in
            ZStack 
                ScrollView(.horizontal) 
                    HStack 
                       // simplified content
                        ForEach(0..<20, id: \.self)  index in
                            Rectangle().fill(Color.red)
                                .frame(width: 40, height: 80)
                        
                    
                    .padding()
                    .animation(.interactiveSpring())
                

                // inject gradient at right side only
                Rectangle()
                    .fill(
                        LinearGradient(gradient: Gradient(stops: [
                            .init(color: Color(UIColor.systemBackground).opacity(0.01), location: 0),
                            .init(color: Color(UIColor.systemBackground), location: 1)
                        ]), startPoint: .leading, endPoint: .trailing)
                    ).frame(width: 0.2 * gp.size.width)
                    .frame(maxWidth: .infinity, alignment: .trailing)
            .fixedSize(horizontal: false, vertical: true)
        
    

【讨论】:

使用 .allowsHitTesting(false) 可以使其可拖动 @RayXu 不幸的是它不适用于 iOS

以上是关于SwiftUI - 淡出 ScrollView的主要内容,如果未能解决你的问题,请参考以下文章

在动画偏移时动画删除/添加 SwiftUI 视图

动画 SwiftUI 文本编辑器文本

SwiftUI:动画过渡

在 SwiftUI 中链接动画

SwiftUI 中的动态转换

NavigationView中的SwiftUI自定义动画过渡?