SwiftUI: The actual touchable area is bigger than I specify - ios

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

Related

SwiftUI: Custom shape material fill does not work

I made a custom shape in SwiftUI with the following code:
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(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))
path.addQuadCurve(to: CGPoint(x: 0, y: rect.minY), control: CGPoint(x: rect.midX, y: rect.minY - 25))
return path
}
}
The usage of this shape looks like the following:
CustomShape()
.fill(.ultraThickMaterial)
.frame(height: 200)
The problem is that if I try to fill it with a color it works perfectly. If I try to fill it with a material only the rectangle seems to get filled, the arc portion remains white:
Do you have an idea how to solve this?
It seems that clipping is being applied when you are using a material, but not when you are using a color. The arc of your path is extending outside the frame of the view.
You can fix this by making the path fully contained within the rect parameter of the shape:
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(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 + 25))
path.addQuadCurve(to: CGPoint(x: 0, y: rect.minY + 25), control: CGPoint(x: rect.midX, y: rect.minY))
path.closeSubpath()
return path
}
If you really want the material to extend outside the frame then you'll need to use a container view of some sort.
I'd say that expected effect should be achieved with background and clipShape (assuming that CustomShape is constructed correctly)
Here is a demo for better visibility (tested with Xcode 13.3 / iOS 15.4)
Image("background").overlay(
Rectangle()
.background(.thinMaterial) // << here !! (thin for demo)
.frame(height: 200)
.clipShape(CustomShape()) // << here !!
)

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

SwiftUI: clipping an image to shape

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() { ... }

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

Resources