I have two lines with a SwiftUI Path (see image). I want to connect these with a perfect smooth arc so it looks like the speech bubble tail in this image.
I'm struggling to get it to connect smoothly. As you can see in my image, it's almost perfect but the first connection between the line and the arc (on the right) isn't perfect.
Any ideas what I'm doing wrong? I even drew this out on paper and I can't see which value isn't correct.
struct SpeechBubbleTail: Shape {
private let radius: CGFloat
init(radius: CGFloat = 10) {
self.radius = radius
}
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX + radius, y: rect.maxY - radius))
path.addArc(
center: CGPoint(x: rect.minX + ((radius / 2) * 1.5), y: rect.maxY - (radius * 1.5)),
radius: (radius / 2),
startAngle: Angle(degrees: 45),
endAngle: Angle(degrees: 180),
clockwise: false
)
path.addLine(to: CGPoint(x: rect.minX + ((radius / 2) * 0.5), y: rect.minY))
}
}
}
Let us rename radius
to diameter
instead, since you used radius / 2
as the radius...
So you have two lines that you want to join with a circular arc.
(x: rect.maxX, y: rect.minY)
to (x: rect.minX + diameter, y: rect.maxY - diameter)
, and,rect.minX + diameter / 4
Observation: The centre of the circle cannot be at (x: rect.minX + ((diameter / 2) * 1.5), y: rect.maxY - (diameter * 1.5)
.
Demo on Desmos
The point highlighted is where the diagonal line ends. The circle does not go through it at all.
Don't worry though, there is a convenient addArc(tangent1End:tangent2End:radius:transform:)
API that takes 2 lines and a radius, and produces a circle that is tangent to both!
path.move(to: CGPoint(x: rect.maxX, y: rect.minY))
// addArc will draw this line automatically, so you don't even need to draw it yourself
// path.addLine(to: CGPoint(x: rect.minX + diameter, y: rect.maxY - diameter))
path.addArc(
// tangent1End is the intersection of the two lines when you extend them
tangent1End: CGPoint(x: rect.minX + diameter / 4, y: rect.maxY - diameter / 4),
tangent2End: CGPoint(x: rect.minX + ((diameter / 2) * 0.5), y: rect.minY),
radius: diameter / 2)
path.addLine(to: CGPoint(x: rect.minX + ((diameter / 2) * 0.5), y: rect.minY))
This post has a very nice figure showing how this overload works.
See also this post on Math.SE for the mathematics behind this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With