Inside gradient color UIBezierPath - swift

I need to fill my bezier path with a gradient color. I can fill the background on the graph but not the content of the bezier. Any idea why?
//: Playground - noun: a place where people can play
import Foundation
import UIKit
import CoreGraphics
import QuartzCore
class DemoView: UIView {
override func draw(_ rect: CGRect) {
let origin = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
let radius = frame.size.width / 2
// self.createCircle(origin: origin, radius: radius)
self.addLinesInCircle(origin: origin, radius: radius)
}
func createCircle(origin: CGPoint, radius: CGFloat) {
let path = UIBezierPath()
path.addArc(withCenter: origin, radius: radius, startAngle: 0, endAngle: CGFloat(2 * Double.pi), clockwise: true)
path.close()
UIColor.clear.setFill()
path.fill()
}
func addLinesInCircle(origin: CGPoint, radius: CGFloat) {
let bezier = UIBezierPath()
let incrementAngle: CGFloat = CGFloat.pi / 24
let ratios: [CGFloat] = [3/6, 5/6, 3/6, 1/6, 5/6, 2/6, 4/6, 2/6, 4/6, 4/6, 4/6, 4/6,
3/6, 5/6, 3/6, 1/6, 5/6, 2/6, 4/6, 2/6, 4/6, 4/6, 4/6, 4/6,
3/6, 5/6, 3/6, 1/6, 5/6, 2/6, 4/6, 2/6, 4/6, 4/6, 4/6, 4/6,
3/6, 5/6, 3/6, 1/6, 5/6, 2/6, 4/6, 2/6, 4/6, 4/6, 4/6, 4/6]
for (index, ratio) in ratios.enumerated() {
let point = CGPoint(x: origin.x + cos(CGFloat(index) * incrementAngle) * radius * ratio,
y: origin.y + sin(CGFloat(index) * incrementAngle) * radius * ratio)
if index == 0 {
bezier.move(to: point)
} else {
bezier.addLine(to: point)
}
}
bezier.close()
let layer = CAGradientLayer()
layer.frame = bezier .bounds
layer.colors = [UIColor.red.cgColor,UIColor.blue.cgColor,UIColor.yellow.cgColor ,UIColor.black.cgColor]
let mask = CAShapeLayer()
mask.frame = bezier.bounds
mask.path = bezier.cgPath
mask.fillColor = UIColor.black.cgColor
layer.mask = mask
self.layer.addSublayer(layer)
self.layer.addSublayer(mask)
}
}
let demoView = DemoView(frame: CGRect(x: 0, y: 0, width: 1000, height: 1000))
Here is the result. I need the exact opposite, assign the gradient to the bezier.
(I directly used the xcode playground in order to test)

One option is to use CGGradient along with using the bezier path as a clipping region. No need for layers.
Replace everything after closing the path with:
let gradient = CGGradient(colorsSpace: nil, colors: [UIColor.red.cgColor,UIColor.blue.cgColor,UIColor.yellow.cgColor ,UIColor.black.cgColor] as CFArray, locations: nil)!
let ctx = UIGraphicsGetCurrentContext()!
ctx.saveGState()
// Clip to the path
bezier.addClip()
// Draw the gradient in the clipped region
ctx.drawLinearGradient(gradient, start: CGPoint(x: 0, y: 0), end: CGPoint(x: 0, y: frame.height), options: [])
ctx.restoreGState()

Related

MKPolylineRenderer CGContext drawPath blurry

I'm trying to draw an arrow on a MKPolyline, which is working well by overriding the draw method, however the result is a blurred line. Any clues on why this might be?
It also does not show the arrows when zoomed out (zoomScale < 0.05), and also increases the line width as you zoom out,
Code below:
class ArrowPolylineRenderer: MKPolylineRenderer {
override func applyStrokeProperties(to context: CGContext, atZoomScale zoomScale: MKZoomScale) {
super.applyStrokeProperties(to: context, atZoomScale: zoomScale)
UIGraphicsPushContext(context)
if let ctx = UIGraphicsGetCurrentContext() {
ctx.setLineWidth(self.lineWidth)
}
}
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
if self.polyline.pointCount < 2 { return }
let mapPoints = self.polyline.points()
let startPoint = mapPoints[0]
let endPoint = mapPoints[1]
let p = CGMutablePath()
if zoomScale >= 0.05 {
let midPoint = self.point(for: MKMapPoint(x: (startPoint.x + endPoint.x)/2, y: (startPoint.y + endPoint.y)/2))
let originX = endPoint.x - startPoint.x
let originY = endPoint.y - startPoint.y
let bearingR = atan2f(Float(originY), Float(originX))
let p1 = CGPoint(x: midPoint.x - 60 / (zoomScale + 0.5), y: midPoint.y + 80 / (zoomScale + 0.5))
let p2 = CGPoint(x: midPoint.x + 60 / (zoomScale + 0.5), y: midPoint.y)
let p3 = CGPoint(x: midPoint.x - 60 / (zoomScale + 0.5), y: midPoint.y - 80 / (zoomScale + 0.5))
let points = [p1, p2, p3]
let t1 = CGAffineTransform(translationX: midPoint.x, y: midPoint.y)
let t2 = t1.rotated(by: CGFloat(bearingR))
let t3 = t2.translatedBy(x: -midPoint.x, y: -midPoint.y)
p.addLines(between: points, transform: t3)
}
p.addLines(between: [self.point(for: startPoint), self.point(for: endPoint)])
p.closeSubpath()
context.setStrokeColor(UIColor.yellow.cgColor)
context.setLineWidth(5/zoomScale)
context.addPath(p)
context.strokePath()
}
}

How to round corners of this custom shape SwiftUI?

I used this tutorial to create a hexagon shape:
https://www.hackingwithswift.com/quick-start/swiftui/how-to-draw-polygons-and-stars
My goal is to try to round the corners of my hexagon shape. I know I have to use the path.addCurve somehow, but I cannot figure out where I need to do that. I am only getting weird results. Has anyone got an idea?
struct Polygon: Shape {
let corners: Int
let smoothness: CGFloat
func path(in rect: CGRect) -> Path {
guard corners >= 2 else { return Path() }
let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
var currentAngle = -CGFloat.pi / 2
let angleAdjustment = .pi * 2 / CGFloat(corners * 2)
let innerX = center.x * smoothness
let innerY = center.y * smoothness
var path = Path()
path.move(to: CGPoint(x: center.x * cos(currentAngle), y: center.y * sin(currentAngle)))
var bottomEdge: CGFloat = 0
for corner in 0 ..< corners * 2 {
let sinAngle = sin(currentAngle)
let cosAngle = cos(currentAngle)
let bottom: CGFloat
if corner.isMultiple(of: 2) {
bottom = center.y * sinAngle
path.addLine(to: CGPoint(x: center.x * cosAngle, y: bottom))
} else {
bottom = innerY * sinAngle
path.addLine(to: CGPoint(x: innerX * cosAngle, y: bottom))
}
if bottom > bottomEdge {
bottomEdge = bottom
}
currentAngle += angleAdjustment
}
let unusedSpace = (rect.height / 2 - bottomEdge) / 2
let transform = CGAffineTransform(translationX: center.x, y: center.y + unusedSpace)
return path.applying(transform)
}
}
struct Hexagon: View {
#Environment(\.colorScheme) var colorScheme
var body: some View {
Polygon(corners: 3, smoothness: 1)
.fill(.clear)
.frame(width: 76, height: 76)
}
}
Haven't found a fix but this library does what I want:
https://github.com/heestand-xyz/PolyKit

Add lines to the circle at a given angle in Core Graphics Swift

I create this circle view like this:
override func draw(_ rect: CGRect) {
super.draw(rect)
if let context = UIGraphicsGetCurrentContext()
{
let width = fmin(self.frame.size.width, self.frame.size.height)
let offset_x = abs(width - self.frame.size.width)/2
let offset_y = abs(width - self.frame.size.height)/2
let padding = CGFloat(0.5)
let radius_size = (width/2) - (padding*2)
let circle_width = radius_size/4
context.setStrokeColor(UIColor.black.cgColor)
// Draw a circle
for i in 0 ..< 4
{
let offset = CGFloat(i) * circle_width
context.strokeEllipse(in:
CGRect(
x: padding + offset + offset_x,
y: padding + offset + offset_y,
width: (radius_size - offset)*2,
height: (radius_size - offset)*2))
}
context.strokePath()
}
}
How can I create a line to the circle center, if I have an array, of angles for the most top circle? And how can I do the same, for the middle circle?
For example, I have an array, with the given angles in degrees: [87.0, 112.0, 150.0]
Here's a function drawLine that draws a line from a center point at an angle with a specific radius. To change which circle the line reaches, just change the radius:
override func draw(_ rect: CGRect) {
super.draw(rect)
if let context = UIGraphicsGetCurrentContext()
{
let width = fmin(self.frame.size.width, self.frame.size.height)
let offset_x = abs(width - self.frame.size.width)/2
let offset_y = abs(width - self.frame.size.height)/2
let padding = CGFloat(0.5)
let radius_size = (width/2) - (padding*2)
let circle_width = radius_size/4
context.setStrokeColor(UIColor.black.cgColor)
// Draw a circle
for i in 0 ..< 4
{
let offset = CGFloat(i) * circle_width
context.strokeEllipse(in:
CGRect(
x: padding + offset + offset_x,
y: padding + offset + offset_y,
width: (radius_size - offset)*2,
height: (radius_size - offset)*2))
}
let angles: [CGFloat] = [87.0, 112.0, 150]
let angles2: [CGFloat] = [210.0, 250.0, 330.0]
let center = CGPoint(x: width/2 + offset_x, y: width/2 + offset_y)
for angle in angles {
drawLine(context: context, center: center, radius: radius_size, angle: angle)
}
for angle in angles2 {
drawLine(context: context, center: center, radius: radius_size * 3 / 4, angle: angle)
}
context.strokePath()
}
}
func drawLine(context: CGContext, center: CGPoint, radius: CGFloat, angle: CGFloat) {
context.move(to: center)
context.addLine(to: CGPoint(x: center.x + radius * cos(angle * .pi / 180), y: center.y - radius * sin(angle * .pi / 180)))
}

Drawing hexagon image using Swift 4

public func roundedPolygonPath(rect: CGRect, lineWidth: CGFloat, sides: NSInteger, cornerRadius: CGFloat, rotationOffset: CGFloat = 0) -> UIBezierPath {
let path = UIBezierPath()
let theta: CGFloat = CGFloat(2.0 * M_PI) / CGFloat(sides) // How much to turn at every corner
let offset: CGFloat = cornerRadius * tan(theta / 2.0) // Offset from which to start rounding corners
let width = min(rect.size.width, rect.size.height) // Width of the square
let center = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + width / 2.0)
// Radius of the circle that encircles the polygon
// Notice that the radius is adjusted for the corners, that way the largest outer
// dimension of the resulting shape is always exactly the width - linewidth
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0
// Start drawing at a point, which by default is at the right hand edge
// but can be offset
var angle = CGFloat(rotationOffset)
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle),y: center.y + (radius - cornerRadius) * sin(angle))
path.move(to: CGPoint(x: corner.x + cornerRadius * cos(angle + theta),y: corner.y + cornerRadius * sin(angle + theta)))
for _ in 0..<sides {
angle += theta
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle),y: center.y + (radius - cornerRadius) * sin(angle))
let tip = CGPoint(x: center.x + radius * cos(angle),y: center.y + radius * sin(angle))
let start = CGPoint(x: corner.x + cornerRadius * cos(angle - theta),y: corner.y + cornerRadius * sin(angle - theta))
let end = CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta))
path.addLine(to: start)
path.addQuadCurve(to: end, controlPoint: tip)
}
path.close()
// Move the path to the correct origins
let bounds = path.bounds
let transform = CGAffineTransform(translationX: -bounds.origin.x + rect.origin.x + lineWidth / 2.0, y: -bounds.origin.y + rect.origin.y + lineWidth / 2.0)
path.apply(transform)
return path
}
public func createImage(layer: CALayer) -> UIImage {
let size = CGSizeMake(CGRect.maxX(layer.frame), CGRect.maxY(layer.frame))
UIGraphicsBeginImageContextWithOptions(size, layer.isOpaque, 0.0)
let ctx = UIGraphicsGetCurrentContext()
layer.render(in: ctx!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
let lineWidth = CGFloat(7.0)
let rect = CGRectMake(0.0, 0.0, 150.0, 150.0)
let sides = 6
var path = roundedPolygonPath(rect, lineWidth, sides, 15.0, rotationOffset: CGFloat(-M_PI / 2.0))
let borderLayer = CAShapeLayer()
borderLayer.frame = CGRectMake(0.0, 0.0, path.bounds.width + lineWidth, path.bounds.height + lineWidth)
borderLayer.path = path.CGPath
borderLayer.lineWidth = lineWidth
borderLayer.lineJoin = kCALineJoinRound
borderLayer.lineCap = kCALineCapRound
borderLayer.strokeColor = UIColor.blackColor().CGColor
borderLayer.fillColor = UIColor.whiteColor().CGColor
var image = createImage(borderLayer)}[enter image description here][1]
I cannot convert the Swift 3 with CGRect and CGSizeMake functions to latest Swift 4.
The following errors is due to the conversion of Swift 3 example. I have found,
'CGRectMake' is unavailable in Swift
Extensions may not contain stored properties
'CGSizeMake' is unavailable in Swift
*Im trying to draw the path **
Instead of CGSizeMake you now can use CGSize.init, e.g. CGSize(width: 100, height: 50).
Instead of the static function CGRect.maxX you now can use the member method of the same name: CGRect.maxX(layer.frame) becomes layer.frame.maxX.

Swift, polar coordinates

I have to draw 12 points in a circle with different radiant. From point 1, draw a line to point 2, from point 2 to 3, etc. The lines will not be a problem.
I can not find a formula to find the 12 * (x,y), but I think it's something with polar coordinates / circle?
Is anyone working with it and maybe want to share with me?
See the picture which might explain better than I can:
This is the result I got:
And This is my Playground:
//: Playground - noun: a place where people can play
import Foundation
import UIKit
class DemoView: UIView {
override func draw(_ rect: CGRect) {
let origin = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
let radius = frame.size.width / 2
self.createCircle(origin: origin, radius: radius)
self.addLinesInCircle(origin: origin, radius: radius)
}
func createCircle(origin: CGPoint, radius: CGFloat) {
let path = UIBezierPath()
path.addArc(withCenter: origin, radius: radius, startAngle: 0, endAngle: CGFloat(2 * Double.pi), clockwise: true)
path.close()
UIColor.orange.setFill()
path.fill()
}
func addLinesInCircle(origin: CGPoint, radius: CGFloat) {
let path = UIBezierPath()
let incrementAngle: CGFloat = CGFloat.pi / 6
let ratios: [CGFloat] = [3/6, 5/6, 3/6, 1/6, 5/6, 2/6, 4/6, 2/6, 4/6, 4/6, 4/6, 4/6, 3/6]
for (index, ratio) in ratios.enumerated() {
let point = CGPoint(x: origin.x + cos(CGFloat(index) * incrementAngle) * radius * ratio,
y: origin.y + sin(CGFloat(index) * incrementAngle) * radius * ratio)
if index == 0 {
path.move(to: point)
} else {
path.addLine(to: point)
}
}
path.close()
UIColor.black.set()
path.stroke()
}
}
let demoView = DemoView(frame: CGRect(x: 0, y: 0, width: 320, height: 320))