I have made a custom 'slanted' rectangle that I would like to become a rounded 'slanted' rectangle. Here is a picture so far:
struct SlantedRectangle: Shape {
func path(in rect: CGRect) -> Path {
return Path { path in
path.move(to: CGPoint(x: rect.width, y: 30))
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.addLine(to: CGPoint(x: 0, y: rect.height))
path.addLine(to: CGPoint(x: 0, y: 0))
}
}
}
How can I round the top left and top right corners?
For rounded corners, you can use addArc method of Path.
struct SlantedRectangle: Shape {
let slanted: CGFloat
let radius: CGFloat
func path(in rect: CGRect) -> Path {
return Path { path in
path.move(to: CGPoint(x: rect.width - radius, y: slanted))
path.addArc(center: CGPoint(x: rect.width - radius, y: slanted + radius), radius: radius, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false)
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.addLine(to: CGPoint(x: 0, y: rect.height))
path.addLine(to: CGPoint(x: 0, y: radius))
path.addArc(center: CGPoint(x: radius, y: radius), radius: radius, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
path.closeSubpath()
}
}
}
Now create your SlantedRectangle like this.
SlantedRectangle(slanted: 30, radius: 30)
Preview
I was using PaintCode app for line to code.
But I can't use this code.. Because this code is not responsive.. I don't want button sizes to be static.
I don't want to give a static number. How can I make responsive button with Custom Path ? You can download SVG file below
struct BarButton: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX - 14, y: .zero))
path.addCurve(to: CGPoint(x: 121.63, y: 11.28), control1: CGPoint(x: 116.2, y: -0.69), control2: CGPoint(x: 121.6, y: 4.65))
path.addCurve(to: CGPoint(x: 121.07, y: 14.95), control1: CGPoint(x: 121.63, y: 12.53), control2: CGPoint(x: 121.45, y: 13.76))
path.addLine(to: CGPoint(x: 116.14, y: 30.58))
path.addCurve(to: CGPoint(x: 104.63, y: 38.97), control1: CGPoint(x: 114.56, y: 35.6), control2: CGPoint(x: 109.89, y: 39))
path.addLine(to: CGPoint(x: 11.02, y: 38.44))
path.addCurve(to: CGPoint(x: -0.91, y: 26.44), control1: CGPoint(x: 4.42, y: 38.4), control2: CGPoint(x: -0.91, y: 33.04))
path.addLine(to: CGPoint(x: -0.91, y: 11.81))
path.addCurve(to: CGPoint(x: 11.04, y: -0.19), control1: CGPoint(x: -0.91, y: 5.21), control2: CGPoint(x: 4.43, y: -0.16))
return path
}
}
struct BarButton_Previews: PreviewProvider {
static var previews: some View {
BarButton()
.frame(width: 125, height: 40)
// .background(Color.orange)
}
}
WeTransfer File Link for SVG:
https://wetransfer.com/downloads/3cc5bab61ce0c676c95c3a8fbd3bd4cf20220506201936/9d9ce6
Let's say I have a Shape:
struct ShapeA: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: 100, y: 100))
path.addQuadCurve(to: CGPoint(x: 110, y: 210), control: CGPoint(x: 130, y: 200))
path.addQuadCurve(to: CGPoint(x: 180, y: 240), control: CGPoint(x: 140, y: 240))
path.addQuadCurve(to: CGPoint(x: 200, y: 250), control: CGPoint(x: 150, y: 250))
path.closeSubpath()
}
}
}
and a single CGPoint: CGPoint(x: 100, y: 100). How can I check whether this CGPoint is in the ShapeA (or any other Shape)
path.contains(point)
(Create path by calling .path(in rect:) on a ShapeA instance.)
For self-intersecting paths, you should look at the eoFill option for dealing with even-odd fill rules.
I'm trying to convert some SVG data (some shapes for chinese characters) to an UIBezierPath. Each character consists of multiple UIBezierPath. This is an example for one UIBezierPath:
var mypath: UIBezierPath {
let bp = UIBezierPath()
bp.move(to: CGPoint(x: 147, y: 32))
bp.addQuadCurve(to: CGPoint(x: 203, y: 102), controlPoint: CGPoint(x: 181, y: 74))
bp.addQuadCurve(to: CGPoint(x: 271, y: 189), controlPoint: CGPoint(x: 242, y: 166))
bp.addQuadCurve(to: CGPoint(x: 274, y: 217), controlPoint: CGPoint(x: 287, y: 204))
bp.addQuadCurve(to: CGPoint(x: 229, y: 235), controlPoint: CGPoint(x: 258, y: 229))
bp.addQuadCurve(to: CGPoint(x: 193, y: 235), controlPoint: CGPoint(x: 204, y: 241))
bp.addQuadCurve(to: CGPoint(x: 190, y: 219), controlPoint: CGPoint(x: 183, y: 231))
bp.addQuadCurve(to: CGPoint(x: 143, y: 71), controlPoint: CGPoint(x: 199, y: 195))
bp.addQuadCurve(to: CGPoint(x: 125, y: 33), controlPoint: CGPoint(x: 134, y: 55))
bp.addCurve(to: CGPoint(x: 147, y: 32), controlPoint1: CGPoint(x: 113, y: 5), controlPoint2: CGPoint(x: 128, y: 9))
bp.close()
return bp
}
Since my final result consists of multiple UIBezierPaths I'm trying to scale them:
extension UIBezierPath {
static func calculateBounds(paths: [UIBezierPath]) -> CGRect {
let myPaths = UIBezierPath()
for path in paths {
myPaths.append(path)
}
return (myPaths.bounds)
}
}
struct ShapeView: Shape {
let bezier: UIBezierPath
let pathBounds: CGRect
func path(in rect: CGRect) -> Path {
let pointScale = (rect.width >= rect.height) ?
max(pathBounds.height, pathBounds.width) :
min(pathBounds.height, pathBounds.width)
let pointTransform = CGAffineTransform(scaleX: 1/pointScale, y: 1/pointScale)
let path = Path(bezier.cgPath).applying(pointTransform)
let multiplier = min(rect.width, rect.height)
let transform = CGAffineTransform(scaleX: multiplier, y: multiplier)
return path.applying(transform)
}
}
View:
struct Drawer: View {
let pathBounds = UIBezierPath.calculateBounds(paths: [mypath]) // to simplify I just add only one shape. Usually there are multiple
var body: some View {
ZStack{
ShapeView(bezier: mypath, pathBounds: pathBounds)
.background(Color.red)
}
}
}
This is the result that is displayed:
As you can see: The shape is not centered and it's bigger than the screen. What am I doing wrong and how can I achieve that the shapes will be centered and fit perfectly in the entire screen?
I'm not sure about what's that path transforming logic is about (if even you need it the defect is there), but just applying shape like
struct ShapeView: Shape {
let bezier: UIBezierPath
let pathBounds: CGRect
func path(in rect: CGRect) -> Path {
Path(bezier.cgPath) // << simple conversion
}
}
gives this (probably what you did expect):
I really like the Timer symbol in the SF Symbol collection, and I would like to animate it in SwiftUI. There are a couple of different ways to do this, the easy one is to make an image of the face, and another image of the hand, slap them in a ZStack, and then animate the Image("hand") view.
I could also draw the face and the hand as individual shapes and stack and animate them the same way, like this:
struct TimerFace: Shape {
func path(in rect: CGRect) -> Path {
var facePath = Path()
facePath.move(to: CGPoint(x: 50, y: 100))
facePath.addCurve(to: CGPoint(x: 100, y: 50), control1: CGPoint(x: 77.61, y: 100), control2: CGPoint(x: 100, y: 77.61))
facePath.addCurve(to: CGPoint(x: 50.05, y: 0), control1: CGPoint(x: 100, y: 22.44), control2: CGPoint(x: 77.66, y: 0))
facePath.addCurve(to: CGPoint(x: 44.74, y: 5.12), control1: CGPoint(x: 46.75, y: 0), control2: CGPoint(x: 44.74, y: 1.96))
facePath.addLine(to: CGPoint(x: 44.74, y: 22.01))
facePath.addCurve(to: CGPoint(x: 49.38, y: 26.79), control1: CGPoint(x: 44.74, y: 24.74), control2: CGPoint(x: 46.65, y: 26.79))
facePath.addCurve(to: CGPoint(x: 54.02, y: 22.01), control1: CGPoint(x: 52.11, y: 26.79), control2: CGPoint(x: 54.02, y: 24.74))
facePath.addLine(to: CGPoint(x: 54.02, y: 11.24))
facePath.addCurve(to: CGPoint(x: 88.76, y: 50), control1: CGPoint(x: 73.68, y: 13.3), control2: CGPoint(x: 88.76, y: 29.81))
facePath.addCurve(to: CGPoint(x: 50, y: 88.8), control1: CGPoint(x: 88.76, y: 71.44), control2: CGPoint(x: 71.58, y: 88.8))
facePath.addCurve(to: CGPoint(x: 11.24, y: 50), control1: CGPoint(x: 28.47, y: 88.8), control2: CGPoint(x: 11.2, y: 71.44))
facePath.addCurve(to: CGPoint(x: 19.62, y: 26.08), control1: CGPoint(x: 11.29, y: 41.05), control2: CGPoint(x: 14.4, y: 32.63))
facePath.addCurve(to: CGPoint(x: 19.67, y: 17.8), control1: CGPoint(x: 21.67, y: 23.06), control2: CGPoint(x: 22.11, y: 20.19))
facePath.addCurve(to: CGPoint(x: 11, y: 19.04), control1: CGPoint(x: 17.32, y: 15.5), control2: CGPoint(x: 13.44, y: 15.74))
facePath.addCurve(to: CGPoint(x: 0, y: 50), control1: CGPoint(x: 4.16, y: 27.56), control2: CGPoint(x: 0, y: 38.37))
facePath.addCurve(to: CGPoint(x: 50, y: 100), control1: CGPoint(x: 0, y: 77.61), control2: CGPoint(x: 22.39, y: 100))
return facePath
}
}
struct TimerHand: Shape {
func path(in rect: CGRect) -> Path {
var handPath = Path()
handPath.move(to: CGPoint(x: 7.93, y: 40))
handPath.addCurve(to: CGPoint(x: 15.8, y: 29.75), control1: CGPoint(x: 13.5, y: 39.9), control2: CGPoint(x: 16.88, y: 35.25))
handPath.addLine(to: CGPoint(x: 10.76, y: 2.77))
handPath.addCurve(to: CGPoint(x: 5.22, y: 2.77), control1: CGPoint(x: 10.03, y: -0.92), control2: CGPoint(x: 5.95, y: -0.92))
handPath.addLine(to: CGPoint(x: 0.21, y: 29.72))
handPath.addCurve(to: CGPoint(x: 7.93, y: 40), control1: CGPoint(x: -0.9, y: 35.25), control2: CGPoint(x: 2.44, y: 39.93))
return handPath
}
}
struct TimerView: View {
#State private var rotation = 0.0
var body: some View {
VStack {
ZStack {
TimerFace().frame(CGSize(width: 100,height: 100), alignment: .center)
TimerHand().frame(CGSize(width: 16,height: 64), alignment: .center)
.rotationEffect(.degrees(rotation), anchor: .center)
}
Slider(value: $rotation, in: 0...360, step: 1.0)
}
}
}
It would look like this
I do think this is a bit like "cheating". Any suggestions on how to do this the "proper" way, instead of animating the hand frame, animating the paths themselves?
You can put the rotation inside the frame:
struct TimerHand: Shape {
var degree : Double
func path(in rect: CGRect) -> Path {
var handPath = Path()
handPath.move(to: CGPoint(x: 7.93, y: 40))
handPath.addCurve(to: CGPoint(x: 15.8, y: 29.75), control1: CGPoint(x: 13.5, y: 39.9), control2: CGPoint(x: 16.88, y: 35.25))
handPath.addLine(to: CGPoint(x: 10.76, y: 2.77))
handPath.addCurve(to: CGPoint(x: 5.22, y: 2.77), control1: CGPoint(x: 10.03, y: -0.92), control2: CGPoint(x: 5.95, y: -0.92))
handPath.addLine(to: CGPoint(x: 0.21, y: 29.72))
handPath.addCurve(to: CGPoint(x: 7.93, y: 40), control1: CGPoint(x: -0.9, y: 35.25), control2: CGPoint(x: 2.44, y: 39.93))
return handPath.rotation(.degrees(degree), anchor: .center).path(in: rect)
}
}
struct TimerView: View {
#State private var rotation = 0.0
var body: some View {
VStack {
ZStack {
TimerFace().frame(width: 100,height: 100, alignment: .center)
TimerHand(degree: rotation).frame(width: 16,height: 64, alignment: .center)
}
Slider(value: $rotation, in: 0...360, step: 1.0)
}
}
}