Add border of varying width to different sides - ios

I have a requirement where I need to border width 1 (CGFloat) on the top, left, and bottom side of the uiview and 0 (CGFloat) on the right side. I tried adding multiple CALayers for each side, but the view was not good, as while scrolling the border did not scroll with uiview.
Code I tried using:
enum ViewSide: String {
case left
case right
case top
case bottom
}
func addBorderToAllSides(color: UIColor, thickness: CGFloat) {
addBorder(toSides: [.left, .right, .bottom, .top], withColor: color, andThickness: thickness)
}
func addBorder(toSides sides: [ViewSide], withColor color: UIColor, andThickness thickness: CGFloat) {
sides.forEach { (side) in
///remove previously added sublayer
layer.sublayers?.removeAll(where: {$0.name == side.rawValue})
let border = CALayer()
border.backgroundColor = color.cgColor
switch side {
case .left:
border.frame = CGRect(x: frame.minX, y: frame.minY, width: thickness, height: frame.height)
case .right:
border.frame = CGRect(x: frame.maxX, y: frame.minY, width: thickness, height: frame.height)
case .top:
border.frame = CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: thickness)
case .bottom:
border.frame = CGRect(x: frame.minX, y: frame.maxY, width: frame.width, height: thickness)
}
border.name = side.rawValue
layer.addSublayer(border)
}
}
}
src - https://gist.github.com/MrJackdaw/6ffbc33fc274838412bfe3ad48592b9b
What I'm trying to achieve:
here for each item, adding the same border width on each side causes the border in the middle of 2 items to be double width. Want to get rid of that.
Any help is appreciated

There are various ways to do this, but one method that you may like is to add a CAShapeLayer as a single sublayer, and use a UIBezierPath for the sides.
Here's a simple example:
class SidesCell: UICollectionViewCell {
enum ViewSide: String {
case left
case right
case top
case bottom
}
var sides: [ViewSide] = []
let sidesLayer: CAShapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.layer.addSublayer(sidesLayer)
sidesLayer.fillColor = UIColor.clear.cgColor
sidesLayer.strokeColor = UIColor.gray.cgColor
sidesLayer.lineWidth = 1.0
}
// modify this based on what you're setting in the cell
// label text, colors, whatever...
func setData(_ str: String, sides: [ViewSide]) -> Void {
self.sides = sides
// use other data
}
// this will be called when the cell is ready for layout
override func layoutSubviews() {
super.layoutSubviews()
let pth = UIBezierPath()
if sides.contains(.left) {
pth.move(to: CGPoint(x: bounds.minX, y: bounds.minY))
pth.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
}
if sides.contains(.top) {
pth.move(to: CGPoint(x: bounds.minX, y: bounds.minY))
pth.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
}
if sides.contains(.right) {
pth.move(to: CGPoint(x: bounds.maxX, y: bounds.minY))
pth.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
}
if sides.contains(.bottom) {
pth.move(to: CGPoint(x: bounds.minX, y: bounds.maxY))
pth.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
}
sidesLayer.path = pth.cgPath
}
}
Then, in cellForItemAt, you would set the sides needed for each cell.
For example, if you have 7 cells, and you want Top/Left/Bottom "side lines" on the first 6:
let c = collectionView.dequeueReusableCell(withReuseIdentifier: "myIdentifier", for: indexPath) as! SidesCell
if indexPath.item == 6 {
c.setData("Test", sides: [.top, .bottom])
} else {
c.setData("Test", sides: [.top, .left, .bottom])
}
return c

Related

I want to use only top border line and right and left top corners . I did it but corners colors does not appear. Can anybody help me?

extension UIView {
func roundCorners(view :UIView, corners: UIRectCorner, radius: CGFloat){
let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
view.layer.mask = mask
path.close()
let color = UIColor.white
color.setStroke()
path.stroke()
}
enum ViewSide: String {
case Left = "Left", Right = "Right", Top = "Top", Bottom = "Bottom"
}
func addBorder(toSide side: ViewSide, withColor color: CGColor, andThickness thickness: CGFloat) {
let border = CALayer()
border.borderColor = color
border.name = side.rawValue
switch side {
case .Left: border.frame = CGRect(x: 0, y: 0, width: thickness, height: frame.height)
case .Right: border.frame = CGRect(x: frame.width - thickness, y: 0, width: thickness, height: frame.height)
case .Top: border.frame = CGRect(x: 0, y: 0, width: frame.width, height: thickness)
case .Bottom: border.frame = CGRect(x: 0, y: frame.height - thickness, width: frame.width, height: thickness)
}
border.borderWidth = thickness
layer.addSublayer(border)
}
func removeBorder(toSide side: ViewSide) {
guard let sublayers = self.layer.sublayers else { return }
var layerForRemove: CALayer?
for layer in sublayers {
if layer.name == side.rawValue {
layerForRemove = layer
}
}
if let layer = layerForRemove {
layer.removeFromSuperlayer()
}
}
}
class TabbarView: UIView {
var viewColor = UIView()
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
addBorder(toSide: .Bottom, withColor:CGColor.init(gray: 100/255, alpha: 100/255), andThickness: 1)
addBorder(toSide: .Top, withColor: CGColor.init(gray: 100/255, alpha: 100/255), andThickness: 1)
addBorder(toSide: .Left, withColor: CGColor.init(gray: 100/255, alpha: 100/255), andThickness: 1)
addBorder(toSide: .Right, withColor: CGColor.init(gray: 100/255, alpha: 100/255), andThickness: 1)
self.roundCorners(view: self, corners: [.topLeft, .topRight], radius: 20)
removeBorder(toSide: .Bottom)
removeBorder(toSide: .Left)
removeBorder(toSide: .Right)
}

How to draw a curve like this in UIBezierPath Swift?

I am trying to develop a screen whose background looks like this:
Here I am trying to develop the gray curved background and it fills the lower part of the screen as well. I'm very new to UIBezierPath and I've tried this:
class CurvedView: UIView {
//MARK:- Data Types
//MARK:- View Setup
override func draw(_ rect: CGRect) {
let fillColor: UIColor = .blue
let path = UIBezierPath()
let y:CGFloat = 0
print(rect.height)
print(rect.width)
path.move(to: CGPoint(x: .zero, y: 100))
path.addLine(to: CGPoint(x: 60, y: 100))
path.addCurve(to: .init(x: 100, y: 0), controlPoint1: .init(x: 125, y: 80), controlPoint2: .init(x: 50, y: 80))
path.close()
fillColor.setFill()
path.fill()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .init(hex: "#dfe1e3")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = .init(hex: "#dfe1e3")
}
}
This code gave me this:
I followed a lot of tutorials but I didn't get the exact understanding. I understood that for this curve I have to move to (0,100) and then add a line and then add a curve and ten extend the line add a curve then straight line lower curve and then straight line and close. But, when I started as you can see the blue line didn't cover the upper part. Can any one please help me?
Here some example that I create, you can change the value to make it more similar to what you want
Here a guide how control point in a curve work
Note: I called this code in viewDidload
let path = UIBezierPath()
let fillColor = UIColor.blue
let y: CGFloat = UIScreen.main.bounds.size.height
let x: CGFloat = UIScreen.main.bounds.size.width
let height: CGFloat = 200
path.move(to: CGPoint(x: 0, y: y)) // bottom left
path.addLine(to: CGPoint(x: 0, y: y - 20)) // top left
path.addCurve(to: CGPoint(x: x, y: y - height), controlPoint1: CGPoint(x: x * 2 / 3, y: y), controlPoint2: CGPoint(x: x * 5 / 6, y: y - height * 6 / 5)) // curve to top right
path.addLine(to: CGPoint(x: x, y: y)) // bottom right
path.close() // close the path from bottom right to bottom left
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = fillColor.cgColor
view.layer.addSublayer(shapeLayer)
in reference to #aiwiguna
class CurvedView: UIView {
//MARK:- Data Types
//MARK:- View Setup
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
let fillColor = UIColor.blue
let y: CGFloat = UIScreen.main.bounds.size.height
let x: CGFloat = UIScreen.main.bounds.size.width
let height: CGFloat = 200
path.move(to: CGPoint(x: 0, y: y)) // bottom left
path.addLine(to: CGPoint(x: 0, y: y - 20)) // top left
path.addCurve(to: CGPoint(x: x, y: y - height), controlPoint1: CGPoint(x: x * 2 / 3, y: y), controlPoint2: CGPoint(x: x * 5 / 6, y: y - height * 6 / 5)) // curve to top right
path.addLine(to: CGPoint(x: x, y: y)) // bottom right
path.close() // close the path from bottom right to bottom left
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = fillColor.cgColor
path.close()
fillColor.setFill()
path.fill()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = .yellow
}
}

CustomView with squiggle(wavy) top.(Swift)

I am trying to create a custom view a squiggle top and add an image view in the middle.
Something like this:
But I am not so used to UIBezierPath, so I am pretty confused.
This is what I have done so far.
class DemoView: UIView {
var path: UIBezierPath!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.darkGray
complexShape()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
complexShape()
}
func complexShape() {
path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 0.0))
path.addLine(to: CGPoint(x: self.frame.size.width/2 - 50.0, y: 0.0))
path.addLine(to: CGPoint(x: self.frame.size.width/2, y: 0.0))
path.addCurve(to: CGPoint(x: self.frame.size.width, y: 50.0),
controlPoint1: CGPoint(x: self.frame.size.width + 50.0, y: 25.0),
controlPoint2: CGPoint(x: self.frame.size.width - 150.0, y: 50.0))
path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
path.addLine(to: CGPoint(x: 0.0, y: self.frame.size.height))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
self.layer.mask = shapeLayer
}
}
extension CGFloat {
func toRadians() -> CGFloat {
return self * .pi / 180.0
}
}
The method below will let you add the background wave effect to another view. All you then need to do for the foreground square is add another view. Play with the constants to change the wave shape/height.
func addWaveBackground(to view: UIView){
let leftDrop:CGFloat = 0.4
let rightDrop: CGFloat = 0.3
let leftInflexionX: CGFloat = 0.4
let leftInflexionY: CGFloat = 0.47
let rightInflexionX: CGFloat = 0.6
let rightInflexionY: CGFloat = 0.22
let backView = UIView(frame: view.frame)
backView.backgroundColor = .gray
view.addSubview(backView)
let backLayer = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x:0, y: view.frame.height * leftDrop))
path.addCurve(to: CGPoint(x:view.frame.width, y: view.frame.height * rightDrop),
controlPoint1: CGPoint(x: view.frame.width * leftInflexionX, y: view.frame.height * leftInflexionY),
controlPoint2: CGPoint(x: view.frame.width * rightInflexionX, y: view.frame.height * rightInflexionY))
path.addLine(to: CGPoint(x:view.frame.width, y: 0))
path.close()
backLayer.fillColor = UIColor.blue.cgColor
backLayer.path = path.cgPath
backView.layer.addSublayer(backLayer)
}
Pass in the view you want to add the wave effect to (this will usually be the VC's main view).

How to draw a custom rounded rectangle in SWIFT?

I'm trying to draw a shape shown on the upper image programmatically.
This shape has custom rounded corners.
view.layer.cornerRadius = some value less than half diameter
This didn't work. Setting cornerRadius draws straight lines on every side(as seen on the bottom image) but the shape I'm trying to draw has no straight lines at all and it's not an oval.
I also tried below without luck. This code just draws an oval.
var path = UIBezierPath(ovalIn: CGRect(x: 000, y: 000, width: 000, height: 000))
I believe this can not be done by setting cornerRadius.
There should be something more.
I have no idea what class should I use and how.
Please anybody give me some direction.
Thanks!
I accomplished this by drawing a quadratic.
let dimention: CGFloat = some value
let path = UIBezierPath()
path.move(to: CGPoint(x: dimension/2, y: 0))
path.addQuadCurve(to: CGPoint(x: dimension, y: dimension/2),
controlPoint: CGPoint(x: dimension, y: 0))
path.addQuadCurve(to: CGPoint(x: dimension/2, y: dimension),
controlPoint: CGPoint(x: dimension, y: dimension))
path.addQuadCurve(to: CGPoint(x: 0, y: dimension/2),
controlPoint: CGPoint(x: 0, y: dimension))
path.addQuadCurve(to: CGPoint(x: dimension/2, y: 0),
controlPoint: CGPoint(x: 0, y: 0))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 1.0
shapeLayer.position = CGPoint(x: 0, y: 0)
self.someView.layer.addSublayer(shapeLayer)
You have to add cipsToBounds to the code for getting the image with given cornerRadius.
Try the blow codes ,
view.layer.cornerRadius = some value
view.clipsToBounds = true
You can use following path for your custom shape
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 150, height: 150), byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 8, height: 8))
You can change width and height according to your requirements and UI
class RoundView: UIView {
var roundCorner: UIRectCorner? = nil
var roundRadius:CGFloat = 0
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed(“nib name”, owner: self, options: nil))
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
isUserInteractionEnabled = true
}
override func layoutSubviews() {
super.layoutSubviews()
if roundCorner != nil {
// self.roundCorners([.topRight, .bottomLeft, .bottomRight], radius: 15)
self.roundCorners(roundCorner!, radius: roundRadius)
}
}
}

UIBezierPath partially stroke

I have this code to draw a rectangle which is rounded rect only on one side.
override func draw(_ rect: CGRect) {
// Drawing code
guard let context = UIGraphicsGetCurrentContext() else { return }
let lineWidth = CGFloat(4)
let pathRect = CGRect(x: 0, y: 0, width: rect.width, height: rect.height)
let path = UIBezierPath(roundedRect: pathRect.inset(by: UIEdgeInsets(top: lineWidth, left: lineWidth, bottom: lineWidth, right: 0)), byRoundingCorners: [.topLeft, .bottomLeft], cornerRadii: CGSize(width: 7, height: 7))
context.setFillColor(UIColor.black.cgColor)
path.fill()
context.setLineWidth(lineWidth)
}
I want to stroke it with red color on all but the right edge (no stroke on the right edge). How do I do it?
You’ll have to create your own path.
A couple of observations:
Don’t use the rect parameter. The rect is what is being asked to being drawn at this point in time, which may not be the entire view. Use bounds when figuring out what the overall path should be.
I might inset the path so that the stroke stays within the bounds of the view.
You can make this #IBDesignable if you want to also be able to see it rendered in IB.
You don’t really need UIGraphicsGetCurrentContext(). The UIKit methods fill(), stroke(), setFill(), and setStroke() methods automatically use the current context.
Thus:
#IBDesignable
class OpenRightView: UIView {
#IBInspectable var lineWidth: CGFloat = 4 { didSet { setNeedsDisplay() } }
#IBInspectable var radius: CGFloat = 7 { didSet { setNeedsDisplay() } }
#IBInspectable var fillColor: UIColor = .black { didSet { setNeedsDisplay() } }
#IBInspectable var strokeColor: UIColor = .red { didSet { setNeedsDisplay() } }
override func draw(_ rect: CGRect) {
let pathRect = bounds.inset(by: .init(top: lineWidth / 2, left: lineWidth / 2, bottom: lineWidth / 2, right: 0))
let path = UIBezierPath()
path.lineWidth = lineWidth
path.move(to: CGPoint(x: pathRect.maxX, y: pathRect.minY))
path.addLine(to: CGPoint(x: pathRect.minX + radius, y: pathRect.minY))
path.addQuadCurve(to: CGPoint(x: pathRect.minX, y: pathRect.minY + radius), controlPoint: pathRect.origin)
path.addLine(to: CGPoint(x: pathRect.minX, y: pathRect.maxY - radius))
path.addQuadCurve(to: CGPoint(x: pathRect.minX + radius, y: pathRect.maxY), controlPoint: CGPoint(x: pathRect.minX, y: pathRect.maxY))
path.addLine(to: CGPoint(x: pathRect.maxX, y: pathRect.maxY))
fillColor.setFill()
path.fill()
strokeColor.setStroke()
path.stroke()
}
}
That yields:
Theoretically, it might be more efficient to use CAShapeLayer and let Apple take care of the draw(_:) for us. E.g., they may have optimized the rendering to handle partial view updates, etc.
That might look like the following:
#IBDesignable
class OpenRightView: UIView {
#IBInspectable var lineWidth: CGFloat = 4 { didSet { updatePath() } }
#IBInspectable var radius: CGFloat = 7 { didSet { updatePath() } }
#IBInspectable var fillColor: UIColor = .black { didSet { shapeLayer.fillColor = fillColor.cgColor } }
#IBInspectable var strokeColor: UIColor = .red { didSet { shapeLayer.strokeColor = strokeColor.cgColor } }
lazy var shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = fillColor.cgColor
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.lineWidth = lineWidth
return shapeLayer
}()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
updatePath()
}
}
private extension OpenRightView {
func configure() {
layer.addSublayer(shapeLayer)
}
func updatePath() {
let pathRect = bounds.inset(by: .init(top: lineWidth / 2, left: lineWidth / 2, bottom: lineWidth / 2, right: 0))
let path = UIBezierPath()
path.move(to: CGPoint(x: pathRect.maxX, y: pathRect.minY))
path.addLine(to: CGPoint(x: pathRect.minX + radius, y: pathRect.minY))
path.addQuadCurve(to: CGPoint(x: pathRect.minX, y: pathRect.minY + radius), controlPoint: pathRect.origin)
path.addLine(to: CGPoint(x: pathRect.minX, y: pathRect.maxY - radius))
path.addQuadCurve(to: CGPoint(x: pathRect.minX + radius, y: pathRect.maxY), controlPoint: CGPoint(x: pathRect.minX, y: pathRect.maxY))
path.addLine(to: CGPoint(x: pathRect.maxX, y: pathRect.maxY))
shapeLayer.path = path.cgPath
shapeLayer.lineWidth = lineWidth
}
}

Resources