SwiftUI: clipping an image to shape - ios

I'm currently working on some loading screen in SwiftUI and want to implement some kind of image animation with a simple path. I've done a simple Shape-animation with the path, but Ive stuck with clipping an image to it. Is it possible?
I just want my image to simply fly over the screen (with a configurable path). Imagine that the stroke has a color of background, so we won't see it. Thanks!
struct loadingPath: Shape {
func path(in rect: CGRect) -> Path {
let path =
Path { path in
path.move(to: CGPoint(x: rect.midX, y: rect.midY))
path.addQuadCurve(to: CGPoint(x: 230, y: 200), control: CGPoint(x: -100, y: 300))
path.addQuadCurve(to: CGPoint(x: 90, y: 400), control: CGPoint(x: 400, y: 130))
path.addLine(to: CGPoint(x: 90, y: 600))
}
return path
}
}
.........
var body: some View {
ZStack {
loadingPath()
.trim(from: 0, to: x)
.stroke(Color.purple)
.frame(width: 200, height: 200)
.onAppear() {
withAnimation(Animation.easeInOut(duration: 3).delay(0.5)) {
self.x = 1
}
}
}

If you want to give shape and then want to clipped it then use following :
.clipShape(RoundedRectangle(cornerRadius: 55,
style: .continuous))
Or if you you want to clipped image according to it's frame then you can simply clipped it after give it frame like following :
.
.
loadingPath()
.trim(from: 0, to: x)
.stroke(Color.purple)
.frame(width: 200, height: 200)
.clipped()
.onAppear() { ... }

Related

SwiftUI Custom Navigation Bar with Rounded bottom edges and items

I have searched all over stackoverflow for a solution but I haven't found one.
The desired goal as described in the title is this:
The desired color as we can see is also gradient.
I also wonder if it is possible to also display an image on the right side of the navigation bar whenever I wanted to show it ( sometimes i want to show an image and sometimes i dont)
I know that i have to use UIKIT in order to create something like this but i have not found a solution where i can make the bottom edges of the navigation bar rounded.
no need to use UIKit in that you can use native SwiftUI using Path drawing to make a view like that and keep a note to hide the default navigation view title and view and use the code below you also can customize the view depending on your needs
struct ContentView: View {
var body: some View {
ZStack {
CustomShapeNav().fill(
LinearGradient(gradient: Gradient(colors: [.green, .blue]),
startPoint: .top, endPoint: .bottom)
// use gradient color here
).shadow(color: Color(#colorLiteral(red: 0.4745098039, green:
0.4745098039, blue: 0.4745098039, alpha: 1)), radius:
15).ignoresSafeArea()
Button {
print("Button was tapped")
} label: {
Image(systemName: "chevron.backward")
.padding()
.foregroundColor(.black)
}.position(x: 40, y: 40)
}.frame(width: 700, height: 300, alignment: .center)
}
}
//Define the custom navigation using path drawing and make struct confirming to Shape
struct CustomShapeNav: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: 0, y: 0)) // start point from 0 x-axis & 0 y-axis
path.addLine(to: CGPoint(x: rect.maxX , y: 0)) // draw line max x-axis & 0 y-axis
path.addLine(to: CGPoint(x: rect.maxX, y: 50)) // draw line max x-axis & 50 y-axis
path.addLine(to: CGPoint(x: 0, y: 50)) // draw line 0 x-axis & 50 y-axis
path.move(to: CGPoint(x: 0, y: 50)) // make the start point 0,50 instead of 0,0
path.addQuadCurve(to: CGPoint(x: 90, y: 90), control: CGPoint(x:
0, y: 85)) // add curve to point 90,90 and make the point of curve 0,85
path.addLine(to: CGPoint(x: rect.maxX - 90, y: 90)) // draw line -90 x-axis & 90 y-axis
path.addQuadCurve(to: CGPoint(x: rect.maxX, y: 50), control: CGPoint(x: rect.maxX, y: 85))
// add curve to point max x-axis and 50 and make the point of curve max x-axis and 85
}
}
}
and the navigation bar will be like that view

BezierPath clipShape issue

I added curves on the right and left sides. When you look at the stroke function, everything is normal, but when the stroke function is removed, the curve on the right works normally, but the curve on the left does not work. is this a bug?
ContentView
struct ContentView: View {
#State var viewState: CGSize = .zero
var body: some View {
ZStack {
Color.orange
.clipShape(FooBezierPath(rightOffset: viewState).stroke())//remove stroke
.edgesIgnoringSafeArea(.all)
.overlay(
HStack {
Image(systemName: "chevron.right")
.font(.largeTitle)
.offset(y: 115)
.edgesIgnoringSafeArea(.all)
Spacer()
Image(systemName: "chevron.left")
.font(.largeTitle)
.contentShape(Rectangle())
.gesture(DragGesture().onChanged({ (value) in
withAnimation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0)) {
self.viewState = value.translation
}
}).onEnded({ (value) in
withAnimation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0)) {
self.viewState = .zero
}
}))
.edgesIgnoringSafeArea(.all)
.offset(y: 70)
}
,alignment: .topTrailing
)
.padding(.horizontal)
}
}
}
BezierPath
struct FooBezierPath: Shape {
var rightOffset: CGSize
func path(in rect: CGRect) -> Path {
return Path { path in
let width = rect.width + rightOffset.width
let height = rect.height
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: 0))
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))
//MARK: - Left Curve
path.move(to: CGPoint(x: 0, y: 80))
path.addCurve(to: CGPoint(x: 0, y: 180), control1: CGPoint(x: 50, y: 130), control2: CGPoint(x: 50, y: 130))
//MARK: - Right Curve
path.move(to: CGPoint(x: width, y: 80))
path.addCurve(to: CGPoint(x: width, y: 180), control1: CGPoint(x: width - 50, y: 130), control2: CGPoint(x: width - 50, y: 130))
}
}
}
It Helps to Draw the entire shape without moving points, otherwise the clip malfunctions for some reason. Try this:
struct FooBezierPath: Shape {
var rightOffset: CGSize
func path(in rect: CGRect) -> Path {
return Path { path in
let width = rect.width + rightOffset.width
let height = rect.height
// Draw left Edge of Shape
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: 80))
path.addCurve(to: CGPoint(x: 0, y: 180), control1: CGPoint(x: 50, y: 130), control2: CGPoint(x: 50, y: 130))
path.addLine(to: CGPoint(x: 0, y: height))
// Draw Bottom Edge of Shape
path.addLine(to: CGPoint(x: width, y: height))
// Draw Right Edge of Shape
path.addLine(to: CGPoint(x: width, y: 180))
path.addCurve(to: CGPoint(x: width, y: 80), control1: CGPoint(x: width - 50, y: 130), control2: CGPoint(x: width - 50, y: 130))
path.addLine(to: CGPoint(x: width, y: 0))
// Draw Top Edge of Shape
path.addLine(to: CGPoint(x:0, y: 0))
}
}
}
Tried and tested the code, the results are:
Without Stroke
With Stroke

Split a Rectangle View Using the Diagonal in Swift UI

I'm new to SwiftUI and I'm trying to achieve something that is quite easy using UIKit. Basically, I want to draw something like this in a View:
I figured that I could use GeometryScanner but I didn't manage to make it work. Any help would be appreciated.
You can create your own Triangle:
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
}
}
and use it like this:
Rectangle()
.fill(Color.white)
.frame(width: 300, height: 200)
.overlay(
Triangle()
.fill(Color.red)
)

SwiftUI: The actual touchable area is bigger than I specify

I am implementing a simple resize cursors for my drawing app, using SwiftUI.
Even though I am making the each cursor small (14x14 pixels), the actual hit area is much bigger (like 40x40) for some reason. I am wondering why it is happening, and figure out how to eliminate it.
Here is the code:
var body: some View {
let center = scaled(point:model.cursorCenter)
return ZStack {
let rect = scaledCursor()
Path(CGPath(rect: rect, transform: nil))
.stroke(lineWidth: 1.0)
.foregroundColor(selectionColor)
if !model.isDragging {
Rectangle()
.frame(width:14, height:14)
.position(CGPoint(x: rect.maxX, y: rect.maxY))
.foregroundColor(selectionColor)
.gesture(resizeGesture(geometry: geometry, sx:nil, sy:nil))
Rectangle()
.frame(width:14, height:14)
.position(CGPoint(x: center.x, y: rect.maxY))
.foregroundColor(selectionColor)
.gesture(resizeGesture(geometry: geometry, sx:1, sy:nil))
Rectangle()
.frame(width:14, height:14)
.position(CGPoint(x: rect.maxX, y: center.y))
.foregroundColor(selectionColor)
.gesture(resizeGesture(geometry: geometry, sx:nil, sy:1))
Circle()
.frame(width:14, height:14)
.position(CGPoint(x: center.x, y: rect.minY))
.foregroundColor(selectionColor)
.gesture(rotateGesture(geometry: geometry))
}
}
.transformEffect(model.cursorTransform(center: center))
.contentShape(Rectangle())
}

How to combine shapes in SwiftUI?

I need to create a shape from several other shapes, and ideally, I'd get a single shape struct at the end which would have two shapes stacked as ZStack would. I haven't noticed an obvious implementation of this anywhere, so maybe somebody has some ideas on how to implement it?
Here's what I want to have:
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
// Add shapes here???? For example, combine Rectangle with Circle?
}
}
You can use func addPath(_ path: Path, transform: CGAffineTransform = .identity) to create a new Shape like this :
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
var customShape = Path { p in
p.move(to: CGPoint(x: 0, y: 0))
p.addQuadCurve(to: CGPoint(x: 0, y: 100),
control: CGPoint(x: 0, y: 0))
p.addCurve(to: CGPoint(x: 100, y: 400),
control1: CGPoint(x: 0, y: 200),
control2: CGPoint(x: 100, y: 200))
p.addCurve(to: CGPoint(x: 200, y: 100),
control1: CGPoint(x: 100, y: 200),
control2: CGPoint(x: 200, y: 200))
p.addQuadCurve(to: CGPoint(x: 200, y: 0),
control: CGPoint(x: 200, y: 0))
}
let rectangle = Rectangle()
.path(in: customShape.boundingRect)
.offsetBy(dx: 100, dy: 0)
customShape.addPath(rectangle, transform: .init(scaleX: 0.5, y: 0.35))
return customShape
}
}
And use it like this :
struct SwiftUIView: View {
var body: some View {
CustomShape()
}
}

Resources