How to convert image view to custom shape (swift3) - ios

I am trying to convert the a single imageview to output in the in the object on the right. This is a very similar question to How to make imageview with different shape in swift. However that was in swift2. I tried it and it does not seem to work in swift3.

Try like this:
extension UIImageView {
func addMask(_ bezierPath: UIBezierPath) {
let pathMask = CAShapeLayer()
pathMask.path = bezierPath.cgPath
layer.mask = pathMask
}
}
Testing playground:
let myPicture = UIImage(data: try! Data(contentsOf: URL(string:"http://i.stack.imgur.com/Xs4RX.jpg")!))!
let iv = UIImageView(image: myPicture)
let bezierPath = UIBezierPath()
bezierPath.move(to: iv.center)
bezierPath.addLine(to: CGPoint(x: iv.frame.maxX, y: 0))
bezierPath.addLine(to: CGPoint(x: iv.frame.maxX, y: iv.frame.maxY))
bezierPath.addLine(to: CGPoint(x: 0, y: iv.frame.maxY))
bezierPath.addLine(to: .zero)
bezierPath.close()
iv.addMask(bezierPath)

Related

How to add a custom shape to an UIImageView in Swift?

I'm trying to add a custom shape to an imageView. Please check the below images.
This is the required one:
This is what I have done so far:
I'm new to Core Graphics and I have done this so far:
private func customImageClipper(imageV: UIImageView){
let path = UIBezierPath()
let size = imageV.frame.size
print(size)
path.move(to: CGPoint(x: 0.0, y: size.height))
path.addLine(to: CGPoint(x: 0.8, y: size.height/2))
path.close()
let shape = CAShapeLayer()
shape.path = path.cgPath
imageV.layer.sublayers = [shape]
}
I'm creating a function to achieve a shape like this, but whenever I pass the imageView into this function, I can not see any change at all. I know that I have to move from points to another point to achieve this shape, but I have never done this. Any help would be appreciated. This is how I'm calling this function:
imageV.layoutIfNeeded()
customImageClipper(imageV: imageV)
P.S.: I'm not using Storyboard, I have created this programmatically.
There are many ways to create shapes using UIBezierPaths. This post here discusses the use of the draw function to create a shape.
Here is an example using your clip function within the cell.
func clip(imageView: UIView, withOffset offset: CGFloat) {
let path = UIBezierPath()
//Move to Top Left
path.move(to: .init(x: imageView.bounds.size.width * offset, y: 0))
//Draw line from Top Left to Top Right
path.addLine(to: .init(x: imageView.bounds.size.width, y: 0))
//Draw Line from Top Right to Bottom Right
path.addLine(to: .init(x: imageView.bounds.size.width * (1 - offset), y: imageView.bounds.size.height))
//Draw Line from Bottom Right to Bottom Left
path.addLine(to: .init(x: 0, y: imageView.bounds.size.height))
//Close Path
path.close()
//Create the Shape Mask for the ImageView
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.black.cgColor
imageView.layer.mask = shapeLayer
}
In this function, the offset is the amount of angle you would like on the shape, ranging from 0 to 1. (0.4) seems to work for your requirements.
This shares a lot of similarities with Apseri's answer, except I chose the route of percentages, rather than exact size. Nothing wrong with either approach, I just found it easier to understand with percentages. :)
One last note to point out, I used this function in the layoutSubviews function.
override func layoutSubviews() {
super.layoutSubviews()
imageView.layoutIfNeeded()
clip(imageView: self.imageView, withOffset: 0.4)
}
This output the following image:
Hope this helps.
Here is example of some path clipping. Of course path can be also put via parameters, and this can be applied to any view, as shown.
Before:
After (grey background is below ScrollView background):
func customImageClipper(imageV: UIView){
let path = UIBezierPath()
let size = imageV.frame.size
path.move(to: CGPoint(x: size.width/3.0, y: 0))
path.addLine(to: CGPoint(x: size.width/3.0 + 50, y: 0))
path.addLine(to: CGPoint(x: size.width/3.0, y: size.height))
path.addLine(to: CGPoint(x: size.width/3.0 - 50, y: size.height))
path.addLine(to: CGPoint(x: size.width/3.0, y: 0))
path.close()
let shape = CAShapeLayer()
shape.path = path.cgPath
shape.fillColor = UIColor.black.cgColor
imageV.layer.mask = shape
}
1- Subclassing your UIImageView
2- implement your custom drawings inside setNeedsLayout using UIBezierPath
class MyCustomImageView: UIImageView {
override func setNeedsLayout() {
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: self.frame.size.width/2, y: self.frame.size.height))
path.addLineToPoint(CGPoint(x: self.frame.size.width, y: self.frame.size.height/2))
path.addLineToPoint(CGPoint(x: self.frame.size.width/2, y: 0))
path.addArcWithCenter(CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2), radius: self.frame.size.width/2, startAngle:-CGFloat(M_PI_2), endAngle: CGFloat(M_PI_2), clockwise: false)
path.moveToPoint(CGPoint(x: self.frame.size.width/2, y: self.frame.size.height))
path.closePath()
UIColor.redColor().setFill()
path.stroke()
path.bezierPathByReversingPath()
let shapeLayer = CAShapeLayer()
shapeLayer.frame = self.bounds
shapeLayer.path = path.CGPath
shapeLayer.fillColor = UIColor.redColor().CGColor
self.layer.mask = shapeLayer;
self.layer.masksToBounds = true;
}
}

How to clip a UIView with a bezier path?

I'm not totally sure to understand how UIBezierPath is supposed to work.
I have added a simple UIView in the middle of the screen, and I wanted to clip it by adding a mask to its layer. I tried this, thinking I'd get something like a losange in the middle of the view:
override func viewDidLoad() {
super.viewDidLoad()
viewToClip.backgroundColor = .white
let bezierPath = UIBezierPath()
bezierPath.move(to: viewToClip.center)
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x - 5, y: viewToClip.center.y))
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x, y: viewToClip.center.y - 5))
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x + 5, y: viewToClip.center.y))
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x, y: viewToClip.center.y + 5))
bezierPath.close()
let testLayer = CAShapeLayer()
testLayer.path = bezierPath.cgPath
viewToClip.layer.mask = testLayer
}
But instead of that, the view simply disappears from the screen. What am I doing wrong?
Thanks for your help.
Can you try
import UIKit
class bb: UIView {
var once:Bool = true
override func draw(_ rect: CGRect) {
if(once)
{
once = false
self.backgroundColor = .white
let bezierPath = UIBezierPath()
let cen = CGPoint.init(x: self.bounds.size.width/2, y: self.bounds.size.height/2)
bezierPath.move(to: cen)
bezierPath.addLine(to: CGPoint(x: cen.x - 5, y: cen.y))
bezierPath.addLine(to: CGPoint(x: cen.x, y: cen.y - 5))
bezierPath.addLine(to: CGPoint(x: cen.x + 5, y: cen.y))
bezierPath.addLine(to: CGPoint(x: cen.x, y: cen.y + 5))
bezierPath.close()
let testLayer = CAShapeLayer()
testLayer.path = bezierPath.cgPath
testLayer.lineWidth = 1.0
testLayer.strokeColor = UIColor.blue.cgColor
testLayer.fillColor = UIColor.green.cgColor
self.layer.addSublayer(testLayer)
}
}

swift Export CAShapeLayer animation to gif

I have a small code on Swift, that makes animation drawing of house. For animation drawing I use CAShapeLayer() based on UIBezierPath():
func setupDrawingLayer() {
// Stop and remove all other actions and pics on the animationLayer
clearLayer()
if let _ = animationLayer{
let pathRect: CGRect = animationLayer!.bounds.insetBy(dx: 100.0, dy: 100.0)
let bottomLeft = CGPoint(x: pathRect.minX, y: pathRect.minY)
let topLeft = CGPoint(x: pathRect.minX, y: pathRect.minY + pathRect.height * 2.0 / 3.0)
let bottomRight = CGPoint(x: pathRect.maxX, y: pathRect.minY)
let topRight = CGPoint(x: pathRect.maxX, y: pathRect.minY + pathRect.height * 2.0 / 3.0)
let roofTip = CGPoint(x: pathRect.midX, y: pathRect.maxY)
let path = UIBezierPath()
path.move(to: bottomLeft)
path.addLine(to: topLeft)
...
path.addLine(to: bottomLeft)
path.addLine(to: bottomRight)
let pathShapeLayer = CAShapeLayer()
pathShapeLayer.frame = animationLayer!.bounds
pathShapeLayer.bounds = pathRect
pathShapeLayer.isGeometryFlipped = true
pathShapeLayer.path = path.cgPath
pathShapeLayer.strokeColor = UIColor.black.cgColor
pathShapeLayer.fillColor = nil
pathShapeLayer.lineWidth = 10.0
pathShapeLayer.lineJoin = kCALineJoinBevel
animationLayer!.addSublayer(pathShapeLayer)
pathLayer = pathShapeLayer
}
}
I need to export this animation to GIF file. How can I do this?
Or may be you know some other solution, that can animate UIBezierPath() drawing with exporting to GIF?
Thank you.

Swift - How To Make A Table Containing Labels

This is what I want to achieve.
I tried coding it myself but the first outer view border does not show up.
Here's my code:
import UIKit
class InfoTableView: UIView {
override func drawRect(rect: CGRect) {
self.backgroundColor = UIColor.whiteColor()
let outerBorder = UIColorCode.init(hexString: "#666666")
let startingTopPoint = CGPoint(x: rect.minX, y: rect.minY)
let endingTopPoint = CGPoint(x: rect.maxX, y: rect.minY)
let startingPoint = CGPoint(x: rect.minX, y: rect.maxY)
let endingPoint = CGPoint(x: rect.maxX, y: rect.maxY)
// top
let tpPath = UIBezierPath()
tpPath.moveToPoint(startingPoint)
tpPath.addLineToPoint(endingTopPoint)
tpPath.lineWidth = 2.0
outerBorder.setStroke()
tpPath.stroke()
// bottom
let btPath = UIBezierPath()
btPath.moveToPoint(startingPoint)
btPath.addLineToPoint(endingPoint)
btPath.lineWidth = 2.0
outerBorder.setStroke()
btPath.stroke()
}
}
There are outer borders top and bottom. But only the bottom one shows up. I don't know where did I go wrong.
I have made little bit of edits on your code . Try if it works for you.
override func drawRect(rect: CGRect) {
// Drawing code
self.backgroundColor = UIColor.whiteColor()
let outerBorder = UIColor.redColor()
let lineWidth : CGFloat = 2.0
let insetRect = rect.insetBy(dx: lineWidth/2, dy: lineWidth/2)
let startingTopPoint = CGPointMake(insetRect.origin.x,insetRect.origin.y)
let endingTopPoint = CGPoint(x: insetRect.maxX, y: insetRect.minY)
let startingPoint = CGPoint(x: insetRect.minX, y: insetRect.maxY)
let endingPoint = CGPoint(x: insetRect.maxX, y: insetRect.maxY)
// top
let tpPath = UIBezierPath()
tpPath.moveToPoint(startingTopPoint)
tpPath.addLineToPoint(endingTopPoint)
tpPath.lineWidth = 2.0
outerBorder.setStroke()
tpPath.stroke()
// bottom
let btPath = UIBezierPath()
btPath.moveToPoint(startingPoint)
btPath.addLineToPoint(endingPoint)
btPath.lineWidth = 2.0
outerBorder.setStroke()
btPath.stroke()
}

Creating Triangle with UIBezierPath in Swift

I am trying to understand how to create a triangle shape with Swift. I found this code that creates a triangle.
class TriangleLayer: CAShapeLayer {
let innerPadding: CGFloat = 30.0
override init() {
super.init()
fillColor = Colors.red.CGColor
strokeColor = Colors.red.CGColor
lineWidth = 7.0
lineCap = kCALineCapRound
lineJoin = kCALineJoinRound
path = trianglePathSmall.CGPath
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var trianglePathSmall: UIBezierPath {
let trianglePath = UIBezierPath()
trianglePath.moveToPoint(CGPoint(x: 5.0 + innerPadding, y: 95.0)) // #1
trianglePath.addLineToPoint(CGPoint(x: 50.0, y: 12.5 + innerPadding)) // #2
trianglePath.addLineToPoint(CGPoint(x: 95.0 - innerPadding, y: 95.0)) // #3
trianglePath.closePath()
return trianglePath
}
And this code creates a shape like this
in the middle of the screen.
I tried to tweak and play around with it to understand how it works; however, at this point I realised that I got lost with the logic quite a bit. I placed the CGPoints of above triangle on an x-y axis in my head and it seems something like:
#1 x:35, y:95 #3 x:65, y:95
  #2 x:50, y: 42.5
But the triangle is created upside-down if I place the dots on the x-y axis.
What I want to achieve is what the axis tells, and I want to achieve..
. . .
<like this. not this>
. . .
You just have the axes in your head upside down. The coordinate system starts at 0,0 and extends right in X and down in Y.
So your points are really:
#2 x:50, y: 42.5
#1 x:35, y:95 #3 x:65, y:95
to get your desired triangle you'd have something like:
#1 x:35, y:95 #3 x:65, y:95
#2 x:50, y: 147.5
Result triangles
Code in swift5
//TriangleView
extension UIView {
func setRightTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width //you can use triangleView.frame.size.height
let path = CGMutablePath()
path.move(to: CGPoint(x: heightWidth/2, y: 0))
path.addLine(to: CGPoint(x:heightWidth, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setLeftTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: heightWidth/2, y: 0))
path.addLine(to: CGPoint(x:0, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setUpTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth, y:heightWidth))
path.addLine(to: CGPoint(x:0, y:heightWidth))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setDownTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth, y:0))
path.addLine(to: CGPoint(x:0, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
}
Swift 4.*
The easiest way of doing it by using AutoLayout:
Open your Storyboard and drag a UIView in UIViewController, position it and set the size as you wish (that's the place where the triangle will be). Set the view background to be transparent.
Create a new class, you can name it however you want (I named mine TriangleView). This will be the content of that class:
class TriangleView: UIView {
// predefined variables that can be changed
var startPoint: CGPoint = CGPoint(x: 0.0, y: 0.5)
var endPoint: CGPoint = CGPoint(x: 1.0, y: 0.5)
var firstGradientColor: UIColor = UIColor.white
var secondGradientColor: UIColor = UIColor.blue
let gradient: CAGradientLayer = CAGradientLayer()
override func draw(_ rect: CGRect) {
let height = self.layer.frame.size.height
let width = self.layer.frame.size.width
// draw the triangle
let path = UIBezierPath()
path.move(to: CGPoint(x: width / 2, y: 0))
path.addLine(to: CGPoint(x: width, y: height))
path.addLine(to: CGPoint(x: 0, y: height))
path.close()
// draw the triangle 'upside down'
// let path = UIBezierPath()
// path.move(to: CGPoint(x: 0, y: 0))
// path.addLine(to: CGPoint(x: width, y: 0))
// path.addLine(to: CGPoint(x: width / 2, y: height))
// path.close()
// add path to layer
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.lineWidth = 1.0
// Add the gradient for the view background if needed
gradient.colors = [firstGradientColor.cgColor, secondGradiendColor.cgColor]
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.frame = self.bounds
gradient.mask = shapeLayer
self.layer.addSublayer(gradient)
}
}
Go to your Storyboard, select the UIView and in Identity Inspector write the class name TriangleView
Enjoy your triangle! :)

Resources