❤️ Support my apps ❤️
- Push Hero - pure Swift native macOS application to test push notifications
- PastePal - Pasteboard, note and shortcut manager
- My other apps
❤️❤️😇😍🤘❤️❤️
RoughSwift allows us to easily make shapes in hand drawn, sketchy, comic style in SwiftUI.
- Support iOS, tvOS
- Support all shapes: line, rectangle, circle, ellipse, linear path, arc, curve, polygon, svg path
- Generate
UIBezierPath
forCAShapeLayer
- Easy cusomizations with Options
- Easy composable APIs
- Convenient draw functions
- Platform independant APIs which can easily support new platforms
- Test coverage
- Immutable and type safe data structure
- SVG elliptical arc
There are Example project where you can explore further.
Use generator
in draw
function to specify which shape to render. The returned CALayer
contains the rendered result in correct size
and is updated everytime generator
is instructed.
Here's how to draw a green rectangle
RoughView()
.fill(.yellow)
.fillStyle(.hachure)
.hachureAngle(-41)
.hachureGap(-1)
.fillWeight(-1)
.stroke(.systemTeal)
.strokeWidth(2)
.curveTightness(0)
.curveStepCount(9)
.dashOffset(-1)
.dashGap(-1)
.zigzagOffset(-9)
The beauty of CALayer
is that we can further animate, transform (translate, scale, rotate) and compose them into more powerful shapes.
Options
is used to custimize shape. It is immutable struct and apply to one shape at a time. The following properties are configurable
- maxRandomnessOffset
- toughness
- bowing
- fill
- stroke
- strokeWidth
- curveTightness
- curveStepCount
- fillStyle
- fillWeight
- hachureAngle
- hachureGap
- dashOffset
- dashGap
- zigzagOffset
RoughSwift supports all primitive shapes, including SVG path
- line
- rectangle
- ellipse
- circle
- linearPath
- arc
- curve
- polygon
- path
Most of the time, we use fill
for solid fill color inside shape, stroke
for shape border, and fillStyle
for sketchy fill style.
Available fill styles
- crossHatch
- dashed
- dots
- hachure
- solid
- starBurst
- zigzag
- zigzagLine
Here's how to draw circles in different fill styles. The default fill style is hachure
struct StylesView: View {
var body: some View {
LazyVGrid(columns: [.init(), .init(), .init()], spacing: 12) {
RoughView()
.fill(.red)
.fillStyle(.crossHatch)
.circle()
.frame(width: 100, height: 100)
RoughView()
.fill(.green)
.fillStyle(.dashed)
.circle()
.frame(width: 100, height: 100)
RoughView()
.fill(.purple)
.fillStyle(.dots)
.circle()
.frame(width: 100, height: 100)
RoughView()
.fill(.cyan)
.fillStyle(.hachure)
.circle()
.frame(width: 100, height: 100)
RoughView()
.fill(.orange)
.fillStyle(.solid)
.circle()
.frame(width: 100, height: 100)
RoughView()
.fill(.gray)
.fillStyle(.starBurst)
.circle()
.frame(width: 100, height: 100)
RoughView()
.fill(.yellow)
.fillStyle(.zigzag)
.circle()
.frame(width: 100, height: 100)
RoughView()
.fill(.systemTeal)
.fillStyle(.zigzagLine)
.circle()
.frame(width: 100, height: 100)
}
}
}
SVG shape can be bigger or smaller than the specifed layer size, so RoughSwift scales them to your requested size
. This way we can compose and transform the SVG shape.
struct SVGView: View {
var apple: String {
"M85 32C115 68 239 170 281 192 311 126 274 43 244 0c97 58 146 167 121 254 28 28 40 89 29 108 -25-45-67-39-93-24C176 409 24 296 0 233c68 56 170 65 226 27C165 217 56 89 36 54c42 38 116 96 161 122C159 137 108 72 85 32z"
}
var body: some View {
VStack {
RoughView()
.stroke(.systemTeal)
.fill(.red)
.draw(Path(d: apple))
.frame(width: 300, height: 300)
}
}
}
With all the primitive shapes, we can create more beautiful things. The only limit is your imagination.
Here's how to create chart
struct Chartview: View {
var heights: [CGFloat] {
Array(0 ..< 10).map { _ in CGFloat.random(in: 0 ..< 150) }
}
var body: some View {
HStack {
ForEach(0 ..< 10) { index in
VStack {
Spacer()
RoughView()
.fill(.yellow)
.rectangle()
.frame(height: heights[index])
}
}
}
.padding(.horizontal)
.padding(.bottom, 100)
}
}
Behind the screen, we composes Generator
and Renderer
.
We can instantiate Engine
or use a shared Engine
for memory efficiency, to make Generator
. Every time we instruct Generator
to draw a shape, the engine works hard to figure out information about the sketchy shape in Drawable
.
The name of these concepts follow rough.js
for better code reasoning.
For iOS, there is a Renderer
that can handle Drawable
and transform it into UIBezierPath
and CALayer
. There will be more Renderer
that can render into graphics context, image and for other platforms like macOS and watchOS.
let layer = CALayer()
let size = CGSize(width: 200, heigh: 200)
let renderer = Renderer(layer: layer)
let generator = Engine.shared.generator(size: bounds.size)
let drawable: Drawable = Rectangle(x: 10, y: 10, width: 100, height: 50)
let drawing = generate.generate(drawable: drawable)
renderer.render(drawing: drawing)
Add the following line to the dependencies in your Package.swift
file
.package(url: "https://github.com/onmyway133/RoughSwift"),
Khoa Pham, onmyway133@gmail.com
- rough for the generator that powers RoughSwift. All the hard work is done via rough in JavascriptCore.
- SVGPath for constructing UIBezierPath from SVG path
We would love you to contribute to RoughSwift, check the CONTRIBUTING file for more info.
RoughSwift is available under the MIT license. See the LICENSE file for more info.