Home > Enterprise >  How do I build a trapezoidal sunburst?
How do I build a trapezoidal sunburst?

Time:12-21

I am wanting to build a trapezoidal sunburst effect in SwiftUI. My designs requirements are a static image, but I need something more reusable and dynamic, because I can't replicate the view as an image in SwiftUI without some incredibly hacky workarounds. My solution is to rebuild this view, natively, rather than relying on the .png asset.

The View to be replicated.

enter image description here

The view I have now.

enter image description here

The code behind what I have now.

struct SunRay: Shape {
    func path(in rect: CGRect) -> Path {
        Path { path in
            path.move(to: CGPoint(x: rect.width * 0.4, y: rect.height))
            path.addLine(to: CGPoint(x: rect.width  * 0.6, y: rect.height))
            path.addLine(to: CGPoint(x: rect.width * 0.8, y: 0))
            path.addLine(to: CGPoint(x: rect.width * 0.2, y: 0))
        }
    }
}

struct RadialSunburstView: View {
    var body: some View {
        ZStack {
            HStack(spacing: -8) {
                Group {
                    SunRay().rotation(Angle(degrees: -40))
                    SunRay().rotation(Angle(degrees: -30))
                    SunRay().rotation(Angle(degrees: -20))
                    SunRay().rotation(Angle(degrees: -10))
                    SunRay()
                    SunRay().rotation(Angle(degrees: 10))
                    SunRay().rotation(Angle(degrees: 20))
                    SunRay().rotation(Angle(degrees: 30))
                    SunRay().rotation(Angle(degrees: 40))
                }
            }
            .frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
            .foregroundColor(.white.opacity(0.05))
            .clipped()
            .background (
                LinearGradient.blueGradient
                    .frame(height: UIScreen.main.bounds.size.height * 0.15)
            )
        }
        .frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height * 0.15)
        .clipped()
        .cornerRadius(20)
    }
}

As you can see, my code is closeish? I'm not really sure if I'd call it a win at this point. The largest issue I have right now is that when I attempt to use it in another view, with RadialSunburstView() I can't actually add any horizontal padding to it. Which I'm sure is a result of my statically defined widths, which really needs to be more dynamic. How do I properly implement something like this, such that I end up with a view that I can reframe, pad, etc. as if it were something like a Rectangle?

CodePudding user response:

That was fun :) I suggest to draw the whole beam structure as a Shape. Then you can fill and clip the shape as needed, and also add padding etc.

I added two properties to define nr. of beams and relative center offset.

enter image description here

struct ContentView: View {
    
    @State private var beams: Double = 10
    @State private var offset: Double = 0.3

    var body: some View {
        
        VStack {
            Sunburst(beams: beams, centerOffset: offset)
                .fill(.orange)
                .background(.blue)
                .clipShape(RoundedRectangle(cornerRadius: 12))
            
                .frame(height: 200)
            
            Divider()
                .padding()
            
            Text("Nr of beams")
            Slider(value: $beams, in: 1.0...100.0)
            
            Text("Offset of center")
            Slider(value: $offset, in: 0...1)
        }
        .padding()
    }
}


struct Sunburst: Shape {
    
    let beams: Double // nr of beams
    let centerOffset: Double // relative offset of centerpoint downwards, relative to height
    
    func path(in rect: CGRect) -> Path {
        
        let step = 180.0 / Double(beams)
        let centerOffsetAbs = rect.height * centerOffset
        
        let center = CGPoint(x: rect.width/2, y: rect.height   centerOffsetAbs)
        let radius = max(rect.height, rect.width) * (1.5   centerOffset)
        
        return Path { path in
            path.move(to: center)
            for angle in stride(from: step / 2, to: 180, by: 2 * step) {
                path.addLine(to: CGPoint(x: center.x   cos(angle * Double.pi / 180) * radius, y: center.y - sin(angle * Double.pi / 180)  * radius ))
                path.addLine(to: CGPoint(x: center.x   cos((angle   step) * Double.pi / 180) * radius, y: center.y - sin((angle   step) * Double.pi / 180)  * radius ))
                path.addLine(to: center)
            }
        }
    }
}
  • Related