Search code examples
swiftuidrawshapes

Custom cross-hatched background shape or view in SwiftUI


I am trying to create a shaded cross-hatched. But so far I can do it by adding a Image.

How can I create a custom view in which lines will be drawn and not filled with a Image?

import SwiftUI

struct ContentView: View {

    var body: some View {
        ZStack {
            Image("lineFilledBG").resizable().clipShape(Circle())
            Circle().stroke()
            Circle().foregroundColor(.yellow).opacity(0.3)
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

This is how it looks now. Want lines to draw on top of another view or shape, without adding opacity and image pattern filling.

Cross-hatched shape


Solution

  • Thank to Cenk Bilgen for stripe pattern. Tweaked a bit so that you can rotate the hatch for any shape.

    import SwiftUI
    import CoreImage.CIFilterBuiltins
    
    extension CGImage {
    
        static func generateStripePattern(
            colors: (UIColor, UIColor) = (.clear, .black),
            width: CGFloat = 6,
            ratio: CGFloat = 1) -> CGImage? {
    
        let context = CIContext()
        let stripes = CIFilter.stripesGenerator()
        stripes.color0 = CIColor(color: colors.0)
        stripes.color1 = CIColor(color: colors.1)
        stripes.width = Float(width)
        stripes.center = CGPoint(x: 1-width*ratio, y: 0)
        let size = CGSize(width: width, height: 1)
    
        guard
            let stripesImage = stripes.outputImage,
            let image = context.createCGImage(stripesImage, from: CGRect(origin: .zero, size: size))
        else { return nil }
        return image
      }
    }
    
    extension Shape {
    
        func stripes(angle: Double = 45) -> AnyView {
            guard
                let stripePattern = CGImage.generateStripePattern()
            else { return AnyView(self)}
    
            return AnyView(Rectangle().fill(ImagePaint(
                image: Image(decorative: stripePattern, scale: 1.0)))
            .scaleEffect(2)
            .rotationEffect(.degrees(angle))
            .clipShape(self))
        }
    }
    

    And usage

    struct ContentView: View {
    
        var body: some View {
            VStack {
                Rectangle()
                    .stripes(angle: 30)
                Circle().stripes()
                Capsule().stripes(angle: 90)
            }
        }
    }
    

    Output image link