How can i add 3 background colour in single UIView?? - ios

I Need to add three colour in single background colour
Without using 3 UIView or image.

Create custom UIView and override the draw(_:) function. Then use the current CGContext and draw according to your preferred size. Example based on the alignment from the given image is shown below:
class CustomView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else {
return
}
let firstRect = CGRect(origin: CGPoint(x: rect.origin.x, y: rect.origin.y), size: CGSize(width: rect.size.width / 3, height: rect.size.height))
let middleRect = CGRect(origin: CGPoint(x: firstRect.maxX, y: rect.origin.y), size: CGSize(width: rect.size.width / 3, height: rect.size.height))
let lastRect = CGRect(origin: CGPoint(x: middleRect.maxX, y: rect.origin.y), size: CGSize(width: rect.size.width / 3, height: rect.size.height))
let arrayTuple: [(rect: CGRect, color: CGColor)] = [(firstRect, UIColor.red.cgColor), (middleRect, UIColor.green.cgColor), (lastRect, UIColor.blue.cgColor)]
for tuple in arrayTuple {
context.setFillColor(tuple.color)
context.fill(tuple.rect)
}
}
}

Use this below func
func addSublayers (_ viewCustom : UIView){
let layer1 = CAShapeLayer()
let layer2 = CAShapeLayer()
let layer3 = CAShapeLayer()
layer1.frame = CGRect(origin: viewCustom.bounds.origin,
size: CGSize(width: viewCustom.frame.size.width/3,
height: viewCustom.frame.size.height))
layer2.frame = CGRect(x: layer1.frame.size.width,
y: layer1.frame.origin.y,
width: viewCustom.frame.size.width/3,
height: viewCustom.frame.size.height)
layer3.frame = CGRect(x: layer2.frame.size.width + layer2.frame.origin.x,
y: layer2.frame.origin.y,
width: viewCustom.frame.size.width/3,
height: viewCustom.frame.size.height)
layer1.backgroundColor = UIColor.red.cgColor
layer2.backgroundColor = UIColor.green.cgColor
layer3.backgroundColor = UIColor.blue.cgColor
viewCustom.layer.addSublayer(layer1)
viewCustom.layer.addSublayer(layer2)
viewCustom.layer.addSublayer(layer3)
}
Output:

extension UIView {
func addMultipleColorsHorizontal(colors: [UIColor]) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
var colorsArray: [CGColor] = []
var locationsArray: [NSNumber] = []
for (index, color) in colors.enumerated() {
colorsArray.append(color.cgColor)
colorsArray.append(color.cgColor)
locationsArray.append(NSNumber(value: (1.0 / Double(colors.count)) * Double(index)))
locationsArray.append(NSNumber(value: (1.0 / Double(colors.count)) * Double(index + 1)))
}
gradientLayer.colors = colorsArray
gradientLayer.locations = locationsArray
gradientLayer.startPoint = CGPoint(x: 0, y: 1)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
self.backgroundColor = .clear
self.layer.addSublayer(gradientLayer)
}
}

Related

Corner radius image Swift

I'm trying to make this corner radius image...it's not exactly the same shape of the image..any easy answer instead of trying random numbers of width and height ?
thanks alot
let rectShape = CAShapeLayer()
rectShape.bounds = self.mainImg.frame
rectShape.position = self.mainImg.center
rectShape.path = UIBezierPath(roundedRect: self.mainImg.bounds, byRoundingCorners: [.bottomLeft , .bottomRight ], cornerRadii: CGSize(width: 50, height: 4)).cgPath
You can use QuadCurve to get the design you want.
Here is a Swift #IBDesignable class that lets you specify the image and the "height" of the rounding in Storyboard / Interface Builder:
#IBDesignable
class RoundedBottomImageView: UIView {
var imageView: UIImageView!
#IBInspectable var image: UIImage? {
didSet { self.imageView.image = image }
}
#IBInspectable var roundingValue: CGFloat = 0.0 {
didSet {
self.setNeedsLayout()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
doMyInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
doMyInit()
}
func doMyInit() {
imageView = UIImageView()
imageView.backgroundColor = UIColor.red
imageView.contentMode = UIViewContentMode.scaleAspectFill
addSubview(imageView)
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = self.bounds
let rect = self.bounds
let y:CGFloat = rect.size.height - roundingValue
let curveTo:CGFloat = rect.size.height + roundingValue
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: curveTo))
myBezier.addLine(to: CGPoint(x: rect.width, y: 0))
myBezier.addLine(to: CGPoint(x: 0, y: 0))
myBezier.close()
let maskForPath = CAShapeLayer()
maskForPath.path = myBezier.cgPath
layer.mask = maskForPath
}
}
Result with 300 x 200 image view, rounding set to 40:
Edit - (3.5 years later)...
To answer #MiteshDobareeya comment, we can switch the rounded edge from Bottom to Top by transforming the bezier path:
let c = CGAffineTransform(scaleX: 1, y: -1).concatenating(CGAffineTransform(translationX: 0, y: bounds.size.height))
myBezier.apply(c)
It's been quite a while since this answer was originally posted, so a few changes:
subclass UIImageView directly - no need to make it a UIView with an embedded UIImageView
add a Bool roundTop var
if set to False (the default), we round the Bottom
if set to True, we round the Top
re-order and "name" our path points for clarity
So, the basic principle:
We create a UIBezierPath and:
move to pt1
add a line to pt2
add a line to pt3
add a quad-curve to pt4 with controlPoint
close the path
use that path for a CAShapeLayer mask
the result:
If we want to round the Top, after closing the path we can apply apply a scale transform using -1 as the y value to vertically mirror it. Because that transform mirror it at "y-zero" we also apply a translate transform to move it back down into place.
That gives us:
Here's the updated class:
#IBDesignable
class RoundedTopBottomImageView: UIImageView {
#IBInspectable var roundingValue: CGFloat = 0.0 {
didSet {
self.setNeedsLayout()
}
}
#IBInspectable var roundTop: Bool = false {
didSet {
self.setNeedsLayout()
}
}
override func layoutSubviews() {
super.layoutSubviews()
let r = bounds
let myBezier = UIBezierPath()
let pt1: CGPoint = CGPoint(x: r.minX, y: r.minY)
let pt2: CGPoint = CGPoint(x: r.maxX, y: r.minY)
let pt3: CGPoint = CGPoint(x: r.maxX, y: r.maxY - roundingValue)
let pt4: CGPoint = CGPoint(x: r.minX, y: r.maxY - roundingValue)
let controlPoint: CGPoint = CGPoint(x: r.midX, y: r.maxY + roundingValue)
myBezier.move(to: pt1)
myBezier.addLine(to: pt2)
myBezier.addLine(to: pt3)
myBezier.addQuadCurve(to: pt4, controlPoint: controlPoint)
myBezier.close()
if roundTop {
// if we want to round the Top instead of the bottom,
// flip the path vertically
let c = CGAffineTransform(scaleX: 1, y: -1) //.concatenating(CGAffineTransform(translationX: 0, y: bounds.size.height))
myBezier.apply(c)
}
let maskForPath = CAShapeLayer()
maskForPath.path = myBezier.cgPath
layer.mask = maskForPath
}
}
You can try with UIView extension. as
extension UIView {
func setBottomCurve(){
let offset = CGFloat(self.frame.size.height + self.frame.size.height/1.8)
let bounds = self.bounds
let rectBounds = CGRect(x: bounds.origin.x,
y: bounds.origin.y ,
width: bounds.size.width,
height: bounds.size.height / 2)
let rectPath = UIBezierPath(rect: rectBounds)
let ovalBounds = CGRect(x: bounds.origin.x - offset / 2,
y: bounds.origin.y ,
width: bounds.size.width + offset,
height: bounds.size.height)
let ovalPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
self.layer.mask = maskLayer
}
}
& use it in viewWillAppear like methods where you can get actual frame of UIImageView.
Usage:
override func viewWillAppear(_ animated: Bool) {
//use it in viewWillAppear like methods where you can get actual frame of UIImageView
myImageView.setBottomCurve()
}

Adding a mask to CAGradientLayer makes UIBezierPath disappear

I want to add an inner border to a view with a gradient. The following code works and gives me this result
import UIKit
class InnerGradientBorderView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
addGradientInnerBorder(width: 8, color: FlatWhite())
}
func addGradientInnerBorder(width: CGFloat, color: UIColor) {
// Setup
let topLeftO = CGPoint(x: 0, y: 0)
let topLeftI = CGPoint(x: width, y: width)
let topRightO = CGPoint(x: frame.width, y: 0)
let topRightI = CGPoint(x: frame.width - width, y: width)
let bottomLeftO = CGPoint(x: 0, y: frame.height)
let bottomLeftI = CGPoint(x: width, y: frame.height - width)
let bottomRightO = CGPoint(x: frame.width, y: frame.height)
let bottomRightI = CGPoint(x: frame.width - width, y: frame.height - width)
// Top
let topPoints = [topLeftO, topLeftI, topRightI, topRightO, topLeftO]
let topGradientPoints = [CGPoint(x: 0, y: 0), CGPoint(x: 0, y: 1)]
addGradientToBeizerPath(path: addClosedPathForPoints(points: topPoints), color: color, gradientPoints: topGradientPoints)
// Left
let leftPoints = [topLeftO, topLeftI, bottomLeftI, bottomLeftO, topLeftO]
let leftGradientPoints = [CGPoint(x: 0, y: 0), CGPoint(x: 1, y: 0)]
addGradientToBeizerPath(path: addClosedPathForPoints(points: leftPoints), color: color, gradientPoints: leftGradientPoints)
// Right
let rightPoints = [topRightO, topRightI, bottomRightI, bottomRightO, topRightO]
let rightGradientPoints = [CGPoint(x: 1, y: 0), CGPoint(x: 0, y: 0)]
addGradientToBeizerPath(path: addClosedPathForPoints(points: rightPoints), color: color, gradientPoints: rightGradientPoints)
// Bottom
let bottomPoints = [bottomLeftO, bottomLeftI, bottomRightI, bottomRightO, bottomLeftO]
let bottomGradientPoints = [CGPoint(x: 0, y: 1), CGPoint(x: 0, y: 0)]
addGradientToBeizerPath(path: addClosedPathForPoints(points: bottomPoints), color: color, gradientPoints: bottomGradientPoints)
}
func addClosedPathForPoints(points: [CGPoint]) -> UIBezierPath? {
guard points.count == 5 else { return nil }
let path = UIBezierPath()
path.move(to: points[0])
path.addLine(to: points[1])
path.addLine(to: points[2])
path.addLine(to: points[3])
path.addLine(to: points[4])
path.close()
return path
}
func addGradientToBeizerPath(path: UIBezierPath?, color: UIColor, gradientPoints: [CGPoint]) {
guard let path = path, gradientPoints.count == 2 else { return }
let gradient = CAGradientLayer()
gradient.frame = path.bounds
gradient.colors = [color.cgColor, UIColor.clear.cgColor]
gradient.startPoint = gradientPoints[0]
gradient.endPoint = gradientPoints[1]
// let shapeMask = CAShapeLayer()
// shapeMask.path = path.cgPath
// gradient.mask = shapeMask
self.layer.insertSublayer(gradient, at: 0)
}
}
You will notice that the edges do not look that great.To fix that, I am giving the edges an angle. When I apply a mask to this gradient with this angle, the right and bottom paths disappear like this:
All I am doing here is using some closed bezierPaths and applying a gradient to them. If the gradient has a mask (the commented code is uncommented), two of the paths disappear. I have a feeling that I am not understanding something so hopefully someone here can tell me how to use CAShapeLayer properly.
This comment to CALayer mask property
explains it perfectly:
The mask layer lives in the masked layer's coordinate system just as if it were a sublayer.
In your case, the origin of the right and bottom gradient layer is not
at (0, 0) of the enclosing view, but at (frame.width - width, 0)
and (frame.height - width, 0) respectively.
On the other hand, the coordinates of the points in
oshapeMask.path are relative to (0, 0) of the enclosing view.
A possible simple fix is to transform the coordinate system of
the shape layer so that it uses the same coordinates as the points
of the given path:
let gradient = CAGradientLayer()
gradient.frame = path.bounds
gradient.bounds = path.bounds // <<--- ADDED HERE!
gradient.colors = [color.cgColor, UIColor.clear.cgColor]
gradient.startPoint = gradientPoints[0]
gradient.endPoint = gradientPoints[1]
let shapeMask = CAShapeLayer()
shapeMask.path = path.cgPath
gradient.mask = shapeMask
self.layer.addSublayer(gradient)

Create CAGradient Layer with transparent hole in it?

Currently I do it like this:
final class BorderedButton: BottomNavigationButton {
private let gradient = CAGradientLayer()
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let radius = bounds.size.height / 2
let outside = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height), cornerRadius: radius)
let inside = UIBezierPath(roundedRect: CGRect(x: 3.0, y: 3.0, width: bounds.width - 6, height: bounds.height - 6), cornerRadius: radius - 3)
outside.append(inside)
outside.usesEvenOddFillRule = true
shapeLayer.path = outside.cgPath
}
init(color: UIColor?) {
super.init(frame: .zero)
let isGradient = color == nil
shapeLayer.fillRule = kCAFillRuleEvenOdd
shapeLayer.fillColor = UIColor.red.cgColor
layer.insertSublayer(shapeLayer, at: 0)
//gradient part
gradient.colors = isGradient ? [Constants.gradientStart, Constants.gradientEnd] : [color!.cgColor, color!.cgColor]
gradient.startPoint = CGPoint(x: 0.2, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 1)
}
}
How can I apply that gradient to my code?
Don't add the CAShapeLayer to the view's layer, rather set it as the mask of the CAGradientLayer. Also don't forget to set the bounds of the gradient layer.
I had to make some modifications to get it to run in a playground, but this works for me:
let gradientStart = UIColor.orange
let gradientEnd = UIColor.blue
final class BorderedButton: UIButton {
private let gradient = CAGradientLayer()
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let radius = bounds.size.height / 2
let outside = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height), cornerRadius: radius)
let inside = UIBezierPath(roundedRect: CGRect(x: 3.0, y: 3.0, width: bounds.width - 6, height: bounds.height - 6), cornerRadius: radius - 3)
outside.append(inside)
outside.usesEvenOddFillRule = true
shapeLayer.path = outside.cgPath
gradient.frame = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height)
}
init(color: UIColor?, frame: CGRect = .zero) {
super.init(frame: frame)
let isGradient = color == nil
shapeLayer.fillRule = kCAFillRuleEvenOdd
//gradient part
gradient.colors = isGradient ? [gradientStart.cgColor, gradientEnd.cgColor] : [color!.cgColor, color!.cgColor]
gradient.startPoint = CGPoint(x: 0.2, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 1)
gradient.mask = shapeLayer
layer.addSublayer(gradient)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Fill Path on Intersecting UIBezierPath

Any idea on how to fill all the paths in here. What is currently happening is that I draw a rectangle path on my view then add small circles in between but it seems that if the circle and rectangle intersects, the white fill color is showing. What I would want is show still the gradient layer. Any help? My current code is below.
func addGradientLayer() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
let startColor = UIColor.create(withHexOrName: OurPayStatesViewUX.GradientColorStart)
let endColor = UIColor.create(withHexOrName: OurPayStatesViewUX.GradientColorEnd)
gradientLayer.colors = [startColor.CGColor, endColor.CGColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
layer.insertSublayer(gradientLayer, atIndex: 0)
}
func createOverlay(view: UIView, circleLocations: [CGPoint]) {
maskLayer?.removeFromSuperlayer()
let radius: CGFloat = view.frame.height/2
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: view.bounds.size.width, height: view.bounds.size.height), cornerRadius: 0)
for i in circleLocations {
// Create a circle path in each of the state views
let circlePath = UIBezierPath(roundedRect: CGRect(x: i.x, y: i.y, width: 2 * radius, height: 2 * radius), cornerRadius: radius)
path.appendPath(circlePath)
}
let rect = createRectangle(startPointX: 0, endPointX: view.bounds.size.width)
path.appendPath(rect)
path.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = path.CGPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = backgroundColor?.CGColor ?? UIColor.whiteColor().CGColor
fillLayer.opacity = 1
maskLayer = fillLayer
layer.addSublayer(fillLayer)
}
func createRectangle(startPointX startPointX: CGFloat, endPointX: CGFloat) -> UIBezierPath {
let rectHeight: CGFloat = 6
let path = UIBezierPath(rect: CGRect(x: startPointX, y: frame.height/2 - rectHeight/2, width: endPointX - startPointX, height: rectHeight))
return path
}

Adding custom overlay in map view

i'm new to iOS and my goal is to add custom overlay in map view using Swift 3 and MapKit. I've followed this Add inverted circle overlay to map view.
Here is the code:
import UIKit
import MapKit
class MyMapOverlayRenderer: MKOverlayRenderer {
let diameter: Double
let fillColor: UIColor
init(overlay: MKOverlay, diameter: Double, fillColor: UIColor) {
self.diameter = diameter
self.fillColor = fillColor
super.init(overlay: overlay)
}
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
let path = UIBezierPath(rect: CGRect(x: mapRect.origin.x, y: mapRect.origin.y, width: mapRect.size.width, height: mapRect.size.height))
path.usesEvenOddFillRule = true
let radiusInMapPoints = diameter * MKMapPointsPerMeterAtLatitude(self.overlay.coordinate.latitude)
let radiusSquared = MKMapSize(width: radiusInMapPoints, height: radiusInMapPoints)
let regionOrigin = MKMapPointForCoordinate(self.overlay.coordinate)
var regionRect = MKMapRect(origin: regionOrigin, size: radiusSquared)
regionRect = MKMapRectOffset(regionRect, -radiusInMapPoints / 2, -radiusInMapPoints / 2)
regionRect = MKMapRectIntersection(regionRect, MKMapRectWorld)
let cornerRadius = CGFloat(regionRect.size.width / Double(2))
let excludePath = UIBezierPath(roundedRect: CGRect(x: regionRect.origin.x, y: regionRect.origin.y, width: regionRect.size.width, height: regionRect.size.height), cornerRadius: cornerRadius)
path.append(excludePath)
context.setFillColor(fillColor.cgColor)
context.addPath(path.cgPath)
context.fillPath()
}
}
Eventually the overlay is shown without exclude path (a circle), any suggestions?
Solved it, just added reversing():
path.append(excludePath.reversing())
Full function code:
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
let path = UIBezierPath(rect: CGRect(x: mapRect.origin.x, y: mapRect.origin.y, width: mapRect.size.width, height: mapRect.size.height))
path.usesEvenOddFillRule = true
let radiusInMapPoints = diameter * MKMapPointsPerMeterAtLatitude(MKMapPointsPerMeterAtLatitude(overlay.coordinate.latitude))
let radiusSquared = MKMapSize(width: radiusInMapPoints, height: radiusInMapPoints)
let regionOrigin = MKMapPointForCoordinate(overlay.coordinate)
var regionRect = MKMapRect(origin: regionOrigin, size: radiusSquared)
regionRect = MKMapRectOffset(regionRect, -radiusInMapPoints / 2, -radiusInMapPoints / 2)
regionRect = MKMapRectIntersection(regionRect, MKMapRectWorld)
let midX = ( regionOrigin.x + regionRect.origin.x) / 2
let midY = ( regionOrigin.y + regionRect.origin.y) / 2
let cornerRadius = CGFloat(regionRect.size.width / Double(2))
let excludePath = UIBezierPath(roundedRect: CGRect(x: midX, y: midY, width: regionRect.size.width / 2, height: regionRect.size.height / 2), cornerRadius: cornerRadius)
path.append(excludePath.reversing())
context.setFillColor(fillColor.cgColor)
context.addPath(path.cgPath)
context.fillPath()
}

Resources