iOS: Chat bubble change position of tail - swift

I'm trying to achive this layout
[LAYOUT TO ACHIEVE]
and right now I have gradient part done, blur part done, and also bezier path mostly done. My question is that I can't replace TAIL from bottom right part to top left part (to be similar like in the picture).
[LAYOUT ACHIEVED]
My Code I wrote for acheiving:
public final class BubbleView: UIView {
public var configuration: Configuration = Configuration.defaultConfiguration {
didSet {
setNeedsLayout()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
super.backgroundColor = .clear
}
public override func draw(_ rect: CGRect) {
let hasBlurSubview = subviews.compactMap({$0}).contains(where: {$0 is UIVisualEffectView })
guard !hasBlurSubview else {
return
}
let lineWidth = configuration.lineWidth
let bezierPath = UIBezierPath()
bezierPath.lineWidth = lineWidth
let bottom = rect.height - lineWidth
let right = rect.width - lineWidth
let top = 0.0
let left = 0.0
bezierPath.move(to: CGPoint(x: right - 22, y: bottom))
bezierPath.addLine(to: CGPoint(x: 17 + lineWidth, y: bottom))
bezierPath.addCurve(to: CGPoint(x: left, y: bottom - 18), controlPoint1: CGPoint(x: 7.61 + lineWidth, y: bottom), controlPoint2: CGPoint(x: left, y: bottom - 7.61))
bezierPath.addLine(to: CGPoint(x: left, y: 17 + lineWidth))
bezierPath.addCurve(to: CGPoint(x: 17 + borderWidth, y: top), controlPoint1: CGPoint(x: left, y: 7.61 + lineWidth), controlPoint2: CGPoint(x: 7.61 + lineWidth, y: top))
bezierPath.addLine(to: CGPoint(x: right - 21, y: top))
bezierPath.addCurve(to: CGPoint(x: right - 4, y: 17 + lineWidth), controlPoint1: CGPoint(x: right - 11.61, y: top), controlPoint2: CGPoint(x: right - 4, y: 7.61 + lineWidth))
bezierPath.addLine(to: CGPoint(x: right - 4, y: bottom - 11))
bezierPath.addCurve(to: CGPoint(x: right, y: bottom), controlPoint1: CGPoint(x: right - 4, y: bottom - 1), controlPoint2: CGPoint(x: right, y: bottom))
bezierPath.addLine(to: CGPoint(x: right + 0.05, y: bottom - 0.01))
bezierPath.addCurve(to: CGPoint(x: right - 11.04, y: bottom - 4.04), controlPoint1: CGPoint(x: right - 4.07, y: bottom + 0.43), controlPoint2: CGPoint(x: right - 8.16, y: bottom - 1.06))
bezierPath.addCurve(to: CGPoint(x: right - 22, y: bottom), controlPoint1: CGPoint(x: right - 16, y: bottom), controlPoint2: CGPoint(x: right - 19, y: bottom))
bezierPath.close()
let blurEffect = UIVisualEffectView(effect: UIBlurEffect(style: .light))
blurEffect.alpha = configuration.blurAlpha
blurEffect.frame = bezierPath.bounds
let mask = CAShapeLayer()
mask.path = bezierPath.cgPath
blurEffect.layer.mask = mask
insertSubview(blurEffect, at: 0)
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPoint.zero, size: size)
gradient.colors = configuration.borderGradientColors.map({$0.cgColor})
gradient.startPoint = configuration.borderGradientStartPoint
gradient.endPoint = configuration.borderGradientEndPoint
let shape = CAShapeLayer()
shape.lineWidth = lineWidth
shape.path = bezierPath.cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
blurEffect.layer.addSublayer(gradient)
}
}
public class Configuration {
static var defaultConfiguration: Configuration {
return .init(
lineWidth: 2.0,
blurAlpha: 0.95,
borderGradientColors: [Assets.Colors.white05.color, Assets.Colors.white01.color],
borderGradientStartPoint: CGPoint(x: 0.0, y: 0.5),
borderGradientEndPoint: CGPoint(x: 1.0, y: 0.5)
)
}
var lineWidth: CGFloat
var blurAlpha: CGFloat
var borderGradientColors: [UIColor]
var borderGradientStartPoint: CGPoint
var borderGradientEndPoint: CGPoint
init(
lineWidth: CGFloat,
blurAlpha: CGFloat,
borderGradientColors: [UIColor],
borderGradientStartPoint: CGPoint,
borderGradientEndPoint: CGPoint
) {
self.lineWidth = lineWidth
self.blurAlpha = blurAlpha
self.borderGradientColors = borderGradientColors
self.borderGradientStartPoint = borderGradientStartPoint
self.borderGradientEndPoint = borderGradientEndPoint
}
}

Related

How to blur the border of the mask for an NSImage?

I am trying to blur the border of the mask for an NSImage in swift and I can't figure out, how to do this. The mask is supposed to be created from a CGMutablePath, so that it can be changed programmatically. This is what it looks like at the moment:
This is what it is supposed to look like:
The code for creating the mask with sharp edges is the following:
class MyView: NSView {
override func draw(_ dirtyRect: NSRect) {
let bottomLayer = CALayer()
bottomLayer.bounds = self.bounds
bottomLayer.position = CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2)
bottomLayer.contents = NSImage(named: "bottom.jpg")
bottomLayer.contentsGravity = .resize
self.layer?.addSublayer(bottomLayer)
let path = CGMutablePath()
path.move(to: CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2))
path.addCurve(to: CGPoint(x: 0.5*self.bounds.size.width, y: self.bounds.size.height), control1: CGPoint(x: 0.4*self.bounds.size.width, y: 0), control2: CGPoint(x: 0, y: self.bounds.size.height))
path.addCurve(to: CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2), control1: CGPoint(x: self.bounds.size.width, y: self.bounds.size.height), control2: CGPoint(x: 0.8*self.bounds.size.width, y: 0))
path.closeSubpath()
let maskLayer = CAShapeLayer()
maskLayer.path = path
maskLayer.fillRule = .evenOdd
maskLayer.borderWidth = 20
let topLayer = CALayer()
topLayer.bounds = self.bounds
topLayer.position = CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2)
topLayer.contents = NSImage(named: "top.jpg")
topLayer.contentsGravity = .resize
topLayer.masksToBounds = true
topLayer.mask = maskLayer
self.layer?.addSublayer(topLayer)
}
}
I hope there is a nice solution to this :)
Frederik
Try adding CAGradientLayer instead of CAShapeLayer:
Sample Source:
class MyView: NSView {
override func draw(_ dirtyRect: NSRect) {
let bottomLayer = CALayer()
bottomLayer.bounds = self.bounds
bottomLayer.position = CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2)
bottomLayer.contents = NSImage(named: "bottom")
bottomLayer.contentsGravity = .resize
self.layer?.addSublayer(bottomLayer)
let path = CGMutablePath()
path.move(to: CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2))
path.addCurve(to: CGPoint(x: 0.5*self.bounds.size.width, y: self.bounds.size.height), control1: CGPoint(x: 0.4*self.bounds.size.width, y: 0), control2: CGPoint(x: 0, y: self.bounds.size.height))
path.addCurve(to: CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2), control1: CGPoint(x: self.bounds.size.width, y: self.bounds.size.height), control2: CGPoint(x: 0.8*self.bounds.size.width, y: 0))
path.closeSubpath()
let maskLayer = CAGradientLayer()
maskLayer.shadowRadius = 4
maskLayer.shadowPath = path
maskLayer.shadowOpacity = 1
maskLayer.shadowOffset = CGSize.zero
maskLayer.shadowColor = NSColor.green.cgColor
let topLayer = CALayer()
topLayer.bounds = self.bounds
topLayer.position = CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2)
topLayer.contents = NSImage(named: "top")
topLayer.contentsGravity = .resize
topLayer.masksToBounds = true
topLayer.mask = maskLayer
self.layer?.addSublayer(topLayer)
}
}
Output

How to curve edges of UIImageview in swift ios?

I used
let view = UIImageView(CGRect(x: 0, y: 10, width: 100, height: 100))
view.cornerRadius = 10
self.mainView.addSubview(view)
This rounds the corner I want round the edges
The standard technique is to
Define a UIBezierPath for the shape.
Make a CAShapeLayer using that path.
Define the image view’s layer’s mask to use that shape layer.
E.g.
#IBDesignable
class RoundedCornerView: UIImageView {
#IBInspectable
var cornerRadius: CGFloat = 17 { didSet { setNeedsLayout() } }
#IBInspectable
var lineWidth: CGFloat = 75 { didSet { setNeedsLayout() } }
override func layoutSubviews() {
super.layoutSubviews()
let rect = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)
let path = UIBezierPath()
var point = CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius)
path.move(to: point)
var controlPoint = CGPoint(x: point.x + cornerRadius, y: rect.minY)
point = CGPoint(x: rect.midX, y: rect.minY)
path.addQuadCurve(to: point, controlPoint: controlPoint)
point = CGPoint(x: rect.maxX - cornerRadius, y: rect.minY + cornerRadius)
controlPoint = CGPoint(x: point.x - cornerRadius, y: rect.minY)
path.addQuadCurve(to: point, controlPoint: controlPoint)
controlPoint = CGPoint(x: rect.maxX, y: point.y + cornerRadius)
point = CGPoint(x: rect.maxX, y: rect.midY)
path.addQuadCurve(to: point, controlPoint: controlPoint)
point = CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius)
controlPoint = CGPoint(x: rect.maxX, y: point.y - cornerRadius)
path.addQuadCurve(to: point, controlPoint: controlPoint)
controlPoint = CGPoint(x: point.x - cornerRadius, y: rect.maxY)
point = CGPoint(x: rect.midX, y: rect.maxY)
path.addQuadCurve(to: point, controlPoint: controlPoint)
point = CGPoint(x: rect.minX + cornerRadius, y: rect.maxY - cornerRadius)
controlPoint = CGPoint(x: point.x + cornerRadius, y: rect.maxY)
path.addQuadCurve(to: point, controlPoint: controlPoint)
controlPoint = CGPoint(x: rect.minX, y: point.y - cornerRadius)
point = CGPoint(x: rect.minX, y: rect.midY)
path.addQuadCurve(to: point, controlPoint: controlPoint)
point = CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius)
controlPoint = CGPoint(x: rect.minX, y: point.y + cornerRadius)
path.addQuadCurve(to: point, controlPoint: controlPoint)
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = lineWidth
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.white.cgColor
layer.mask = shapeLayer
clipsToBounds = true
}
}
Yields:
You’ll have to play around with values for cornerRadius and lineWidth for your image view’s dimensions, but hopefully this illustrates the idea. The default values I used in the code above was for a 300×300 image view.
Note, I implemented the path in layoutSubviews so that it would respond to resizing of your image view. I also make it #IBDesignable so that you could see it rendered in IB if you happened to be using storyboards.
Try to wrap image inside UIView
let image = UIImageView(CGRect(x:0,y:0,width:100,height:100))
let view = UIView(CGRect(x:0,y:10,width:100,height:100))
view.cornerRadius = 10
view.addSubiew(image)
self.mainView.addSubview(view)
Swift 4
let image = UIImageView(frame: CGRect(x:0,y:0,width:100,height:100))
let view = UIView(frame: CGRect(x:0,y:10,width:100,height:100))
view.layer.cornerRadius = 30
view.addSubview(image)
self.mainView.addSubview(view)

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

Using Drawn Shapes and Changing Their Features

After spending a whole day trying to work out how to do it I have finally worked out a way of having non-rectangular buttons.
All three buttons at the bottom are on one button. The hitTest then works out which UIBezierPath the click was within then calls a function.
import UIKit
class ViewController: UIViewController {
let path = UIBezierPath()
let path2 = UIBezierPath()
let path3 = UIBezierPath()
let path4 = UIBezierPath()
#IBAction func clicked(_ sender: Any) {
print("Clicked")
}
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
drawShape()
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.checkAction(sender:)))
self.button.addGestureRecognizer(gesture)
}
func drawShape(){
let buttonWidth = button.frame.width
let buttonHeight = buttonWidth * 1.23
path.move(to: CGPoint(x: 0, y: buttonHeight*0.85))
path.addLine(to: CGPoint(x: 0, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path.close()
path2.move(to: CGPoint(x: 0, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: 0, y: buttonHeight*0.85))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path2.close()
path3.move(to: CGPoint(x: 0, y: buttonHeight*0.50))
path3.addLine(to: CGPoint(x: 0, y: buttonHeight*0.68))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path3.close()
path4.move(to: CGPoint(x: 0, y: buttonHeight*0.45))
path4.addLine(to: CGPoint(x: 0, y: buttonHeight*0.50))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.01))
path4.close()
}
func checkAction(sender : UITapGestureRecognizer) {
let location = sender.location(in: button)
print(location)
hitTest(tapLocation: location)
}
public func hitTest(tapLocation:CGPoint){
if path.contains(tapLocation){
print("Button3")
}
if path2.contains(tapLocation){
print("Button2")
}
if path3.contains(tapLocation){
print("Button1")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Then in the file linked to the button:
import UIKit
#IBDesignable
class PushButtonView: UIButton {
override func draw(_ rect: CGRect) {
let buttonWidth = self.frame.width
let buttonHeight = buttonWidth * 1.23
let color1 = hexStringToUIColor(hex: "#e0dfd5")
let color2 = hexStringToUIColor(hex: "#ef6461")
let color3 = hexStringToUIColor(hex: "#e4b363")
let color4 = hexStringToUIColor(hex: "#313638")
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: buttonHeight*0.85))
path.addLine(to: CGPoint(x: 0, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path.close()
color4.setFill()
path.fill()
let path2 = UIBezierPath()
path2.move(to: CGPoint(x: 0, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: 0, y: buttonHeight*0.85))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path2.close()
color3.setFill()
path2.fill()
let path3 = UIBezierPath()
path3.move(to: CGPoint(x: 0, y: buttonHeight*0.50))
path3.addLine(to: CGPoint(x: 0, y: buttonHeight*0.68))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path3.close()
color2.setFill()
path3.fill()
let path4 = UIBezierPath()
path4.move(to: CGPoint(x: 0, y: buttonHeight*0.45))
path4.addLine(to: CGPoint(x: 0, y: buttonHeight*0.50))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.01))
path4.close()
color1.setFill()
path4.fill()
}
func hexStringToUIColor (hex:String) -> UIColor {
var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if (cString.hasPrefix("#")) {
cString.remove(at: cString.startIndex)
}
if ((cString.characters.count) != 6) {
return UIColor.gray
}
var rgbValue:UInt32 = 0
Scanner(string: cString).scanHexInt32(&rgbValue)
return UIColor(
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: CGFloat(1.0)
)
}
}
Everything works! It may not be the best way of doing it but after 5 hours of trying different things this is whats worked best. However I am now stuck with the problem of having no idea how to manipulate those shapes I have drawn in the external file. I am sure that this has been asked and answered before but I literally have no idea was to search for.
For example if it was javascript I would assume a 'path' drawn within the object 'button' would be accessed by self.button.path (or something similar), however, my understanding is Swift3 doesn't work like that. Am I correct? So if I wanted to change the backgroundColor of the NOTES button/shape with an animate how would I get this effect:
UIView.animate(withDuration: 1, animations: {
self.button.path.color = UIColor.red
}, completion: { finished in
if(finished){
//callFunction()
}
})
Is this possible, or once a path is drawn is it not a changeable object and has to be deleted and redrawn?

UIBezierPath Rounded corners

I am trying to make this image appear with some rounded corners, dictated in code below
My code for this is as follows. I want path1,path[4], path[5] rounded. I am open to a different approach. Please dont recommend UIBezierPath(roundedRect:..) as I dont need corenrs 0,2 rounded. This is tricky!
let width = self.view.frame.size.width
let trianglePath = UIBezierPath()
var pts = [CGPoint(x: 2 * width / 16, y: width / 16),
CGPoint(x: width / 32, y: width / 16 + width / 16),
CGPoint(x: 2 * width / 16 , y: width / 16 + width / 8),
CGPoint(x: width / 5 + 2 * width / 16, y: width / 8 + width / 16),
CGPoint(x: width / 5 + 2 * width / 16, y: width / 16 ),
CGPoint(x: 2 * width / 16, y: width / 16 )
]
// this path replaces this, because i cannot easily add rectangle
//var path = UIBezierPath(roundedRect: rect, cornerRadius: width / 37.5).cgPath
trianglePath.move(to: pts[0])
trianglePath.addLine(to: pts[1]) // needs to be rounded
trianglePath.addLine(to: pts[2])
trianglePath.addLine(to: pts[3])
trianglePath.addLine(to: pts[4]) // needs to be rounded
trianglePath.addLine(to: pts[5]) // needs to be rounded
trianglePath.close()
let layer = CAShapeLayer()
layer.path = trianglePath.cgPath
layer.fillColor = UIColor.blue.cgColor
layer.lineCap = kCALineCapRound
layer.lineJoin = kCALineJoinRound
layer.zPosition = 4
layer.isHidden = false
view.layer.addSublayer(layer)
You can create a clipping path as explained here: https://www.raywenderlich.com/162313/core-graphics-tutorial-part-2-gradients-contexts
Specifically this bit:
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: .allCorners,
cornerRadii: CGSize(width: 8.0, height: 8.0))
path.addClip()
If you want to make rounded shapes you must use
trianglePath.AddArcToPoint(...);
See documentation here: https://developer.apple.com/library/content/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/BezierPaths/BezierPaths.html#//apple_ref/doc/uid/TP40010156-CH11-SW5
To be extra helpful, here is an entire subclass which you can use to learn how to draw custom views. Hope this helps.
import Foundation
import UIKit
final class BorderedView:UIView
{
var drawTopBorder = false
var drawBottomBorder = false
var drawLeftBorder = false
var drawRightBorder = false
var topBorderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.4)
var leftBorderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.4)
var rightBorderColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
var bottomBorderColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
var upperLeftCornerRadius:CGFloat = 0
var upperRightCornerRadius:CGFloat = 0
var lowerLeftCornerRadius:CGFloat = 0
var lowerRightCornerRadius:CGFloat = 0
var subview:UIView?
fileprivate var visibleView:UIView!
fileprivate var _backgroundColor:UIColor!
override init(frame: CGRect)
{
super.init(frame: frame)
super.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
super.backgroundColor = UIColor.clear
}
func setVisibleBackgroundColor(_ color:UIColor)
{
_backgroundColor = color
}
func setBackgroundGradient(_ gradient:CAGradientLayer)
{
gradient.frame = visibleView.bounds
gradient.masksToBounds = true
visibleView.layer.insertSublayer(gradient, at: 0)
}
func drawView()
{
visibleView = UIView(frame: self.frame)
visibleView.backgroundColor = _backgroundColor
visibleView.clipsToBounds = true
if let v = subview
{
visibleView.addSubview(v)
}
}
override func draw(_ rect: CGRect)
{
// Drawing code
let width = rect.size.width - 0.5;
let height = rect.size.height - 0.5;
guard let context = UIGraphicsGetCurrentContext() else { return }
let w = self.frame.size.width;
let h = self.frame.size.height;
// Create clipping area as path.
let path = CGMutablePath();
path.move(to: CGPoint(x: 0, y: upperLeftCornerRadius))
path.addArc(tangent1End: CGPoint(x: 0, y: 0), tangent2End: CGPoint(x: upperLeftCornerRadius, y: 0), radius: upperLeftCornerRadius)
path.addLine(to: CGPoint(x: w - upperRightCornerRadius, y: 0))
path.addArc(tangent1End: CGPoint(x: w, y: 0), tangent2End: CGPoint(x: w, y: upperRightCornerRadius), radius: upperRightCornerRadius)
path.addLine(to: CGPoint(x: w, y: h - lowerRightCornerRadius))
path.addArc(tangent1End: CGPoint(x: w, y: h), tangent2End: CGPoint(x: w - lowerRightCornerRadius, y: h), radius: lowerRightCornerRadius)
path.addLine(to: CGPoint(x: lowerLeftCornerRadius, y: h))
path.addArc(tangent1End: CGPoint(x: 0, y: h), tangent2End: CGPoint(x: 0, y: h - lowerLeftCornerRadius), radius: lowerLeftCornerRadius)
path.closeSubpath();
// Add clipping area to path
context.addPath(path);
// Clip to the path and draw the image
context.clip ();
self.drawView()
visibleView.layer.render(in: context)
// ********** Your drawing code here **********
context.setLineWidth(0.5);
var stroke = false
if (drawTopBorder)
{
context.setStrokeColor(topBorderColor.cgColor);
context.move(to: CGPoint(x: 0, y: 0.5)); //start at this point
context.addLine(to: CGPoint(x: width, y: 0.5)); //draw to this point
stroke = true
}
if (drawLeftBorder)
{
context.setStrokeColor(leftBorderColor.cgColor);
context.move(to: CGPoint(x: 0.5, y: 0.5)); //start at this point
context.addLine(to: CGPoint(x: 0.5, y: height)); //draw to this point
stroke = true
}
if (drawBottomBorder)
{
context.setStrokeColor(bottomBorderColor.cgColor);
context.move(to: CGPoint(x: 0.5, y: height)); //start at this point
context.addLine(to: CGPoint(x: width, y: height)); //draw to this point
stroke = true
}
if (drawRightBorder)
{
context.setStrokeColor(rightBorderColor.cgColor);
context.move(to: CGPoint(x: width, y: 0.5)); //start at this point
context.addLine(to: CGPoint(x: width, y: height)); //draw to this point
stroke = true
}
// and now draw the Path!
if stroke
{
context.strokePath();
}
}
}