I working on app where I want to make custom shape button like this below
for this I'm using coregraphics to draw that shape for button and here code of button
class RewardStepsButton: UIButton {
#IBInspectable var firstStep: Bool = true{
didSet{
if firstStep{
secondStep = false
thirdStep = false
}
}
}
#IBInspectable var secondStep: Bool = false{
didSet{
if secondStep{
firstStep = false
thirdStep = false
}
}
}
#IBInspectable var thirdStep: Bool = false{
didSet{
if thirdStep{
firstStep = false
secondStep = false
}
}
}
#IBInspectable var buttonColor: UIColor = UIColor.lightGray
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
let path = UIBezierPath()
let btnLayer = CAShapeLayer()
if firstStep{
path.addArc(withCenter: CGPoint(x: 5, y: 5), radius: 5, startAngle: .pi, endAngle: 3 * .pi / 2, clockwise: true)
path.addLine(to: CGPoint(x: self.bounds.width - 15, y: 0))
path.addLine(to: CGPoint(x: self.bounds.width, y: self.bounds.height / 2))
path.addLine(to: CGPoint(x: self.bounds.width - 15, y: self.bounds.height))
path.addArc(withCenter: CGPoint(x: 5, y: self.bounds.height - 5), radius: 5, startAngle: .pi / 2, endAngle: .pi, clockwise: true)
path.close()
}else if secondStep{
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: self.bounds.width - 15, y: 0))
path.addLine(to: CGPoint(x: self.bounds.width, y: self.bounds.height / 2))
path.addLine(to: CGPoint(x: self.bounds.width - 15, y: self.bounds.height))
path.addLine(to: CGPoint(x: 0, y: self.bounds.height))
path.addLine(to: CGPoint(x: 15, y: self.bounds.height / 2))
path.addLine(to: CGPoint(x: 0, y: 0))
}else{
path.move(to: CGPoint(x: 0, y: 0))
path.addArc(withCenter: CGPoint(x: self.bounds.width - 5, y: 5), radius: 5, startAngle: 3 * .pi / 2, endAngle: 2 * .pi, clockwise: true)
path.addArc(withCenter: CGPoint(x: self.bounds.width - 5, y: self.bounds.height - 5), radius: 5, startAngle: 2 * .pi, endAngle:.pi / 2, clockwise: true)
path.addLine(to: CGPoint(x: 0, y: self.bounds.height))
path.addLine(to: CGPoint(x: 15, y: self.bounds.height / 2))
path.close()
}
btnLayer.path = path.cgPath
btnLayer.fillColor = self.buttonColor.cgColor
self.layer.addSublayer(btnLayer)
self.bringSubviewToFront(self.titleLabel!)
if thirdStep || secondStep{
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: 25, bottom: 0, right: 0)
}else{
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)
}
}
}
now it works almost perfect when I run the application buttons draw on app same as I want see below
now the problem it that when I changed the orientation of device, shape of drawing button is same doesn't refresh.
so I googled it and try to find solution and lot of answer were use
setNeedsDisplay() method for this so I use that method also but problem is, it draw another shape or add another layer and not erase the previous one. see this
When app load
when landscape orientation
when again portrait
please give me solution or idea how to solve this mess, Thanks.
Dont add layer in draw() method as in calls every time you call setNeedsLayout or layoutifNeeded ... use to add them in common init()
self.layer.addSublayer(btnLayer)
remove this line from draw() method
//MARK:- initializers
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
private func commonInit(){
self.layer.addSublayer(btnLayer)
}
Related
I want to make curved bottom bar it is not looking perfect round by below code
struct customShape: Shape {
var xAxis : CGFloat
var animatableData: CGFloat{
get { return xAxis}
set { xAxis = newValue}
}
func path(in rect: CGRect) -> Path {
return Path { path in
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))
let center = xAxis
path.move(to: CGPoint(x: center - 43, y: 0))
let to1 = CGPoint(x: center, y: 50)
let center1 = CGPoint(x: center - 30, y: 0)
let center2 = CGPoint(x: center - 46, y: 42)
let to2 = CGPoint(x: center + 52, y: 0)
let center3 = CGPoint(x: center + 57, y: 47)
let center4 = CGPoint(x: center + 35, y: 0)
path.addCurve(to: to1, control1: center1, control2: center2)
path.addCurve(to: to2, control1: center3, control2: center4)
}
}
}
You are trying to construct a circle with Bezier handles. And it seems you are not calculating the values but guessing and trying. This will be at least very hard if not impossible to solve. Try to add an arc instead of a curve.
Possible implementation:
struct customShape: Shape {
var xAxis : CGFloat
var radius: CGFloat = 40
var animatableData: CGFloat{
get { return xAxis}
set { xAxis = newValue}
}
func path(in rect: CGRect) -> Path {
return Path { path in
path.move(to: CGPoint(x: 0, y: 0))
// add a line to the starting point of the circle
path.addLine(to: CGPoint(x: xAxis - radius, y: 0))
// add the arc with the centerpoint of (xAxis,0) and 180°
path.addArc(center: .init(x: xAxis, y: 0), radius: radius, startAngle: .init(degrees: 180), endAngle: .init(degrees: 0), clockwise: true)
// complete the rectangle
path.addLine(to: .init(x: rect.size.width, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.addLine(to: CGPoint(x: 0, y: rect.height))
path.closeSubpath()
}
}
}
how to draw the curve with blur background?
here I have attached my code but am not getting the exact output
#IBDesignable class CurvedHeaderView: UIView {
#IBInspectable var curveHeight:CGFloat = 50.0
var curvedLayer = CAShapeLayer()
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.move(to: CGPoint(x: 0, y: rect.height))
// path.addLine(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.addArc(withCenter: CGPoint(x: CGFloat(rect.width) - curveHeight, y: 100), radius: curveHeight, startAngle: 0, endAngle: 1.5 * CGFloat.pi, clockwise: false)
// path.addArc(withCenter: CGPoint(x: CGFloat(rect.width) - curveHeight, y: rect.height), radius: curveHeight, startAngle: 0, endAngle: 1.5 * CGFloat.pi, clockwise: false)
// path.addLine(to: CGPoint(x: curveHeight, y: rect.height - curveHeight))
// path.addLine(to: CGPoint(x: curveHeight, y: 50))
path.addLine(to: CGPoint(x: curveHeight, y: curveHeight))
path.addArc(withCenter: CGPoint(x: curveHeight, y: 0), radius: curveHeight, startAngle: 0, endAngle: CGFloat.pi, clockwise: true)
path.addLine(to: CGPoint(x: 0, y: 0))
// path.addArc(withCenter: CGPoint(x: curveHeight, y: rect.height - (curveHeight * 2.0)), radius: curveHeight, startAngle: 0, endAngle: CGFloat.pi, clockwise: true)
path.close()
curvedLayer.path = path.cgPath
curvedLayer.fillColor = UIColor.white.cgColor
curvedLayer.frame = rect
self.layer.insertSublayer(curvedLayer, at: 0)
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowRadius = 10.0
self.layer.shadowOpacity = 0.7
}
}
Please share me reference if anything wrong
Thanks in advance
I have tried but it showing code working but not exactly been getting
#IBOutlet weak var blurEffectView: UIVisualEffectView!
override func viewDidLayoutSubviews() {
let maskView = CurvedHeaderView(frame: blurEffectView.bounds)
maskView.clipsToBounds = true;
maskView.backgroundColor = UIColor.clear
blurEffectView.mask = maskView
}
I tried to add a curve but not working.
I have attached the image left side is given by the designer, the right side I have done in swift.
here the if I add corner radius not working for the curve if add curve corner radius not working.
I have added left side and right side separate views, how it's possible to achieve the remove the super view background-color and curve.
extension UIView {
func roundCorners(
corners: UIRectCorner,
radius: CGFloat
) {
let path = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(
width: radius,
height: radius
)
)
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}
// here I am using on UITableviewCell in layoutSubview
leftCurve.roundCorners(corners: [.topRight,.bottomRight], radius: 20)
rightCurver.roundCorners(corners: [.topLeft,.bottomLeft], radius: 20)
Here is a demo for add curve to both side using UIBezierPath
class ShapeView: UIView {
var path: UIBezierPath = UIBezierPath()
// Set you circle position for verticle.
var circleYPosition: CGFloat = 50 {
didSet {
self.setNeedsDisplay()
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = UIColor.clear
// Here apply shadow
}
override func draw(_ rect: CGRect) {
super.draw(rect)
// 4 corners radious
UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 10, height: 10)).addClip()
// Left - right circle
path.move(to: CGPoint(x: 0, y: self.frame.size.height))
//left side
path.addArc(withCenter: CGPoint(x: 0, y: self.circleYPosition - 15),
radius: 10,
startAngle: CGFloat((90 * Double.pi) / 180),
endAngle: CGFloat((270 * Double.pi) / 180),
clockwise: false)
path.addLine(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: self.frame.size.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
path.move(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
//right side
path.addArc(withCenter: CGPoint(x: self.frame.size.width, y: self.circleYPosition - 15),
radius: 10,
startAngle: CGFloat((270 * Double.pi) / 180),
endAngle: CGFloat((90 * Double.pi) / 180),
clockwise: false)
path.close()
UIColor.white.setFill()
path.fill()
// Center Dash path
let dashPath = UIBezierPath()
dashPath.move(to: CGPoint(x: self.bounds.minX + 12, y: self.circleYPosition - 15))
dashPath.addLine(to: CGPoint(x: self.bounds.maxX - 12, y: self.circleYPosition - 15))
dashPath.setLineDash([5,5], count: 2, phase: 0.0)
dashPath.lineWidth = 1.0
dashPath.lineCapStyle = .butt
UIColor.lightGray.set()
dashPath.stroke()
}
}
How to make a shape like this
func createShape() {
bezierPath = UIBezierPath()
bezierPath.move(to: .zero)
bezierPath.addLine(to: CGPoint(x:self.frame.width , y: self.frame.origin.y))
bezierPath.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height / 2))
bezierPath.addCurve(to: CGPoint(x:self.frame.width/2 , y: self.frame.height), controlPoint1: CGPoint(x: self.frame.width, y: self.frame.height / 2), controlPoint2: CGPoint(x:self.frame.width/2 + 33 , y: self.frame.height))
bezierPath.addCurve(to: CGPoint(x: 0, y: self.frame.height / 2), controlPoint1: CGPoint(x: self.frame.width/2 - 33, y:self.frame.height), controlPoint2: CGPoint(x: 0, y: self.frame.height / 2))
bezierPath.addLine(to: .zero)
bezierPath.close()
}
and as a result i got
Can you help me?
read how a curve work here
let bezierPath = UIBezierPath()
bezierPath.move(to: .zero)
bezierPath.addLine(to: CGPoint(x: 0 , y: self.frame.height/2))
bezierPath.addCurve(to: CGPoint(x:self.frame.width , y: self.frame.height / 2), controlPoint1: CGPoint(x: 0, y: (self.frame.height+self.frame.width)/2), controlPoint2: CGPoint(x: self.frame.width, y: (self.frame.height+self.frame.width)/2))
bezierPath.addLine(to: CGPoint(x: self.frame.width, y: 0))
bezierPath.addLine(to: .zero)
Here is the drawing that you need.. it will give you perfect circle with UIBezierpath addArc method
import UIKit
#IBDesignable class CustomView: UIView {
private lazy var shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
shapeLayer.fillColor = UIColor.black.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.borderWidth = 5
shapeLayer.frame = bounds
layer.addSublayer(shapeLayer)
}
override func layoutSubviews() {
shapeLayer.path = drawShape()
}
private func drawShape() -> CGPath {
let bezierPath = UIBezierPath()
bezierPath.move(to: .zero)
bezierPath.addLine(to: CGPoint(x: 0, y: bounds.midY/2))
bezierPath.addArc(withCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: bounds.midX, startAngle: .pi, endAngle: 0, clockwise: false)
bezierPath.addLine(to: CGPoint(x: bounds.maxX, y: 0))
bezierPath.close()
return bezierPath.cgPath
}
}
with Background color
I'm trying to use my own UITabBar instance inside a UITabBarController. Using Storyboard I know that you can add your Custom Class to your TabbarController using the following:
How can I achieve the same thing programmatically?
Here's my custom class:
I've tried modifying and overriding the tabBar variable inside UITabBarController but it seems that it's a get only variable and can not be modified. Is there an easy solution to fix this issue?
Much appreciated!
class CustomTabBar: UITabBar {
private var shapeLayer: CALayer?
private func addShape() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = createPath()
shapeLayer.strokeColor = UIColor.lightGray.cgColor
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.lineWidth = 1.0
if let oldShapeLayer = self.shapeLayer {
self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
} else {
self.layer.insertSublayer(shapeLayer, at: 0)
}
self.shapeLayer = shapeLayer
}
override func draw(_ rect: CGRect) {
self.addShape()
}
func createPath() -> CGPath {
let height: CGFloat = 37.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
path.move(to: CGPoint(x: 0, y: 0)) // start top left
path.addLine(to: CGPoint(x: (centerWidth - height * 2), y: 0)) // the beginning of the trough
// first curve down
path.addCurve(to: CGPoint(x: centerWidth, y: height),
controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height))
// second curve up
path.addCurve(to: CGPoint(x: (centerWidth + height * 2), y: 0),
controlPoint1: CGPoint(x: centerWidth + 35, y: height), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))
// complete the rect
path.addLine(to: CGPoint(x: self.frame.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.height))
path.close()
return path.cgPath
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let buttonRadius: CGFloat = 35
return abs(self.center.x - point.x) > buttonRadius || abs(point.y) > buttonRadius
}
func createPathCircle() -> CGPath {
let radius: CGFloat = 37.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: (centerWidth - radius * 2), y: 0))
path.addArc(withCenter: CGPoint(x: centerWidth, y: 0), radius: radius, startAngle: CGFloat(180).degreesToRadians, endAngle: CGFloat(0).degreesToRadians, clockwise: false)
path.addLine(to: CGPoint(x: self.frame.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.height))
path.close()
return path.cgPath
}
}
And here's my view controller:
final class TabbarViewController: UITabBarController {
// MARK: - Properties
private lazy var tabBarItemControllers: [UIViewController] = {
let editorController = ...
let settingsController = ...
return [editorController, settingsController]
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureTabbar()
setViewControllers(tabBarItemControllers, animated: true)
}
}