带遮罩的 LinearGradient 不会填满整个区域

问题描述

我正在创建一个折线图视图,灵感来自 this blog post

线条是用 Path 绘制的,渐变是用 LinearGradient 和蒙版绘制的,它们相互叠加。

我的主要问题是,正如您在屏幕截图中看到的,所有渐变的开始都有一个斜率,如果该值为零,它们似乎过早关闭(末端似乎也过早削减,但这不是我的主要问题)。如果值 > 0(如橙色),则开始似乎也为时已晚。

swiftui line chart

我想要的是让渐变完全填充红色箭头所指的部分(去除斜坡和突然切割)。

但是我无法通过调整幻数来找到解决方案。此外,我更愿意了解正在发生的事情,而不是四处闲逛。

我做错了什么?


我制作了一个可重现的示例,粘贴这是一个 ContentView:

struct ContentView: View {
    
    @State var values: [Double] = [0,1,2,6,4,0]
    @State var totalMaxnum: Double = 6
    
    var body: some View {
        GeometryReader { proxy in
            ScrollView {
                vstack {
                    // some other view
                    
                    vstack {
                        vstack {
                            RoundedRectangle(cornerRadius: 5)
                                .overlay(
                                    ZStack {
                                        Color.white.opacity(0.95)
                                        
                                        drawValuesLine(values: values,color: .blue)
                                    }
                                )
                                .overlay(
                                    ZStack {
                                        Color.clear
                                        
                                        drawValuesGradient(values: values,start: .orange,end: .red)
                                    }
                                )
                                .frame(width: proxy.size.width - 40,height: proxy.size.height / 4)
                            
                            // some other view
                        }
                        
                        // some other view
                    }
                }
            }
        }
    }
    
    func drawValuesLine(values: [Double],color: Color) -> some View {
        GeometryReader { geo in
            Path { p in
                let scale = (geo.size.height - 40) / CGFloat(totalMaxnum)
                var index: CGFloat = 0
                
                let y1: CGFloat = geo.size.height - 10 - (CGFloat(values[0]) * scale)
                let y = y1.isNaN ? 0 : y1
                p.move(to: CGPoint(x: 8,y: y))
                
                for _ in values {
                    if index != 0 {
                        let x1: CGFloat = 8 + ((geo.size.width - 16) / CGFloat(values.count)) * index
                        let x = x1.isNaN ? 0 : x1
                        let yy: CGFloat = geo.size.height - 10 - (CGFloat(values[Int(index)]) * scale)
                        let y = yy.isNaN ? 0 : yy
                        p.addLine(to: CGPoint(x: x,y: y))
                    }
                    index += 1
                }
                
                
            }
            .stroke(style: strokeStyle(linewidth: 3,lineCap: .round,lineJoin: .round,miterLimit: 80,dash: [],dashPhase: 0))
            .foregroundColor(color)
        }
    }
    
    func drawValuesGradient(values: [Double],start: Color,end: Color) -> some View {
        LinearGradient(gradient: Gradient(colors: [start,end]),startPoint: .top,endPoint: .bottom)
            .padding(.bottom,1)
            .opacity(0.8)
            .mask(
                GeometryReader { geo in
                    Path { p in
                        let scale = (geo.size.height - 40) / CGFloat(totalMaxnum)
                        var index: CGFloat = 0
                        
                        // Move to the starting point
                        let y1: CGFloat = geo.size.height - (CGFloat(values[Int(index)]) * scale)
                        let y = y1.isNaN ? 0 : y1
                        p.move(to: CGPoint(x: 8,y: y))
                        
                        // Draw the lines
                        for _ in values {
                            if index != 0 {
                                let x1: CGFloat = 8 + ((geo.size.width - 16) / CGFloat(values.count)) * index
                                let x = x1.isNaN ? 0 : x1
                                let yy: CGFloat = geo.size.height - 10 - (CGFloat(values[Int(index)]) * scale)
                                let y = yy.isNaN ? 0 : yy
                                p.addLine(to: CGPoint(x: x,y: y))
                            }
                            index += 1
                        }
                        
                        // Close the subpath
                        let x1: CGFloat = 8 + ((geo.size.width - 16) / CGFloat(values.count)) * (index - 1)
                        let x = x1.isNaN ? 0 : x1
                        p.addLine(to: CGPoint(x: x,y: geo.size.height))
                        p.addLine(to: CGPoint(x: 8,y: geo.size.height))
                        p.closeSubpath()
                    }
                }
            )
    }
}

您可以在屏幕截图中看到橙色渐变如何无法正确填充线条下方的所有区域:

enter image description here

解决方法

问题出在这一行:

let y1: CGFloat = geo.size.height - (CGFloat(values[Int(index)]) * scale)

y1 成为整个图的高度。

Starting point of gradient,whose y is the graph's height,circled

你需要减去 10,因为这就是你一直在为你的蓝线和所有渐变的其他点做的事情。

                              /// subtract 10
let y1: CGFloat = geo.size.height - 10 - (CGFloat(values[Int(index)]) * scale)

Starting point of gradient,whose y is the graph's height minus 10 (exactly the place where the blue line starts),circled