SwiftUI Custom Navigation Bar with Rounded bottom edges and items - ios

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

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

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 set shadow at left right and bottom in UIView in ios Swift?

I want shadow at left, right and bottom in view. But Swift 3 shadow offset provide only height or width. I found similar answer see below link it show shadow top left right shadow. I want left right and bottom.
see link little similar answer
It's almost the same task as link provided, just change the BezierPath.
let block1 = UIView(frame: CGRect.init(x: 50, y: 50, width: 300, height: 300))
block1.backgroundColor = UIColor.red
block1.layer.masksToBounds = false
block1.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
block1.layer.shadowRadius = 5.0
block1.layer.shadowOpacity = 0.7
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 0.0))
// Move to center between two top points instead of bottom
path.addLine(to: CGPoint(x: block1.frame.size.width/2.0, y: block1.frame.size.height/2.0))
path.addLine(to: CGPoint(x: block1.frame.size.width, y: 0.0))
path.addLine(to: CGPoint(x: block1.frame.size.width, y: block1.frame.size.height))
path.addLine(to: CGPoint(x: 0.0, y: block1.frame.size.height))
path.close()
block1.layer.shadowPath = path.cgPath

How to make a CGRect slashed at bottom?

I'm trying to implement the following:
A custom NavigationBar which is slashed at the bottom, how can I do this using CGRect?
Another alternative would be to make a image of it and push the list up to overlap the transparent part in the image. I'd prefer the first solution if possible tho. Any suggestions?
This is my current WIP state:
You can use Bezier path
Just add lines and make your custom shape
like this:
let path = UIBezierPath()
shape.move(to: CGPoint(x: 0, y: 0))
shape.addLine(to: CGPoint(x: 0, y: 320))
shape.addLine(to: CGPoint(x: 94, y: 320))
shape.addLine(to: CGPoint(x: 64, y: 0))
shape.close()

Resources