Slant one edge of rectangle UIBezierPath - swift

I'm trying to slant one edge of a UIBezierPath in Swift. I believe you do this by using move on one of the edges. Similar to the below -
let offset: CGFloat = 60.0;
let path = UIBezierPath()
let width = self.bounds.width - offset
let upperLeftPoint = CGPoint(x: self.bounds.origin.x + width + offset, y: self.bounds.origin.y)
let upperRightPoint = CGPoint(x: self.bounds.origin.x, y: self.bounds.origin.y)
let lowerRightPoint = CGPoint(x: self.bounds.origin.x, y: self.bounds.size.height)
let lowerLeftPoint = CGPoint(x: width - offset, y: self.bounds.size.height)
path.move(to: upperLeftPoint)
path.addLine(to: upperRightPoint)
path.addLine(to: lowerRightPoint)
path.addLine(to: lowerLeftPoint)
path.addLine(to: upperLeftPoint)
// Close the path. This will create the last line automatically.
path.close()
UIColor.red.setFill()
path.fill()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
self.layer.mask = shapeLayer;
However this isn't quite what I'm trying to achieve. Below is what I'm attempting to achieve.

I achieved this shape by doing the following -
class Header: UIView {
var path: UIBezierPath!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func createHeader() {
// Drawing code
// Get Height and Width
let layerHeight = layer.frame.height
let layerWidth = layer.frame.width
// Create Path
let bezierPath = UIBezierPath()
// Points
let pointA = CGPoint(x: 0, y: 0)
let pointB = CGPoint(x: layerWidth, y: 0)
let pointC = CGPoint(x: layerWidth, y: layerHeight*2/3)
let pointD = CGPoint(x: 0, y: layerHeight)
// Draw the path
bezierPath.move(to: pointA)
bezierPath.addLine(to: pointB)
bezierPath.addLine(to: pointC)
bezierPath.addLine(to: pointD)
bezierPath.close()
// Mask to Path
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
layer.mask = shapeLayer
}
override func draw(_ rect: CGRect) {
// self.createRectangle()
self.createHeader()
}
}

Related

curve in tabbar view and bottom popup not working?

Hello all, I tried to add arc for UIBezierPath I could not able to get the exact curve,
here is my code here I have added the bezier path for the added curve from the center position.
#IBDesignable
class MyTabBar: 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
//The below 4 lines are for shadow above the bar. you can skip them if you do not want a shadow
shapeLayer.shadowOffset = CGSize(width:0, height:0)
shapeLayer.shadowRadius = 10
shapeLayer.shadowColor = UIColor.gray.cgColor
shapeLayer.shadowOpacity = 0.3
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
path.addCurve(to: CGPoint(x: centerWidth, y: height),
controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height))
path.addCurve(to: CGPoint(x: (centerWidth + height * 2), y: 0),
controlPoint1: CGPoint(x: centerWidth + 35, y: height), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))
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 hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard !clipsToBounds && !isHidden && alpha > 0 else { return nil }
for member in subviews.reversed() {
let subPoint = member.convert(point, from: self)
guard let result = member.hitTest(subPoint, with: event) else { continue }
return result
}
return nil
}
}
this is tab bar controller added plus button in center view center, and the when tap the plus button to add the curve based popup should show, I don't know how to add curve based popup.
class TabbarViewController: UITabBarController,UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.navigationController?.navigationBar.isHidden = true
setupMiddleButton()
}
// TabBarButton – Setup Middle Button
func setupMiddleButton() {
let middleBtn = UIButton(frame: CGRect(x: (self.view.bounds.width / 2)-25, y: -20, width: 50, height: 50))
middleBtn.setImage(UIImage(named: "PlusBtn"), for: .normal)
self.tabBar.addSubview(middleBtn)
middleBtn.addTarget(self, action: #selector(self.menuButtonAction), for: .touchUpInside)
self.view.layoutIfNeeded()
}
// Menu Button Touch Action
#objc func menuButtonAction(sender: UIButton) {
//show the popUp
}
}
Please share me the findings & share me your refreance
Thanks
New edit:
I created a general-purpose method that will generate polygons with a mixture of sharp and rounded corners of different radii. I used it to create a project with a look rather like what you are after. You can download it from Github:
https://github.com/DuncanMC/CustomTabBarController.git
Here's what it looks like:
Note that the area of the tab's view controller that extends into the tab bar controller does not get taps. If you try to tap there, it triggers the tab bar controller. You will have to do some more tinkering to get that part to work.
Ultimately you may have to create a custom UITabBar (or UITabBar-like component) and possibly a custom parent view controller that acts like a UITabBar, in order to get what you want.
The method that creates polygon paths is called buildPolygonPathFrom(points:defaultCornerRadius:)
It takes an array of PolygonPoint structs. Those are defined like this:
/// A struct describing a single vertex in a polygon. Used in building polygon paths with a mixture of rounded an sharp-edged vertexes.
public struct PolygonPoint {
let point: CGPoint
let isRounded: Bool
let customCornerRadius: CGFloat?
init(point: CGPoint, isRounded: Bool, customCornerRadius: CGFloat? = nil) {
self.point = point
self.isRounded = isRounded
self.customCornerRadius = customCornerRadius
}
init(previousPoint: PolygonPoint, isRounded: Bool) {
self.init(point: previousPoint.point, isRounded: isRounded, customCornerRadius: previousPoint.customCornerRadius)
}
}
The code to build the path for the custom tab bar looks like this:
func tabBarMaskPath() -> CGPath? {
let width = bounds.width
let height = bounds.height
guard width > 0 && height > 0 else { return nil }
let dentRadius: CGFloat = 35
let cornerRadius: CGFloat = 20
let topFlatPartWidth = (width - dentRadius * 2.0) / 2
let polygonPoints = [
PolygonPoint(point: CGPoint(x: 0, y: 0), // Point 0
isRounded: true,
customCornerRadius: cornerRadius),
PolygonPoint(point: CGPoint(x: 0, y: height), // Point 1
isRounded: false),
PolygonPoint(point: CGPoint(x: width, y: height), // Point 2
isRounded: false),
PolygonPoint(point: CGPoint(x: width, y: 0), // Point 3
isRounded: true,
customCornerRadius: cornerRadius),
PolygonPoint(point: CGPoint(x: topFlatPartWidth + dentRadius * 2, y: 0), // Point 4
isRounded: true,
customCornerRadius: cornerRadius),
PolygonPoint(point: CGPoint(x: topFlatPartWidth + dentRadius * 2, y: dentRadius + cornerRadius), // Point 5
isRounded: true,
customCornerRadius: dentRadius),
PolygonPoint(point: CGPoint(x: topFlatPartWidth , y: dentRadius + cornerRadius), // Point 6
isRounded: true,
customCornerRadius: dentRadius),
PolygonPoint(point: CGPoint(x: topFlatPartWidth , y: 0), // Point 7
isRounded: true,
customCornerRadius: cornerRadius),
]
return buildPolygonPathFrom(points: polygonPoints, defaultCornerRadius: 0)
}
Previous answer:
I just tried it, and it is possible to subclass UITabBar. I created a subclass of UITabBar where I use a mask layer to cut a circular "notch" out of the top of the tab bar. The code is below. It looks like the screenshot below. It isn't quite what you're after, but it's a start:
(The background color for the "Page 1" view controller is set to light gray, and you can see that color showing through in the "notch" I cut out of the tab bar.)
//
// CustomTabBar.swift
// TabBarController
//
// Created by Duncan Champney on 3/31/21.
//
import UIKit
class CustomTabBar: UITabBar {
var maskLayer = CAShapeLayer()
override var frame: CGRect {
didSet {
configureMaskLayer()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
configureMaskLayer()
self.layer.mask = maskLayer
self.layer.borderWidth = 0
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configureMaskLayer()
self.layer.mask = maskLayer
}
func configureMaskLayer() {
let rect = layer.bounds
maskLayer.frame = rect
let circleBoxSize = rect.size.height * 1.25
maskLayer.fillRule = .evenOdd
let path = UIBezierPath(rect: rect)
let circleRect = CGRect(x: rect.size.width/2 - circleBoxSize / 2,
y: -circleBoxSize/2,
width: circleBoxSize,
height: circleBoxSize)
let circle = UIBezierPath.init(ovalIn: circleRect)
path.append(circle)
maskLayer.path = path.cgPath
maskLayer.fillColor = UIColor.white.cgColor // Any opaque color works and has no effect
}
}
Edit:
To draw your popover view you'll need to create a filled path that shape. You'll have to construct a custom shape like that with a combination of lines and arcs. I suggest using a CGMutablePath and the method addArc(tangent1End:tangent2End:radius:transform:) since that enables you to provide endpoints rather than angles.
Edit #2:
Another part of the puzzle:
Here is a custom UIView subclass that masks itself in the shape you're after
//
// ShapeWithTabView.swift
// ShapeWithTab
//
// Created by Duncan Champney on 4/1/21.
//
import UIKit
class ShapeWithTabView: UIView {
var cornerRadius: CGFloat = 20
var tabRadius: CGFloat = 60
var tabExtent: CGFloat = 0
var shapeLayer = CAShapeLayer()
var maskLayer = CAShapeLayer()
func buildShapeLayerPath() -> CGPath {
let boxWidth = min(bounds.size.width - 40, 686)
let boxHeight = min(bounds.size.height - 40 - tabRadius * 2 - tabExtent, 832)
// These are the corners of the view's primary rectangle
let point1 = CGPoint(x: 0, y: boxHeight)
let point2 = CGPoint(x: 0, y: 0)
let point3 = CGPoint(x: boxWidth, y: 0)
let point4 = CGPoint(x: boxWidth, y: boxHeight)
// These are the corners of the "tab" that extends outside the view's normal bounds.
let tabPoint1 = CGPoint(x: boxWidth / 2 + tabRadius, y: boxHeight)
let tabPoint2 = CGPoint(x: boxWidth / 2 + tabRadius, y: boxHeight + tabExtent + tabRadius * 2 )
let tabPoint3 = CGPoint(x: boxWidth / 2 - tabRadius, y: boxHeight + tabExtent + tabRadius * 2)
let tabPoint4 = CGPoint(x: boxWidth / 2 - tabRadius , y: boxHeight)
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: boxHeight - cornerRadius))
path.addArc(tangent1End: point2,
tangent2End: point3,
radius: cornerRadius)
path.addArc(tangent1End: point3,
tangent2End: point4,
radius: cornerRadius)
path.addArc(tangent1End: point4,
tangent2End: point1,
radius: cornerRadius)
//
path.addArc(tangent1End: tabPoint1,
tangent2End: tabPoint2,
radius: tabRadius)
path.addArc(tangent1End: tabPoint2,
tangent2End: tabPoint3,
radius: tabRadius)
path.addArc(tangent1End: tabPoint3,
tangent2End: tabPoint4,
radius: tabRadius)
path.addArc(tangent1End: tabPoint4,
tangent2End: point1,
radius: tabRadius)
path.addArc(tangent1End: point1,
tangent2End: point2,
radius: cornerRadius)
return path
}
func doInitSetup() {
self.layer.addSublayer(shapeLayer)
self.layer.mask = maskLayer
backgroundColor = .lightGray
//Configure a shape layer to draw an outline
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 2
//Configure a mask layer to mask the view to our custom shape
maskLayer.fillColor = UIColor.white.cgColor
maskLayer.strokeColor = UIColor.white.cgColor
maskLayer.lineWidth = 2
}
override init(frame: CGRect) {
super.init(frame: frame)
self.doInitSetup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.doInitSetup()
}
public func updateShapeLayerPath() {
let path = buildShapeLayerPath()
shapeLayer.path = path
maskLayer.path = path
}
override var frame: CGRect {
didSet {
print("New frame = \(frame)")
shapeLayer.frame = layer.bounds
}
}
}
Combined with the modified tab bar from above, it looks like the image below. The final task is to get the custom view sized and positioned correctly, and have it land on top of the tab bar.

How to create Right Angle View in Swift?

I am able to create triangle view in swift using below code-
class TriangleView: UIView {
override func draw(_ rect: CGRect) {
// Get Height and Width
let layerHeight = layer.frame.height
let layerWidth = layer.frame.width
// Create Path
let bezierPath = UIBezierPath()
// Draw Points
bezierPath.move(to: CGPoint(x: 0, y: layerHeight))
bezierPath.addLine(to: CGPoint(x: layerWidth, y: layerHeight))
bezierPath.addLine(to: CGPoint(x: layerWidth / 2, y: 0))
bezierPath.addLine(to: CGPoint(x: 0, y: layerHeight))
bezierPath.close()
// Apply Color
UIColor.red.setFill()
bezierPath.fill()
// Mask to Path
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
layer.mask = shapeLayer
}
}
override func viewDidLoad() {
super.viewDidLoad()
let triangle = TriangleView(frame: CGRect(x: 0, y: 0, width: 55 , height: 60))
triangle.backgroundColor = .white
triangle.transform = CGAffineTransform(rotationAngle: .pi * 4.49)
imageView.addSubview(triangle)
}
Now problem is that I want to change it to the right angle triangle using transform properties. How to achieve this?
Expected Result
Current Result
I hope you want this part.
Here is the code.
override func draw(_ rect: CGRect) {
let path = UIBezierPath.init()
// top left
path.move(to: .zero)
// top right
path.addLine(to: CGPoint(x: bounds.size.width,
y: 0))
// bottom left offset
path.addLine(to: CGPoint(x: bounds.size.width * 0.1,
y: bounds.size.height))
// bottom left
path.addLine(to: CGPoint(x: 0,
y: bounds.size.height))
// top left
path.close()
// change fill color here.
UIColor.black.setFill()
path.fill()
}
Result
How I drew
Modification
If you change the code UIColor.black.setFill() to
let fillColor = UIColor(red: 0xF9/0xff, green: 0x0D/0xff, blue: 0x5A/0xff, alpha: 1)
fillColor.setFill()
you get this result.
Have fun coding.
You're currently drawing 'a'. You want to draw 'b'.
extension UIBezierPath {
convenience init(rightAngledTriangleInRect: CGRect, offset withOffset: CGFloat) {
self.init()
move(to: CGPoint(x: rect.minX, y: rect.minY))
addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
addLine(to: CGPoint(x: rect.minX + offset, y: rect.maxY))
addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
addLine(to: CGPoint(x: rect.minX, y: rect.minY))
close()
}
}
Then use by:
let path = UIBezierPath.init(rightAngledTriangleInRect: rect, withOffset: 20.0)
Note that there's no need to add self. in the convenience inits for the bezier path instructions.
If you're doing a lot of drawing, I'd recommend adding a number of these inits to make life easier.
To use in your current TriangleView:
class TriangleView: UIView {
override func draw(_ rect: CGRect) {
let path = UIBezierPath.init(rightAngledTriangleInRect: rect, withOffset: 20.0)
let shape = CAShapeLayer()
shape.path = path.cgPath
// Set the mask
layer.mask = shape
}
}
There are some easy way too for achivieng this but for using transform here is the sample code.use this transform instead of yours.it's sample code for having right angle triangle from your drawing:
triangle.transform = CGAffineTransform.identity.rotated(by: CGFloat.pi).translatedBy(x: (triangle.bounds.width / 2), y: 0.0)
you should notice that CGAffineTransform rotate the view from it's origins

How to add curved view top left cornor in swift

How to add uiview to top-left corner with curves.
I have done somethings like this
class curvedView: UIView {
override func draw(_ rect: CGRect) {
let color = UIColor.blue
let y:CGFloat = 0
let myBezier = UIBezierPath()
myBezier.move(to: CGPoint(x: 0, y: y))
myBezier.addQuadCurve(to: CGPoint(x: rect.width, y: y), controlPoint: CGPoint(x: rect.width / 2, y: rect.height * 2))
myBezier.addLine(to: CGPoint(x: rect.width, y: rect.height))
myBezier.addLine(to: CGPoint(x: 0, y: rect.height))
myBezier.close()
color.setFill()
myBezier.fill()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = UIColor.clear
}
}
But this create a curve in U shape but i want curve from x position 180 to Y position 250.
Any help would be so thankful.
you can try these codes.it will help you
let path = UIBezierPath(roundedRect:viewToRound.bounds, byRoundingCorners:[.TopRight, .BottomLeft], cornerRadii: CGSizeMake(20, 20))
let maskLayer = CAShapeLayer()
maskLayer.path = path.CGPath
viewToRound.layer.mask = maskLayer

Swift 4 - Create UIView with 2 background colors

I have a UIView having equal width/height as of its superview. This is what I want to get.
For the time being I have used a static background image. Any ideas?
Result
Code
import UIKit
#IBDesignable
public class AngleView: UIView {
#IBInspectable public var fillColor: UIColor = .blue { didSet { setNeedsLayout() } }
var points: [CGPoint] = [
.zero,
CGPoint(x: 1, y: 0),
CGPoint(x: 1, y: 0.4),
CGPoint(x: 0, y: 0.5)
] { didSet { setNeedsLayout() } }
private lazy var shapeLayer: CAShapeLayer = {
let _shapeLayer = CAShapeLayer()
self.layer.insertSublayer(_shapeLayer, at: 0)
return _shapeLayer
}()
override public func layoutSubviews() {
shapeLayer.fillColor = fillColor.cgColor
guard points.count > 2 else {
shapeLayer.path = nil
return
}
let path = UIBezierPath()
path.move(to: convert(relativePoint: points[0]))
for point in points.dropFirst() {
path.addLine(to: convert(relativePoint: point))
}
path.close()
shapeLayer.path = path.cgPath
}
private func convert(relativePoint point: CGPoint) -> CGPoint {
return CGPoint(x: point.x * bounds.width + bounds.origin.x, y: point.y * bounds.height + bounds.origin.y)
}
}
Source
Just modified the values :D
swift 4: you can instead ,of making a view with several background colors, add sublayer to your view.
//connect your view.
#IBOutlet weak var yourView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
//add a shape
let shape = CAShapeLayer()
self.yourView.layer.addSublayer(shape)
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.black.cgColor
// add your path
// you can simply adjust the points.
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: (3*yourView.bounds.height)/4))
path.addLine(to: CGPoint(x:self.yourView.bounds.width , y: yourView.bounds.height/2))
path.addLine(to: CGPoint(x: self.yourView.bounds.width, y: 0))
path.close()
shape.path = path.cgPath
}

Add gradient color to a custom UIView

I realized a custom UIView class to a realize a sort of trapeze; following code I had used to realize the class:
import UIKit
import ChameleonFramework
class PolygonalView: UIView
{
override init(frame: CGRect)
{
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.beginPath()
context.move(to: CGPoint(x: rect.minX, y: rect.minY))
context.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
context.addLine(to: CGPoint(x: rect.maxX, y: 171))
context.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
context.closePath()
context.setFillColor(FlatSkyBlue().cgColor)
context.fillPath()
}
}
Then in my UIViewController I had create my view:
let pol = PolygonalView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 196))
pol.backgroundColor = UIColor.clear
self.view.addSubview(pol)
I achieved my goal, yep! But a problem has arisen: if I set a gradient, this gradient cover all the rectangular frame of the view.
Following codes and image...
let gradientColor = CAGradientLayer()
gradientColor.frame = pol.bounds
gradientColor.colors = [FlatSkyBlue().cgColor, FlatSkyBlueDark().cgColor, FlatBlue().cgColor]
gradientColor.startPoint = CGPoint(x: 0, y: 0)
gradientColor.endPoint = CGPoint(x: 1, y: 1)
pol.layer.addSublayer(gradientColor)
Any solutions to adapt the gradient to the real shape of my View?
Set the gradientColor layer's mask property to CAShapeLayer() that has the correct shape filled with black
let mask = CAShapeLayer()
mask.frame = // to whatever size it should be
mask.path = yourShapeBezierHERE.cgPath
mask.fillColor = UIColor.black.cgColor
gradientColor.mask = mask