How can I set image for UIBezierPath - ios

I want to set image inside this path (i.e) I want imageview instead of red colour
enter image description here
Here is my code
import UIKit
#IBDesignable
class diagonalviewprofile: UIView {
#IBInspectable var color : UIColor? = UIColor.red {
didSet {
// self.layer.backgroundColor = self.color?.cgColor
}
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = UIColor.clear
}
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
//get the size of the view
let size = self.bounds.size
//get 4 points for the shape layer
let p1 = self.bounds.origin
let p2 = CGPoint(x: p1.x + size.width, y: p1.y)
let p3 = CGPoint(x: p2.x, y: size.height - 150)
let p4 = CGPoint(x: p1.x, y: size.height - 30)
//create the path
let path = UIBezierPath()
path.move(to: p1)
path.addLine(to: p2)
path.addLine(to: p3)
path.addLine(to: p4)
path.close()
(color ?? UIColor.red).set()
path.fill()
}
}
I want to set image inside this path (i.e) I want imageview instead of red colour

You can set image like:
// get an image of the graphics context
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!

Related

Mask a gradient layer with the intersection of two shape layers in Swift

I have two shapes of type CAShapeLayer (e.g. one box and a circle) and a gradient layer of type CAGradientLayer. How can I mask the gradient layer with the intersection of the two shapes like this picture in Swift?
Not exactly clear what you mean by "intersection of the two shapes" ... but maybe this is what you're going for:
To get that, we can create a CAShapeLayer with an oval (round) path, and use it as a mask on the gradient layer.
Here's some example code:
class GradientMaskingViewController: UIViewController {
let gradView = MaskedGradView()
override func viewDidLoad() {
super.viewDidLoad()
gradView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(gradView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
gradView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
gradView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
gradView.heightAnchor.constraint(equalTo: gradView.widthAnchor),
gradView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
gradView.colorArray = [
.blue, .orange, .purple, .yellow
]
}
}
class MaskedGradView: UIView {
enum Direction {
case horizontal, vertical, diagnal
}
public var colorArray: [UIColor] = [] {
didSet {
setNeedsLayout()
}
}
public var locationsArray: [NSNumber] = [] {
didSet {
setNeedsLayout()
}
}
public var direction: Direction = .vertical {
didSet {
setNeedsLayout()
}
}
private let gLayer = CAGradientLayer()
private let maskLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
// add gradient layer as a sublayer
layer.addSublayer(gLayer)
// mask it
gLayer.mask = maskLayer
// we'll use a 120-point diameter circle for the mask
maskLayer.path = UIBezierPath(ovalIn: CGRect(x: 0.0, y: 0.0, width: 120.0, height: 120.0)).cgPath
// so we can see this view's frame
layer.borderColor = UIColor.black.cgColor
layer.borderWidth = 1
}
override func layoutSubviews() {
super.layoutSubviews()
// update gradient layer
// frame
// colors
// locations
gLayer.frame = bounds
gLayer.colors = colorArray.map({ $0.cgColor })
if locationsArray.count > 0 {
gLayer.locations = locationsArray
}
switch direction {
case .horizontal:
gLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
case .vertical:
gLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
case .diagnal:
gLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
}
}
// touch code to drag the circular mask around
private var curPos: CGPoint = .zero
private var lPos: CGPoint = .zero
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
curPos = touch.location(in: self)
lPos = maskLayer.position
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let newPos = touch.location(in: self)
let diffX = newPos.x - curPos.x
let diffY = newPos.y - curPos.y
CATransaction.begin()
CATransaction.setDisableActions(true)
maskLayer.position = CGPoint(x: lPos.x + diffX, y: lPos.y + diffY)
CATransaction.commit() }
}
To help make it clear, I added touch handling code so you can drag the circle around inside the view:
Edit - after comment (but still missing details), let's try this again...
We can get the desired output by using multiple layers.
a white-filled gray-bordered rectangle CAShapeLayer
a white-filled NON-bordered oval CAShapeLayer
a CAGradientLayer masked with an oval CAShapeLayer
a NON-filled gray-bordered oval CAShapeLayer
So, we start with a view:
add a white-filled gray-bordered rectangle CAShapeLayer:
add a white-filled NON-bordered oval CAShapeLayer (red first, to show it clearly):
add a CAGradientLayer:
mask it with an oval CAShapeLayer:
finally, add a NON-filled gray-bordered oval CAShapeLayer:
Here's the example code:
class GradientMaskingViewController: UIViewController {
let gradView = MultiLayeredGradView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
gradView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(gradView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
gradView.widthAnchor.constraint(equalToConstant: 240.0),
gradView.heightAnchor.constraint(equalTo: gradView.widthAnchor),
gradView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
gradView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
}
}
class MultiLayeredGradView: UIView {
private let rectLayer = CAShapeLayer()
private let filledCircleLayer = CAShapeLayer()
private let gradLayer = CAGradientLayer()
private let maskLayer = CAShapeLayer()
private let outlineCircleLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
// add filled-bordered rect layer as a sublayer
layer.addSublayer(rectLayer)
// add filled circle layer as a sublayer
layer.addSublayer(filledCircleLayer)
// add gradient layer as a sublayer
layer.addSublayer(gradLayer)
// mask it
gradLayer.mask = maskLayer
// add outline circle layer as a sublayer
layer.addSublayer(outlineCircleLayer)
let bColor: CGColor = UIColor.gray.cgColor
let fColor: CGColor = UIColor.white.cgColor
// filled-outlined
rectLayer.strokeColor = bColor
rectLayer.fillColor = fColor
rectLayer.lineWidth = 2
// filled
filledCircleLayer.fillColor = fColor
// clear-outlined
outlineCircleLayer.strokeColor = bColor
outlineCircleLayer.fillColor = UIColor.clear.cgColor
outlineCircleLayer.lineWidth = 2
// gradient layer properties
let colorArray: [UIColor] = [
.blue, .orange, .purple, .yellow
]
gradLayer.colors = colorArray.map({ $0.cgColor })
gradLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
}
override func layoutSubviews() {
super.layoutSubviews()
// circle diameter is 45% of the width of the view
let circleDiameter: CGFloat = bounds.width * 0.45
// circle Top is at vertical midpoint
// circle is moved Left by 25% of the circle diameter
let circleBounds: CGRect = CGRect(x: bounds.minX - circleDiameter * 0.25,
y: bounds.maxY * 0.5,
width: circleDiameter,
height: circleDiameter)
// gradient layer fills the bounds
gradLayer.frame = bounds
let rectPath = UIBezierPath(rect: bounds).cgPath
rectLayer.path = rectPath
let circlePath = UIBezierPath(ovalIn: circleBounds).cgPath
filledCircleLayer.path = circlePath
outlineCircleLayer.path = circlePath
maskLayer.path = circlePath
}
}

How to create particular shape for Button by using UIBezierPath as shown in below Attached Image with Swift Code?

I tried with many ways to draw buttons as shown in image, But I'm able to draw only hexagonal . Am not able to draw as show in image . Please help me how to draw as shown in image. Thank you
You can do this with UIBezierPath and CAShapeLayer
Create a UIButton subclass and override layoutSubviews(), which is where you'll update your path, connecting the points with lines.
Here is a starting point for you:
class AngledButton: UIButton {
var bPath = UIBezierPath()
var theShapeLayer = CAShapeLayer()
var fillColor: UIColor = UIColor.white
var borderColor: UIColor = UIColor.gray
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
layer.addSublayer(theShapeLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
let p1 = CGPoint.zero
let p2 = CGPoint(x: bounds.width - bounds.height * 0.3, y: 0.0)
let p3 = CGPoint(x: bounds.width, y: bounds.height * 0.5)
let p4 = CGPoint(x: bounds.width - bounds.height * 0.3, y: bounds.height)
let p5 = CGPoint(x: 0.0, y: bounds.height)
bPath.move(to: p1)
bPath.addLine(to: p2)
bPath.addLine(to: p3)
bPath.addLine(to: p4)
bPath.addLine(to: p5)
bPath.close()
theShapeLayer.path = bPath.cgPath
theShapeLayer.fillColor = self.fillColor.cgColor
theShapeLayer.strokeColor = self.borderColor.cgColor
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// only allow tap *within* the bezier path
if bPath.contains(point) {
return self
}
return nil
}
}
By also overriding hitTest() the button will only be tapped if the tap is within the bezier path area. You can then overlap your buttons, and a tap at the upper-right corner will "pass through" to the button underneath.
Result:
Notes:
You can make this #IBDesignable to see the layout in Interface Builder
You'll need to play with the Title Insets to get the title labels to account for the angled edge
This is just a starting point, but should get you on your way

Transform UIView shape

I want to transform my UIView's shape into this (the white UIView - look at the right edge) using Swift:
Any ideas how to do it? I guess I should use the transform functionality, but I don't know how exactly. Thanks in advance!
Update:
Here is the update, I misunderstood your question in my first answer
You need to subclass UIView and override the default draw method with your own behaviour. To Adjust the angle you can change the offsetFactor
class ShapeView : UIView {
public var bgc = UIColor.clear
override init(frame: CGRect) {
super.init(frame: frame)
//backup old background color
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
let size = self.bounds.size
let offsetFactor = size.width * 0.98
//define the points
let tl = self.bounds.origin //top left
let tr = CGPoint(x:tl.x + size.width, y:tl.y) //top right
let br = CGPoint(x:tl.x + offsetFactor, y:tr.y + size.height) // bottom right
let bl = CGPoint(x:tl.x, y:tr.y + size.height) //bottom left
let path = UIBezierPath()
path.move(to: tl)
path.addLine(to: tr)
path.addLine(to: br)
path.addLine(to: bl)
path.close()
//set old background color
bgc.set()
path.fill()
}
}
Image
I hope I did understand your question right.
You need to subclass UIView and override the default draw method with your own behaviour. To Adjust the spire you can change the offsetFactor
This should do the job
class ShapeView : UIView {
public var bgc = UIColor.clear
override init(frame: CGRect) {
super.init(frame: frame)
//backup old background color
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
let size = self.bounds.size
let offsetFactor = size.height * 0.7
//define the points
let tl = self.bounds.origin //top left
let tr = CGPoint(x:tl.x + size.width, y:tl.y) //top right
let br = CGPoint(x:tr.x, y:tr.y + offsetFactor) // bottom right
let bm = CGPoint(x:size.width/2, y:size.height) ////bottom middle
let bl = CGPoint(x:tl.x, y:offsetFactor) //bottom left
let path = UIBezierPath()
path.move(to: tl)
path.addLine(to: tr)
path.addLine(to: br)
path.addLine(to: bm)
path.addLine(to: bl)
path.close()
//set old background color
bgc.set()
path.fill()
}
}
Storyboard and Simulator
Dudes, I found an easier solution that works perfectly as an extension to UIView:
extension UIView {
func makeItEdgy() {
let bounds = self.bounds
// Create path to mask the view
let path = CGMutablePath()
path.move(to: bounds.origin)
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
path.addLine(to: CGPoint(x: bounds.maxX - (bounds.height / 5), y: bounds.maxY))
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
path.closeSubpath()
let maskLayer = (self.layer.mask as? CAShapeLayer) ?? CAShapeLayer()
maskLayer.path = path
self.layer.mask = maskLayer
}
}

Draw rectangle with clear background on top of imageView

I am trying to draw a rectangle with a random border color with a clear background on top of an imageView. I need to loop through a set of co-ordinates and add appropriate rectangles to my image. I can't manage to get the rectangle background colour to be clear. Code below:
// the draw class
class Draw: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let h = rect.height
let w = rect.width
let color:UIColor = Palette.getRandomColour()
//var drect = CGRect(x: (w * 0.25),y: (h * 0.25),width: (w * 0.5),height: (h * 0.5))
let drect = CGRect(x: rect.minX ,y: rect.minY , width: w, height: h)
let bpath:UIBezierPath = UIBezierPath(rect: drect)
UIColor.clear.setFill()
color.set()
bpath.stroke()
print("it ran")
NSLog("drawRect has updated the view")
}
}
// and in my viewDidLoad
for bp in sbp {
if let master = Bodyparts.getID(bp.masterid) {
let width = master.x2 - master.x1
let height = master.y2 - master.y1
let k = Draw(frame: CGRect(origin: CGPoint(x: master.x1, y: master.y1), size: CGSize(width: width, height: height)))
self.imgView.addSubview(k)
}
}
You need to set the background to clear. Here is a playground that you can copy and paste:
import PlaygroundSupport
import UIKit
class Draw: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let h = rect.height
let w = rect.width
let color:UIColor = .red
let drect = CGRect(x: rect.minX ,y: rect.minY , width: w, height: h)
let bpath:UIBezierPath = UIBezierPath(rect: drect)
UIColor.clear.setFill()
color.set()
bpath.stroke()
}
}
let d = Draw(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = .green
view.addSubview(d)
PlaygroundPage.current.liveView = view
I'm not exactly sure if mean a transparent center of the rect by clear but you can use the following method.
You would need to set the style of the Paint to stroke, to draw a box instead of a filled rect. Basically you will draw a bounding box if you use the Paint.Style.Stroke option of your Paint.
// Give the canvas an image to draw on
val canvas: Canvas = bitmap?.let { Canvas(it) }!!
// Test points for rect
val left = 100f
val right = 300f
val top = 100f
val bottom = 500f
// Paint definition for bounding box
var borderPaint: Paint = Paint()
borderPaint.strokeWidth = 10f
borderPaint.color = Color.GREEN
borderPaint.style = Paint.Style.STROKE
// Draw border
canvas.drawRect(left, top, right, bottom+100, borderPaint)
// Show the bitmap on display
viewBinding.imageView.setImageBitmap(bitmap)

Problems rotating UIView with drawing inside

I want to rotate a UIView with a drawing inside (circle, rectangle or line). The problem is when I rotate the view and refresh the drawing (I need ir when I change some properties of the drawing, i.e) the drawing doesn't follow the view...
UIView without rotation:
UIView with rotation:
Here is my simple code (little extract from the original code):
Main ViewController
class TestRotation: UIViewController {
// MARK: - Properties
var drawingView:DrawingView?
// MARK: - Actions
#IBAction func actionSliderRotation(_ sender: UISlider) {
let degrees = sender.value
let radians = CGFloat(degrees * Float.pi / 180)
drawingView?.transform = CGAffineTransform(rotationAngle: radians)
drawingView?.setNeedsDisplay()
}
// MARK: - Init
override func viewDidLoad() {
super.viewDidLoad()
drawingView = DrawingView(frame:CGRect(x:50, y:50, width: 100, height:75))
self.view.addSubview(drawingView!)
drawingView?.setNeedsDisplay()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
UIView
class DrawingView: UIView {
override func draw(_ rect: CGRect) {
let start = CGPoint(x: 0, y: 0)
let end = CGPoint(x: self.frame.size.width, y: self.frame.size.height)
drawCicrle(start: start, end: end)
}
func drawCicrle(start:CGPoint, end:CGPoint) {
//Contexto
let context = UIGraphicsGetCurrentContext()
if context != nil {
//Ancho
context!.setLineWidth(2)
//Color
context!.setStrokeColor(UIColor.black.cgColor)
context!.setFillColor(UIColor.orange.cgColor)
let rect = CGRect(origin: start, size: CGSize(width: end.x-start.x, height: end.y-start.y))
let path = UIBezierPath(ovalIn: rect)
path.lineWidth = 2
path.fill()
path.stroke()
}
}
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.gray
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
It seems that the problem is temporally solved if I don't refresh the view after rotating, but if I have to refresh later for some other reason (properties changed), the problem appears again.
What can I do? Thanks in advance
You are using the frame when you should be using the bounds.
The frame is the rectangle which contains the view. It is specified in the coordinate system of the superview of the view, and the frame changes when you rotate the view (because a rotated square extends further in the X and Y directions than a non-rotated square).
The bounds is the rectangle which contains the contents of the view. It is specified in the coordinate system of the view itself, and it doesn't change as the view is rotated or moved.
If you change frame to bounds in your draw(_:) function, it will work as you expect:
override func draw(_ rect: CGRect) {
let start = CGPoint(x: 0, y: 0)
let end = CGPoint(x: self.bounds.size.width, y: self.bounds.size.height)
drawCicrle(start: start, end: end)
}
Here's a demo showing the oval being redrawn as the slider moves.
Here's the changed code:
class DrawingView: UIView {
var fillColor = UIColor.orange {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
let start = CGPoint(x: 0, y: 0)
let end = CGPoint(x: self.bounds.size.width, y: self.bounds.size.height)
drawCircle(start: start, end: end)
}
func drawCircle(start:CGPoint, end:CGPoint) {
//Contexto
let context = UIGraphicsGetCurrentContext()
if context != nil {
//Ancho
context!.setLineWidth(2)
//Color
context!.setStrokeColor(UIColor.black.cgColor)
context!.setFillColor(fillColor.cgColor)
let rect = CGRect(origin: start, size: CGSize(width: end.x-start.x, height: end.y-start.y))
let path = UIBezierPath(ovalIn: rect)
path.lineWidth = 2
path.fill()
path.stroke()
}
}
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.gray
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
#IBAction func actionSliderRotation(_ sender: UISlider) {
let degrees = sender.value
let radians = CGFloat(degrees * Float.pi / 180)
drawingView?.transform = CGAffineTransform(rotationAngle: radians)
drawingView?.fillColor = UIColor(hue: CGFloat(degrees)/360.0, saturation: 1.0, brightness: 1.0, alpha: 1.0)
}

Resources