I have been working on creating this UI.But not able to achieve that upper slant, I tried tweaking around my Code,
By Doing that, UI messes up.
Any Help would be appreciated.
let buttonSlotRadius = CGFloat(50)
let path = UIBezierPath()
path.move(to: CGPoint(x: self.layer.frame.width - buttonSlotRadius , y: 0))
path.addArc(withCenter: CGPoint(x: self.layer.frame.width - buttonSlotRadius, y: buttonSlotRadius), radius: buttonSlotRadius, startAngle: 3 * .pi / 2, endAngle: 2 * .pi, clockwise: true)
path.addLine(to: CGPoint(x: buttonSlotRadius, y: 2 * buttonSlotRadius))
path.addArc(withCenter: CGPoint(x: buttonSlotRadius, y: 2 * buttonSlotRadius), radius: buttonSlotRadius, startAngle: .pi, endAngle: 3 * .pi / 2, clockwise: true)
path.addLine(to: CGPoint(x: self.layer.frame.width, y: 50))
path.addLine(to: CGPoint(x: self.layer.frame.width, y: self.layer.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.layer.frame.height))
path.addLine(to: CGPoint(x: 0, y: 2 * buttonSlotRadius))
path.addLine(to: CGPoint(x: self.layer.frame.width - buttonSlotRadius, y: 0))
path.close()
UIColor.white.setFill()
path.fill()
I figured It Out.
I added one more layer to Fix that
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 100))
path.addLine(to: CGPoint(x: 0.0, y: self.bounds.height))
path.addLine(to: CGPoint(x: self.bounds.width, y: self.bounds.height))
path.addLine(to: CGPoint(x: self.bounds.width, y: 50))
path.close()
UIColor.white.setFill()
path.fill()
And Now its Working.Working UI
Related
I need to create this custom UIView for my project.I need to achieve this
I was doing trial and error by creating, My own custom UIView and then drawing on it.
I'm working on this
My Question is, if I'm able to draw my custom shape, How will I clip-off the remaining white space of the UIView
func drawCustomShape() {
let viewSize = self.frame.size
let buttonSlotRadius = CGFloat(30)
let effectiveViewHeight = viewSize.height - buttonSlotRadius
let path = UIBezierPath()
path.move(to: CGPoint(x: 30, y: 0))
path.addLine(to: CGPoint(x: viewSize.width - 30, y: 0))
path.addArc(withCenter: CGPoint(x: viewSize.width - 30,
y: 30),
radius: 30,
startAngle: .pi * 3 / 2,
endAngle: 0,
clockwise: true)
path.addLine(to: CGPoint(x: viewSize.width, y: effectiveViewHeight))
path.addArc(withCenter: CGPoint(x: viewSize.width - 30,
y: effectiveViewHeight - 30),
radius: 30,
startAngle: 0,
endAngle: .pi / 2,
clockwise: true)
path.addLine(to: CGPoint(x: viewSize.width / 4, y: effectiveViewHeight))
// close path join to origin
path.close()
UIColor.secondarySystemFill.setFill()
path.fill()
path.lineWidth = 6.0
path.stroke()
}
You can use UIView with clear background color, and use CAShapeLayer with desired path like this:
extension UIView {
func drawCustomShape() {
let cornerRadius: CGFloat = 18.0
let shapeOffset = self.frame.size.height * 0.2
//create shape layer
let shapeLayer = CAShapeLayer()
shapeLayer.frame = self.bounds
shapeLayer.lineWidth = 1.0
shapeLayer.fillColor = UIColor.white.cgColor
self.layer.addSublayer(shapeLayer)
//create path
let path = UIBezierPath()
//top left point
path.move(to: CGPoint(x: 0, y: cornerRadius))
//top left corner
path.addQuadCurve(to: CGPoint(x: cornerRadius, y: 0),
controlPoint: CGPoint(x: 0, y: 0))
//top right point
path.addLine(to: CGPoint(x: self.frame.size.width - cornerRadius, y: 0))
//top right corner
path.addQuadCurve(to: CGPoint(x: self.frame.size.width, y: cornerRadius),
controlPoint: CGPoint(x: self.frame.size.width, y: 0))
//bottom right point
path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height - shapeOffset - cornerRadius))
//bottom right corner
path.addQuadCurve(to: CGPoint(x: self.frame.size.width - cornerRadius, y: self.frame.size.height - shapeOffset),
controlPoint: CGPoint(x: self.frame.size.width, y: self.frame.size.height - shapeOffset))
//bottom left
path.addLine(to: CGPoint(x: cornerRadius, y: self.frame.size.height))
//bottom left corner
path.addQuadCurve(to: CGPoint(x: 0, y: self.frame.size.height - cornerRadius),
controlPoint: CGPoint(x: 0, y: self.frame.size.height))
path.close()
shapeLayer.path = path.cgPath
}
Usage:
myView.drawCustomShape()
Result:
Im attempting to animate a UIView along an oval using a bezier curve. The code works except that there is a delay between each loop of the animation. I'm at a loss as to what is causing this.
let myView = UIView()
myView.backgroundColor = UIColor.blue
myView.frame = CGRect(x: 0, y:0, width: 30, height: 30)
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 200, height: 200))
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.repeatCount = 1000
animation.duration = 1
myView.layer.add(animation, forKey: "animate along path")
Found a solution. The pause between animation loops only takes place when making a path with the UIBezierPath(ovalIn:) constructor. There's no delay when manually making your own path. So I made my own oval.
Replaced this
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 200, height: 200))
with this
let rect = CGRect(x: 0, y: 0, width: 200, height: 200)
let path = UIBezierPath()
path.move(to: CGPoint(x: rect.maxX, y: rect.midY))
path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.maxY), controlPoint: CGPoint(x: rect.maxX, y: rect.maxY))
path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.midY), controlPoint: CGPoint(x: rect.minX, y: rect.maxY))
path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.minY), controlPoint: CGPoint(x: rect.minX, y: rect.minY))
path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.midY), controlPoint: CGPoint(x: rect.maxX, y: rect.minY))
I am try create custom shape of view with arc in centre, but I am not working with this tools in past.
I create shape what I want but not elegant like need.
What I do wrong?
I am feel where I do mistakes but can't fix it.
How work with this tools, I use this tutorial - https://ayusinghi96.medium.com/draw-custom-shapes-and-views-with-uiberzierpath-ios-1737f5cb975
Result which need:
My result:
My code:
import Foundation
import UIKit
/// Custom card view class
///
class CardView : UIView
{
// init the view with a rectangular frame
override init(frame: CGRect)
{
super.init(frame: frame)
backgroundColor = UIColor.clear
}
// init the view by deserialisation
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
backgroundColor = UIColor.clear
}
/// override the draw(_:) to draw your own view
///
/// Default implementation - `rectangular view`
///
override func draw(_ rect: CGRect)
{
let padding: CGFloat = 5.0
let centerButtonHeight: CGFloat = 50
let f = CGFloat(centerButtonHeight / 2.0) + padding
let halfW = frame.width/2.0
let r = CGFloat(11)
// Card view corner radius
let cardRadius = CGFloat(7)
// Button slot arc radius
let buttonSlotRadius = CGFloat(30)
// Card view frame dimensions
let viewSize = self.bounds.size
// Effective height of the view
let effectiveViewHeight = viewSize.height - buttonSlotRadius
// Get a path to define and traverse
let path = UIBezierPath()
// Shift origin to left corner of top straight line
path.move(to: CGPoint(x: cardRadius, y: 0))
// top line
path.addLine(to: CGPoint(x: viewSize.width - cardRadius, y: 0))
path.addLine(to: CGPoint(x: halfW-f-(r/2.0), y: 0))
//
path.addQuadCurve(to: CGPoint(x: halfW-f, y: (r/2.0)), controlPoint: CGPoint(x: halfW-f, y: 0))
//
path.addArc(withCenter: CGPoint(x: halfW, y: (r/7.0)), radius: f, startAngle: .pi, endAngle: 0, clockwise: false)
//
path.addQuadCurve(to: CGPoint(x: halfW+f+(r/5.5), y: 0), controlPoint: CGPoint(x: halfW+f, y: 0))
// top-right corner arc
path.addArc(
withCenter: CGPoint(
x: viewSize.width - cardRadius,
y: cardRadius
),
radius: cardRadius,
startAngle: CGFloat(Double.pi * 3 / 2),
endAngle: CGFloat(0),
clockwise: true
)
// right line
path.addLine(
to: CGPoint(x: viewSize.width, y: effectiveViewHeight)
)
// bottom-right corner arc
path.addArc(
withCenter: CGPoint(
x: viewSize.width - cardRadius,
y: effectiveViewHeight - cardRadius
),
radius: cardRadius,
startAngle: CGFloat(0),
endAngle: CGFloat(Double.pi / 2),
clockwise: true
)
// right half of bottom line
path.addLine(
to: CGPoint(x: viewSize.width / 4 * 3, y: effectiveViewHeight)
)
// left half of bottom line
path.addLine(
to: CGPoint(x: cardRadius, y: effectiveViewHeight)
)
// bottom-left corner arc
path.addArc(
withCenter: CGPoint(
x: cardRadius,
y: effectiveViewHeight - cardRadius
),
radius: cardRadius,
startAngle: CGFloat(Double.pi / 2),
endAngle: CGFloat(Double.pi),
clockwise: true
)
// left line
path.addLine(to: CGPoint(x: 0, y: cardRadius))
// top-left corner arc
path.addArc(
withCenter: CGPoint(x: cardRadius, y: cardRadius),
radius: cardRadius,
startAngle: CGFloat(Double.pi),
endAngle: CGFloat(Double.pi / 2 * 3),
clockwise: true
)
// close path join to origin
path.close()
// Set the background color of the view
UIColor.init(red: 0.118, green: 0.118, blue: 0.15, alpha: 1).set()
path.fill()
}
}
You need to change these two lines:
path.addArc(withCenter: CGPoint(x: halfW, y: (r/7.0)), radius: f, startAngle: .pi, endAngle: 0, clockwise: false)
//
path.addQuadCurve(to: CGPoint(x: halfW+f+(r/5.5), y: 0), controlPoint: CGPoint(x: halfW+f, y: 0))
to:
path.addArc(withCenter: CGPoint(x: halfW, y: (r/2.0)), radius: f, startAngle: .pi, endAngle: 0, clockwise: false)
//
path.addQuadCurve(to: CGPoint(x: halfW+f+(r/2), y: 0), controlPoint: CGPoint(x: halfW+f, y: 0))
And here is the fixed and slightly refactored code inside the func draw(_ rect: CGRect)
override func draw(_ rect: CGRect) {
let padding: CGFloat = 5
let centerButtonHeight: CGFloat = 50
let f: CGFloat = centerButtonHeight / 2 + padding
let halfW = frame.width / 2
let r: CGFloat = 11
// Card view corner radius
let cardRadius: CGFloat = 7
// Button slot arc radius
let buttonSlotRadius: CGFloat = 30
// Card view frame dimensions
let viewSize = self.bounds.size
// Effective height of the view
let effectiveViewHeight = viewSize.height - buttonSlotRadius
// Get a path to define and traverse
let path = UIBezierPath()
// Shift origin to left corner of top straight line
path.move(to: CGPoint(x: cardRadius, y: 0))
// top line
path.addLine(to: CGPoint(x: viewSize.width - cardRadius, y: 0))
path.addLine(to: CGPoint(x: halfW - f - (r / 2), y: 0))
path.addQuadCurve(
to: CGPoint(x: halfW - f, y: (r / 2)),
controlPoint: CGPoint(x: halfW - f, y: 0)
)
path.addArc(
withCenter: CGPoint(x: halfW, y: (r / 2)),
radius: f, startAngle: .pi, endAngle: 0, clockwise: false
)
//
path.addQuadCurve(
to: CGPoint(x: halfW + f + (r / 2), y: 0),
controlPoint: CGPoint(x: halfW + f, y: 0)
)
// top-right corner arc
path.addArc(
withCenter: CGPoint(
x: viewSize.width - cardRadius,
y: cardRadius
),
radius: cardRadius, startAngle: .pi * 3 / 2, endAngle: 0, clockwise: true
)
// right line
path.addLine(
to: CGPoint(x: viewSize.width, y: effectiveViewHeight)
)
// bottom-right corner arc
path.addArc(
withCenter: CGPoint(
x: viewSize.width - cardRadius,
y: effectiveViewHeight - cardRadius
),
radius: cardRadius, startAngle: 0, endAngle: .pi / 2, clockwise: true
)
// right half of bottom line
path.addLine(
to: CGPoint(x: viewSize.width / 4 * 3, y: effectiveViewHeight)
)
// left half of bottom line
path.addLine(
to: CGPoint(x: cardRadius, y: effectiveViewHeight)
)
// bottom-left corner arc
path.addArc(
withCenter: CGPoint(
x: cardRadius,
y: effectiveViewHeight - cardRadius
),
radius: cardRadius, startAngle: .pi / 2, endAngle: .pi, clockwise: true
)
// left line
path.addLine(to: CGPoint(x: 0, y: cardRadius))
// top-left corner arc
path.addArc(
withCenter: CGPoint(x: cardRadius, y: cardRadius),
radius: cardRadius, startAngle: .pi, endAngle: .pi / 2 * 3, clockwise: true
)
// close path join to origin
path.close()
// Set the background color of the view
UIColor(red: 0.118, green: 0.118, blue: 0.15, alpha: 1).set()
path.fill()
}
Found that in some edge cases the shadowPath seems to render the shadow too rounded.
I'm applying the same shadow settings to all the subviews, I expected that the UIBezierPath with the same corner radius as the view, would behave the same. But seems that when the height starts to decrease they behave different.
Any idea why or how to fix it?
Try something like that
var cornerRadius: CGFloat = 32.0
let layerHalfHeight = subView.bounds.height / 2.0
if layerHalfHeight < cornerRadius {
cornerRadius = layerHalfHeight
}
subView.layer.cornerRadius = cornerRadius
let path = UIBezierPath()
path.move(to: CGPoint(x: cornerRadius, y: 0.0))
path.addLine(to: CGPoint(x: subView.bounds.width - cornerRadius, y: 0.0))
path.addArc(withCenter: CGPoint(x: subView.bounds.width - cornerRadius, y: cornerRadius), radius: cornerRadius, startAngle: 3.0 * .pi / 2.0, endAngle: 2 * .pi, clockwise: true)
path.addLine(to: CGPoint(x: subView.bounds.width, y: subView.bounds.height - cornerRadius))
path.addArc(withCenter: CGPoint(x: subView.bounds.width - cornerRadius, y: subView.bounds.height - cornerRadius), radius: cornerRadius, startAngle: 0.0, endAngle: .pi / 2.0, clockwise: true)
path.addLine(to: CGPoint(x: cornerRadius, y: subView.bounds.height))
path.addArc(withCenter: CGPoint(x: cornerRadius, y: subView.bounds.height - cornerRadius), radius: cornerRadius, startAngle: .pi / 2.0, endAngle: .pi, clockwise: true)
path.addLine(to: CGPoint(x: 0.0, y: cornerRadius))
path.addArc(withCenter: CGPoint(x: cornerRadius, y: cornerRadius), radius: cornerRadius, startAngle: .pi, endAngle: 3.0 * .pi / 2.0, clockwise: true)
path.close()
subView.layer.shadowPath = path.cgPath
So main idea is when the height of a layer become less then default corner radius * 2 then you need to change corner radius to half of the height.
For now, I added a corrector for the UIBezierPath cornerRadius, it kind of works but feels like is not the proper solution.
var cornerRadiusFix: CGFloat = cornerRadius
if cornerRadiusFix > subView.bounds.height / 3 {
cornerRadiusFix = cornerRadiusFix * 0.8
}
subView.layer.shadowPath = UIBezierPath(roundedRect: subView.bounds, cornerRadius: cornerRadiusFix).cgPath
For shadow path to work, you would have to change the bounds in UIView.layoutSubviews of the view with the shadow.
In your case, there is no need to use shadow path. Just remove the line setting the shadow path and everything will work correctly.
I want to create image view like this.
With Bezier path, the code i tried is
let path = UIBezierPath()
path.move(to: CGPoint(x: myImage.frame.size.width - 10 , y: myImage.frame.size.height))
path.addLine(to: CGPoint(x: myImage.frame.size.width - 20 , y: 20))
path.addLine(to: CGPoint(x: myImage.frame.size.width, y: 20))
path.addArc(withCenter: CGPoint(x: myImage.frame.size.width/2, y: myImage.frame.size.height/2), radius: myImage.frame.size.width/2, startAngle:-CGFloat(M_PI_2), endAngle: CGFloat(M_PI_2), clockwise: false)
//
path.move(to: CGPoint(x: myImage.frame.size.width/2, y: myImage.frame.size.height))
path.close()
UIColor.red.setFill()
path.stroke()
path.reversing()
code
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 153.5, y: 137.5))
bezierPath.addCurve(to: CGPoint(x: 63.5, y: 196.5), controlPoint1: CGPoint(x: 130.5, y: 137.5), controlPoint2: CGPoint(x: 63.5, y: 120.37))
bezierPath.addCurve(to: CGPoint(x: 63.5, y: 310.5), controlPoint1: CGPoint(x: 63.5, y: 260.99), controlPoint2: CGPoint(x: 63.5, y: 218.5))
bezierPath.addCurve(to: CGPoint(x: 88.5, y: 352.5), controlPoint1: CGPoint(x: 63.5, y: 333.5), controlPoint2: CGPoint(x: 73.5, y: 347.5))
bezierPath.addCurve(to: CGPoint(x: 218.5, y: 360.5), controlPoint1: CGPoint(x: 137.23, y: 368.74), controlPoint2: CGPoint(x: 218.5, y: 360.5))
bezierPath.addCurve(to: CGPoint(x: 240.5, y: 309.5), controlPoint1: CGPoint(x: 218.5, y: 360.5), controlPoint2: CGPoint(x: 240.5, y: 337))
bezierPath.addCurve(to: CGPoint(x: 222.5, y: 249.5), controlPoint1: CGPoint(x: 240.5, y: 282), controlPoint2: CGPoint(x: 222.5, y: 249.5))
bezierPath.addCurve(to: CGPoint(x: 240.5, y: 196.5), controlPoint1: CGPoint(x: 222.5, y: 249.5), controlPoint2: CGPoint(x: 240.5, y: 224.5))
bezierPath.addCurve(to: CGPoint(x: 218.5, y: 137.5), controlPoint1: CGPoint(x: 240.5, y: 168.5), controlPoint2: CGPoint(x: 218.5, y: 137.5))
bezierPath.addCurve(to: CGPoint(x: 153.5, y: 137.5), controlPoint1: CGPoint(x: 218.5, y: 137.5), controlPoint2: CGPoint(x: 201.5, y: 137.5))
bezierPath.close()
UIColor.gray.setFill()
bezierPath.fill()
UIColor.black.setStroke()
bezierPath.lineWidth = 1
bezierPath.stroke()
output