SwiftUI, multiple shapes with unified shadow? - swiftui

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)

Related

Divide Rectangle with slanted divider with different views on each side?

I am trying to split a rectangle into two sides with a slanted divider with background web images like this:
I tried using a triangle but having trouble dividing it in the correct direction and angle. Below is my working code I'm trying:
struct ContentView: View {
var body: some View {
VStack {
VStack {
Text("VS")
.bold()
.padding(4)
.background(.thinMaterial.opacity(0.8))
.clipShape(Circle())
}
.frame(maxWidth: .infinity, minHeight: 100)
.padding()
.background(
AsyncImage(url: URL(string: "https://picsum.photos/id/1037/1600/400"), transaction: Transaction(animation: .easeInOut)) { phase in
switch phase {
case .empty:
ProgressView()
case let .success(image):
image
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity)
.transition(.opacity)
default:
Color.red
.transition(.opacity)
}
}
.overlay(
AsyncImage(url: URL(string: "https://picsum.photos/id/1041/1600/400"), transaction: Transaction(animation: .easeInOut)) { phase in
switch phase {
case .empty:
ProgressView()
case let .success(image):
image
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity)
.transition(.opacity)
default:
Color.blue
.transition(.opacity)
}
}
.clipShape(Triangle())
)
)
.cornerRadius(4)
}
.padding()
}
}
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
return path
}
}
This is what it looks like:
How can I accomplish the "back-slash" divider like in my first screenshot?
You just need to adjust the points in your Triangle to fit the shape that you desire. If you look at each point you currently have, you'll see that it matches the shape that it draws.
By adjusting a couple of the points, you can get the desired result:
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.maxX * 0.33, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX * 0.66, y: rect.maxY))
return path
}
}

I can't draw responsive custom button with Path in SwiftUI ( I used Paint Code)

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

SwiftUI inverted mask

I'm trying to create an view like in the screenshot with swiftui. The top frame should be light, other places should be dark.
SwiftUI add inverted mask
I tried to change it over the codes here, but I couldn't. how can i do it?
where I've come so far :
black color not show full screen and mask not working
func HoleShapeMask(in rect: CGRect) -> Path {
return
Path { path in
path.move(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
}
}
struct ContentView: View {
#State var rect :CGRect = .zero
var body: some View {
GeometryReader{g in
Text("deneme")
.foregroundColor(.blue)
.frame(width: 124, height: 100, alignment: .center)
.background(Color.red)
.overlay(
GeometryReader { gp in
Color.clear.opacity(0.00001)
.onAppear(){
rect = gp.frame(in: .named("deneme"))
}
}
)
}
.coordinateSpace(name: "deneme")
.overlay(
Rectangle()
.fill(Color.black.opacity(0.8))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.mask(HoleShapeMask(in: rect).fill(style: FillStyle(eoFill: true)))
)
}
}
my code screenshot :
i want to this screen :

Creating a Capsule Style Progress in SwiftUI

I'm trying to create a capsule style progress view in SwiftUI, basing my code on the popular circle progress style.
Here is the code:
struct PSOCapsuleProgressView: View{
var body: some View{
ZStack{
Text("20/99")
.font(.system(size: 12, weight: .light, design: .rounded))
Capsule(style: .circular)
.stroke(lineWidth: 3)
.foregroundColor(.red)
.padding(4)
.opacity(0.3)
Capsule(style: .circular)
.trim(from: 0.0, to: 0.5)
.stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
.foregroundColor(.red)
.padding(4)
}
}
}
This is how it looks:
My only issue is how to make the progress start from the middle top? The circle progress view's that I've seen use a rotation effect but in this case it won't work because its not circular.
Any help is greatly appreciated.
To solve this you need to construct your own Shape. The trim will start from where the shape starts so you have to start drawing your Shape from the top middle. This gives us something like this:
struct MyCapsule: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let halfHeight = rect.height / 2
let halfWidth = rect.width / 2
path.move(to: CGPoint(x: halfWidth, y: 0))
path.addLine(to: CGPoint(x: rect.width - halfHeight, y: 0))
path.addCurve(to: CGPoint(x: rect.width, y: halfHeight),
control1: CGPoint(x: rect.width, y: 0),
control2: CGPoint(x: rect.width, y: halfHeight))
path.addCurve(to: CGPoint(x: rect.width - halfHeight, y: rect.height),
control1: CGPoint(x: rect.width, y: halfHeight),
control2: CGPoint(x: rect.width, y: rect.height))
path.addLine(to: CGPoint(x: halfHeight, y: rect.height))
path.addCurve(to: CGPoint(x: 0, y: halfHeight),
control1: CGPoint(x: 0, y: rect.height),
control2: CGPoint(x: 0, y: halfHeight))
path.addCurve(to: CGPoint(x: halfHeight, y: 0),
control1: CGPoint(x: 0, y: 0),
control2: CGPoint(x: halfHeight, y: 0))
path.addLine(to: CGPoint(x: halfWidth, y: 0))
return path
}
}
Then we can set up our ContentView like this:
struct ContentView: View {
#State private var trim: CGFloat = 0
var body: some View {
ZStack {
MyCapsule()
.stroke(Color.red)
.opacity(0.6)
MyCapsule()
.trim(from: 0, to: trim)
.stroke(Color.red,
style: StrokeStyle(lineWidth: 4,
lineCap: .round,
lineJoin: .round))
Button(action: {
withAnimation(Animation.easeIn(duration: 2)) {
trim = trim == 0 ? 1 : 0
}
}, label: {
Text("Animate in")
})
}
.frame(width: 200, height: 100)
}
}
This gives the following result:
Note because it is a Shape it will be greedy and fill up as much of the View as it can, so make sure you set a frame around it.
It is a bit kludgy, but it appears the only way to do this is to add another Capsule and have the second Capsule run from 0.75 to 1.0, and the other to run from 0.0 to 0.25:
struct PSOCapsuleProgressView: View{
var body: some View{
ZStack{
Text("20/99")
.font(.system(size: 12, weight: .light, design: .rounded))
Capsule(style: .circular)
.stroke(lineWidth: 3)
.foregroundColor(.red)
.padding(4)
.opacity(0.3)
Capsule(style: .circular)
.trim(from: 0.75, to: 1.0)
.stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
.foregroundColor(.red)
.padding(4)
Capsule(style: .circular)
.trim(from: 0.0, to: 0.25)
.stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
.foregroundColor(.red)
.padding(4)
}
}
}
You will have to manage which capsule responds to the data. It may be possible, depending upon how your updates work to send the data into a ForEach as an array and create as many Capsules as you need in smaller increments.

Animating Paths in SwiftUI Shapes

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)
}
}
}