I would like to know why this custom shape, which is just a letter template takes up the entire space of the view?? I never drew a line that required that much vertical space so I'm wondering what is going on.
Here is an example of what it looks like.
You can see that the image is pretty much doubled vertically.
Here is the code of the shape with its Parent View
import SwiftUI
struct ParentView: View {
var body: some View {
VStack {
Spacer()
LetterPreviewShape()
}
}
}
struct LetterPreviewShape: Shape {
func path(in rect: CGRect) -> Path {
let startingPoint = CGPoint(x: rect.minX, y: rect.minY)
let paragraphStartingPoint = CGPoint(x: rect.maxX * 0.1, y: rect.maxY * 0.1)
var path = Path()
//Letter outline
path.move(to: startingPoint)
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.midY))
path.closeSubpath()
//Greeting Line
path.move(to: CGPoint(x: rect.maxX * 0.1, y: rect.maxY * 0.07))
path.addLine(to: CGPoint(x: rect.maxX * 0.3, y: rect.maxY * 0.07))
//Body Strokes
path.move(to: paragraphStartingPoint)
path.addLine(to: CGPoint(x: rect.maxX * 0.9, y: rect.maxY * 0.1))
path.move(to: CGPoint(x: rect.maxX * 0.1, y: rect.maxY * 0.11))
path.addLine(to: CGPoint(x: rect.maxX * 0.9, y: rect.maxY * 0.11))
path.move(to: CGPoint(x: rect.maxX * 0.1, y: rect.maxY * 0.12))
path.addLine(to: CGPoint(x: rect.maxX * 0.9, y: rect.maxY * 0.12))
path.move(to: CGPoint(x: rect.maxX * 0.1, y: rect.maxY * 0.13))
path.addLine(to: CGPoint(x: rect.maxX * 0.9, y: rect.maxY * 0.13))
path.move(to: CGPoint(x: rect.maxX * 0.1, y: rect.maxY * 0.14))
path.addLine(to: CGPoint(x: rect.maxX * 0.9, y: rect.maxY * 0.14))
path.move(to: CGPoint(x: rect.maxX * 0.1, y: rect.maxY * 0.15))
path.addLine(to: CGPoint(x: rect.maxX * 0.9, y: rect.maxY * 0.15))
path.move(to: CGPoint(x: rect.maxX * 0.1, y: rect.maxY * 0.16))
path.addLine(to: CGPoint(x: rect.maxX * 0.9, y: rect.maxY * 0.16))
//Signature Line
path.move(to: CGPoint(x: rect.maxX * 0.7, y: rect.maxY * 0.18))
path.addLine(to: CGPoint(x: rect.maxX * 0.9, y: rect.maxY * 0.18))
path.move(to: CGPoint(x: rect.maxX * 0.7, y: rect.maxY * 0.19))
path.addLine(to: CGPoint(x: rect.maxX * 0.9, y: rect.maxY * 0.19))
return path
}
}
Shapes in SwiftUI always fill the available space. It doesn't have anything to do with where you draw the content.
You can see this behavior replicated with the non-custom system shapes as well -- if you you just use a Rectangle or Circle, they will also fill the available space.
To constrain it, give it a frame -- possible in conjunction with a GeometryReader if you need to base it on the available size.
For example:
struct ContentView : View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer().frame(height: geometry.size.height / 2)
Rectangle().fill(Color.red).frame(height: geometry.size.height / 2)
}
}
}
}
Shape does not have own frame, so you have to give it one externally (or it fills everything available), like
struct ParentView: View {
var body: some View {
VStack {
Spacer()
LetterPreviewShape()
.frame(height: 200) // << this goes in `func path(in rect: CGRect) -> Path`
}
}
}
Related
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.
Is there a way in SwiftUI to union two shapes so that they cast a unified shadow. I have tried various combinations and modifiers but don't seem to be able to achieve the look I am after. Any insight would be much appreciated.
struct CardView: View {
var body: some View {
Group {
RoundedRectangle(cornerRadius: 20)
.fill(Color.orange)
.frame(width: 200, height: 250)
.zIndex(0)
Circle()
.trim(from: 0.5, to: 1)
.fill(Color.orange)
.frame(width: 100, height: 100)
.offset(y: -125)
.zIndex(1)
}.shadow(color: Color.black, radius: 10, x: 0, y: 0)
}
}
This is what I get, and what I am after ...
NOTE: zIndex was just something I tried, i.e. both shapes having the same zIndex etc. its also a quick way to reorder things without having to move the shapes within the container.
Here is possible solution. Tested with Xcode 11.4 / iOS 13.4
struct CardView: View {
var body: some View {
VStack(spacing: 0) {
Circle()
.trim(from: 0.5, to: 1)
.fill(Color.orange)
.frame(width: 100, height: 100)
.offset(x: 0, y: 50)
RoundedRectangle(cornerRadius: 20)
.fill(Color.orange)
.frame(width: 200, height: 250)
}
.compositingGroup()
.shadow(color: Color.primary, radius: 10, x: 0, y: 0)
}
}
I think the correct answer is #Asperi's .compositingGroup as documented here (https://developer.apple.com/documentation/swiftui/group/3284789-compositinggroup) but I'd like to leave a different working version because there are some use cases where you may not be able or want to use .compositingGroup.
So let's look at .background, you can copy and paste the code to see in the simulator:
import SwiftUI
struct AnyShapedView: View {
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 20)
.fill(Color.orange)
.frame(width: 200, height: 250)
.zIndex(0)
Circle()
.trim(from: 0.5, to: 1)
.fill(Color.orange)
.frame(width: 100, height: 100)
.offset(y: -125)
.zIndex(1)
}
}
}
struct ContentView: View {
var body: some View {
AnyShapedView()
.background(
AnyShapedView()
.shadow(color: Color.black, radius: 10, x: 0, y: 0)
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The output:
The version above is not the best practice, but nevertheless, try to understand how shapes and cropping work, as in some use cases it might come useful.
Draw the shape:
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let width = rect.size.width
let height = rect.size.height
path.move(to: CGPoint(x: 0.89995*width, y: 0.16667*height))
path.addLine(to: CGPoint(x: 0.75*width, y: 0.16667*height))
path.addCurve(to: CGPoint(x: 0.5*width, y: 0), control1: CGPoint(x: 0.75*width, y: 0.07463*height), control2: CGPoint(x: 0.63805*width, y: 0))
path.addCurve(to: CGPoint(x: 0.25*width, y: 0.16667*height), control1: CGPoint(x: 0.36195*width, y: 0), control2: CGPoint(x: 0.25*width, y: 0.07463*height))
path.addLine(to: CGPoint(x: 0.10005*width, y: 0.16667*height))
path.addCurve(to: CGPoint(x: 0, y: 0.23337*height), control1: CGPoint(x: 0.0448*width, y: 0.16667*height), control2: CGPoint(x: 0, y: 0.19653*height))
path.addLine(to: CGPoint(x: 0, y: 0.93333*height))
path.addCurve(to: CGPoint(x: 0.10005*width, y: 1.00003*height), control1: CGPoint(x: 0, y: 0.97017*height), control2: CGPoint(x: 0.0448*width, y: 1.00003*height))
path.addLine(to: CGPoint(x: 0.89995*width, y: 1.00003*height))
path.addCurve(to: CGPoint(x: width, y: 0.93333*height), control1: CGPoint(x: 0.9552*width, y: 1.00003*height), control2: CGPoint(x: width, y: 0.97017*height))
path.addLine(to: CGPoint(x: width, y: 0.23337*height))
path.addCurve(to: CGPoint(x: 0.89995*width, y: 0.16667*height), control1: CGPoint(x: width, y: 0.19653*height), control2: CGPoint(x: 0.9552*width, y: 0.16667*height))
path.closeSubpath()
return path
}
}
And custom color, shadow and size in your parent view:
CustomShape()
.fill(.orange)
.shadow(color: Color.primary, radius: 10, x: 0, y: 0)
.frame(width: 300, height: 400)
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)
}
}
}