skip to Main Content

In Apple SwiftUI Tutorial: Drawing paths and shapes,after completing Section 2 Step 8, the two top and bottom corners are cut off from the badge. The shape is perfectly normal until adding the gradient (Section 2 Step 8), but then the corners are cut off.

Image of the preview in Xcode

This is my code, which I double-checked with the tutorial:

import SwiftUI

struct BadgeBackground: View {
    var body: some View {
        GeometryReader { geometry in
            Path { path in
                var width: CGFloat = min(geometry.size.width, geometry.size.height)
                let height = width
                let xScale: CGFloat = 0.832
                let xOffset = (width * (1.0 - xScale)) / 2.0
                width *= xScale
                path.move(
                    to: CGPoint(
                        x: width * 0.95 + xOffset,
                        y: height * (0.20 + HexagonParameters.adjustment)
                    )
                )
                
                HexagonParameters.segments.forEach { segment in
                    path.addLine(
                        to: CGPoint(
                            x: width * segment.line.x + xOffset,
                            y: height * segment.line.y
                        )
                    )
                    
                    path.addQuadCurve(
                        to: CGPoint(
                            x: width * segment.curve.x + xOffset,
                            y: height * segment.curve.y
                        ),
                        control: CGPoint(
                            x: width * segment.control.x + xOffset,
                            y: height * segment.control.y
                        )
                    )
                }
            }
            .fill(.linearGradient(
                Gradient(colors: [Self.gradientStart, Self.gradientEnd]),
                startPoint: UnitPoint(x: 0.5, y: 0),
                endPoint: UnitPoint(x: 0.5, y: 0.6)
            ))
        }
        .aspectRatio(1, contentMode: .fit)
    }
    static let gradientStart = Color(red: 239.0 / 255, green: 120.0 / 255, blue: 221.0 / 255)
    static let gradientEnd = Color(red: 239.0 / 255, green: 172.0 / 255, blue: 120.0 / 255)
}

#Preview {
    BadgeBackground()
}

After completing Section 2, I noticed that the corners were missing. I retraced my steps and discovered that the corners only got cut off when I add the gradient in Step 8.

2

Answers


  1. It gets interesting if you add an additional .fill() statement before the linear gradient:

    Path { path in
        // ...
    }
    .fill() // 👈 HERE
    .fill(.linearGradient(
        // ...
    ))
    

    This is what you see:

    Screenshot

    A possible explanation is as follows:

    • The gradient in this case is a vertical gradient that starts from (x: 0.5, y: 0), which is the center of the top edge, and goes to (x: 0.5, y: 0.6), which is 6/10 of the height.
    • To determine how the gradient fits the shape, the gradient-filling algorithm needs to know the absolute positions of the min and max points in the shape.
    • It seems that the way it is finding the min and max points is simply by examining the coordinates of the points in the path.
    • Unfortunately, doing it this way does not take the control points of the quad curves into consideration. The control points cause the curves in the path to go higher than the minimum y-value and lower than the maximum y-value.

    → So this would seem to be a bug.

    If you are interested in a workaround, you could use the solid-fill form as a .mask over a LinearGradient. However, even here it is necessary to add .geometryGroup() to the filled form, otherwise the same error is seen.

    LinearGradient(
        colors: [Self.gradientStart, Self.gradientEnd],
        startPoint: .top,
        endPoint: UnitPoint(x: 0.5, y: 0.6)
    )
    .mask {
        GeometryReader { geometry in
            Path { path in
                // ... as before
            }
            .fill()
        }
        .geometryGroup() // ⚠️ IMPORTANT
    }
    .aspectRatio(1, contentMode: .fit)
    

    Screenshot

    Login or Signup to reply.
  2. Use below code, it is working perfectly and drawing smooth shape as per your requirements without any top or bottom area cut.

    Result:
    enter image description here

    struct BadgeBackground: View {
        var body: some View {
            GeometryReader { geometry in
                Path { path in
                    var width: CGFloat = min(geometry.size.width, geometry.size.height)
                    let height = width
                    let xScale: CGFloat = 0.832
                    let xOffset = (width * (1.0 - xScale)) / 2.0
                    width *= xScale
                    path.move(
                        to: CGPoint(
                            x: width * 0.95 + xOffset,
                            y: height * (0.20 + HexagonParameters.adjustment)
                        )
                    )
                    
                    HexagonParameters.segments.forEach { segment in
                        path.addLine(
                            to: CGPoint(
                                x: width * segment.line.x + xOffset,
                                y: height * segment.line.y
                            )
                        )
                        
                        path.addQuadCurve(
                            to: CGPoint(
                                x: width * segment.curve.x + xOffset,
                                y: height * segment.curve.y
                            ),
                            control: CGPoint(
                                x: width * segment.control.x + xOffset,
                                y: height * segment.control.y
                            )
                        )
                    }
                }
                .fill(.linearGradient(
                    Gradient(colors: [Self.gradientStart, Self.gradientEnd]),
                    startPoint: UnitPoint(x: 0.5, y: 0),
                    endPoint: UnitPoint(x: 0.5, y: 0.6)
                ))
            }
            .aspectRatio(1, contentMode: .fit)
        }
        static let gradientStart = Color(red: 239.0 / 255, green: 120.0 / 255, blue: 221.0 / 255)
        static let gradientEnd = Color(red: 239.0 / 255, green: 172.0 / 255, blue: 120.0 / 255)
    }
    
    #Preview {
        BadgeBackground()
    }
    
    struct HexagonParameters {
        struct Segment {
            let line: CGPoint
            let curve: CGPoint
            let control: CGPoint
        }
    
    
        static let adjustment: CGFloat = 0.085
    
    
        static let segments = [
            Segment(
                line:    CGPoint(x: 0.60, y: 0.05),
                curve:   CGPoint(x: 0.40, y: 0.05),
                control: CGPoint(x: 0.50, y: 0.00)
            ),
            Segment(
                line:    CGPoint(x: 0.05, y: 0.20 + adjustment),
                curve:   CGPoint(x: 0.00, y: 0.30 + adjustment),
                control: CGPoint(x: 0.00, y: 0.25 + adjustment)
            ),
            Segment(
                line:    CGPoint(x: 0.00, y: 0.70 - adjustment),
                curve:   CGPoint(x: 0.05, y: 0.80 - adjustment),
                control: CGPoint(x: 0.00, y: 0.75 - adjustment)
            ),
            Segment(
                line:    CGPoint(x: 0.40, y: 0.95),
                curve:   CGPoint(x: 0.60, y: 0.95),
                control: CGPoint(x: 0.50, y: 1.00)
            ),
            Segment(
                line:    CGPoint(x: 0.95, y: 0.80 - adjustment),
                curve:   CGPoint(x: 1.00, y: 0.70 - adjustment),
                control: CGPoint(x: 1.00, y: 0.75 - adjustment)
            ),
            Segment(
                line:    CGPoint(x: 1.00, y: 0.30 + adjustment),
                curve:   CGPoint(x: 0.95, y: 0.20 + adjustment),
                control: CGPoint(x: 1.00, y: 0.25 + adjustment)
            )
        ]
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search