Color filling in Swift - ios

Is there a general method to fill an area in Swift.
I have various shape in my UIView, lines, ellipses, curves … etc.
Those shapes delimit a certain number of areas.
I want to fill one area in a color (uiColorOne) and another area in another color (uiColorTwo).
Is that possible? If YES, how can I do that?

import UIKit
override func drawRect(rect: CGRect)
{
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 200, y: 200))
path.addLineToPoint(CGPoint(x: 200, y: 300))
path.addLineToPoint(CGPoint(x: 300, y: 200))
path.closePath()
UIColor.redColor().set()
path.lineWidth = 1
path.fill()
}
You will need to override drawRect in order to start making some geometric shaped items in your code (ONLY OVERRIDE AND NEVER CALL IT). The code snippet creates an element based on points (careful not pixels) and fills it with the redColor.You will have to use UIBezierPath most of the times you are playing with elements and so I suggest you have a really good look at it from apple documentation here, since it has some awesome methods that will help you out with your every day struggle : UIBezierPath

Related

Can't get Swift UIBezierPath plot to remove and then update

In my app, viewdidLoad() uses UIBezierPath to display green UIBezierPath plotted line.
But later in the program, an event callback uses same UIBezierPath to removeAllPoints and display red line next to green one.
But, all it does is turn the green line to red, at same position.
Ultimately, I want to use UIBezierPath to each second show an ever-changing, multi-line plot --
that is: remove previous multi-line plot and show updated multi-line plot, every second.
CODE THAT SHOWS GREEN LINE...................
var sensor_plot_UIBezierPath = UIBezierPath()
var sensor_plot_CAShapeLayer = CAShapeLayer()
override func viewDidLoad()
{
sensor_plot_UIBezierPath.move(to: CGPoint( x: 100,
y: 100))
sensor_plot_UIBezierPath.addLine( to: CGPoint( x: 200,
y: 200 ) )
init_app_display()
...start background events...
}
func init_app_display()
{
sensor_plot_CAShapeLayer.path = sensor_plot_UIBezierPath.cgPath
sensor_plot_CAShapeLayer.fillColor = UIColor.clear.cgColor // doesn't matter
sensor_plot_CAShapeLayer.strokeColor = UIColor.green.cgColor
sensor_plot_CAShapeLayer.lineWidth = 3.0
view.layer.addSublayer( sensor_plot_CAShapeLayer )
}
CODE THAT IS SUPPOSED TO SHOW RED LINE NEXT TO IT.................
(but actually turns initial green line to red, at same position -- no 2nd line next to first)
...display_plot_update() is called by event from background thread (specifically, BLE event didUpdateValueFor)
func display_plot_update()
{
DispatchQueue.main.async
{
self.sensor_plot_UIBezierPath.removeAllPoints()
self.sensor_plot_UIBezierPath.move(to: CGPoint( x: 110,
y: 100))
self.sensor_plot_UIBezierPath.addLine( to: CGPoint( x: 210,
y: 200 ) )
self.sensor_plot_CAShapeLayer.strokeColor = UIColor.red.cgColor
}
}
You don't show the code that installs your path in your shape layer, or the code that installs your shape layer as a sublayer of your view controller's view. You should show that code so that we can see what you're doing. You should also show the code for your init_app_display() function.
I assume that somewhere you have code that says:
sensor_plot_CAShapeLayer.path = self.sensor_plot_UIBezierPath.cgPath
and code that does something like
view.layer.addSublayer(sensor_plot_CAShapeLayer)
You seem to be under the impression that setting a CAShapeLayer's path to a specific bezier path links the shape layer to that path, and updating the path updates the shape layer. It doesn't work that way. After changing your path, you need to install the new path into your shape layer.
You could change your display_plot_update() function like this:
func display_plot_update()
{
DispatchQueue.main.async
{
// Create a new bezier path.
let newPath = UIBezierPath()
newPath.move(to: CGPoint( x: 110,
y: 100))
newPath.addLine( to: CGPoint( x: 210,
y: 200 ) )
self.sensor_plot_CAShapeLayer.strokeColor = UIColor.red.cgColor
self.sensor_plot_CAShapeLayer.path = newPath.cgPath // The important part
}
}
That would move the line to your new coordinates and make it red.
If you want to add a second line next to the first, then write your `display_plot_update()~ function like this:
func display_plot_update()
{
DispatchQueue.main.async
{
// Add the new line to the existing path (without clearing it)
self.sensor_plot_UIBezierPath.move(to: CGPoint( x: 110,
y: 100))
self.sensor_plot_UIBezierPath.addLine( to: CGPoint( x: 210,
y: 200 ) )
self.sensor_plot_CAShapeLayer.strokeColor = UIColor.red.cgColor
sensor_plot_CAShapeLayer.path = self.sensor_plot_UIBezierPath.cgPath // The important part
}
}
Note that you can't have lines of different colors in a single shape layer. A shape layer can only contain a single path, and it draws the entire path using a single stroke color and a single fill color. (The stroke color and the fill color can be different, but the entire path will use those same colors.)
Rather than trying to remove all the points from a path and reuse the path object, just construct a new path.
Once a path has been drawn, it’s part of the context, you can’t remove or change the graphic by changing the path. If you want to remove something from the drawing you have to erase the context (or part of it) and redraw where you’ve erased.
Usually you erase everything and redraw if.
You also should not be drawing in viewDidLoad, the view might not even be connected to a drawing surface at that point. Drawing should be done by the view, not the view controller.

draw rounded lines iOS Swift

Im trying to make an app which draws a fractal tree. I managed to make the code that generates all the start and end points from all the lines. I also managed to draw the lines but right now they are really boxy and want them to have rounded corners.
I using a UIView and using UIBezierPaths to draw the lines inside the view draw function. To retrieve the points I have an array of Branch objects inside a sigleton class. A branch object has among other things a startingPoint and a ending point which are both tuples( (x: Double, y: Double) ).
override func draw(_ rect: CGRect) {
super.draw(rect)
UIColor.blue.setStroke()
for branch in Tree.shared.branches{
let path = UIBezierPath()
print(branch.startingPoint)
print(branch.endingPoint)
path.move(to: CGPoint(x: branch.startingPoint.x, y: branch.startingPoint.y))
path.addLine(to: CGPoint(x: branch.endingPoint.x, y: branch.endingPoint.y))
path.lineWidth = 3
path.stroke()
}
}
How could i make the corners rounded?
Also if someone knows a free library that could facilitate this Im also interested.
edit: Im not interested in how to generate the tree, I have done already done this part of the code I need help with drawing the lines.
You don't need a library, you just need to spend a little time learning how to draw curves with UIBezierPath, and curves are one of the things that that class is best at. A key to drawing curves is understanding how control points work. Here's an answer I wrote some time ago about how to smoothly connect curved lines, which I think will help. Play around with -addCurveToPoint:controlPoint1:controlPoint2:.
If you don't actually want curves, but really just want the corners to be rounded rather than pointy, then all you need to do is to set the lineJoinStyle to kCGLineJoinRound.
About rounded corners of some view, is just set the parameters below:
func adjustView(_ view: UIView){
view.layer.borderWidth = 2.0
view.layer.borderColor = UIColor.red
view.layer.cornerRadius = 10
view.clipsToBounds = true
view.backgroundColor = UIColor.white
}
If you wish more information check the current documentation about layer property:
https://developer.apple.com/documentation/uikit/uiview/1622436-layer

Drawing a circular loader

I have to create a circular loader as per below image (Front is thicker than tail)
I am able to create a circular progress bar and also provided rotation animation.
below is code for circle
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2 * circleRadius, height: 2 * circleRadius)
let circlePathBounds = circlePathLayer.bounds
circleFrame.origin.x = circlePathBounds.midX - circleFrame.midX
circleFrame.origin.y = circlePathBounds.midY - circleFrame.midY
return circleFrame
}
func circlePath() -> UIBezierPath {
return UIBezierPath(ovalIn: circleFrame())
}
Using above code I can create a circle of equal width but not like as displayed image.
Please guide me how to create a loader like above image (tail is thinner than front). Any idea or suggestion would be great.
The simplest approach might be to create the image you want to display and then rotate it, rather than trying to draw it from scratch.
I haven't tried the following tutorial but I'm including it as a sample of how this might be done:
https://bencoding.com/2015/07/27/spinning-uiimageview-using-swift/
Note that the GitHub version (linked on that page) includes a Swift 4 update.

Two UIBezierPaths intersection as a UIBezierPath

I have two UIBezierPaths, one that represents the polygon of part of an image and the other is a path to be drawn on top of it.
I need to find the intersection between them so that only the point that are inside this intersection area will be colored.
Is there a method in UIBezierPath that can find intersection points - or a new path - between two paths ?
I'm not aware of a method for getting a new path that's the intersection of two paths, but you can fill or otherwise draw in the intersection by using the clipping property of each path.
In this example, there are two paths, a square and a circle:
let path1 = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
let path2 = UIBezierPath(ovalIn: CGRect(x:50, y:50, width: 100, height: 100))
I make a renderer to draw these, but you could be doing this in drawRect or wherever:
let renderer = UIGraphicsImageRenderer(bounds: CGRect(x: 0, y: 0, width: 200, height: 200))
let image = renderer.image {
context in
// You wouldn't actually stroke the paths, this is just to illustrate where they are
UIColor.gray.setStroke()
path1.stroke()
path2.stroke()
// You would just do this part
path1.addClip()
path2.addClip()
UIColor.red.setFill()
context.fill(context.format.bounds)
}
The resulting image looks like this (I've stroked each path for clarity as indicated in the code comments, in actual fact you'd only do the fill part):
I wrote a UIBezierPath library that will let you cut a given closed path into sub shapes based on an intersecting path. It'll do essentially exactly what you're looking for: https://github.com/adamwulf/ClippingBezier
NSArray<UIBezierPath*>* componentShapes = [shapePath uniqueShapesCreatedFromSlicingWithUnclosedPath:scissorPath];
Alternatively, you can also just find the intersection points:
NSArray* intersections = [scissorPath findIntersectionsWithClosedPath:shapePath andBeginsInside:nil];

Drawing lines on top of CGImage

I am currently trying to develop a drawing app. The problem that I've come across is optimising the app's performance.
I have subclassed an UIView, in which I am detecting user's inputs.
At first I tried to draw all the lines with CoreGraphics in draw(_ rect: CGRect) method but when the number of lines was 10 000+, the lag was very noticeable. So then I thought of creating a CGImage that I would make after every draw(...) cycle and then when the next draw cycle came, I would put just the new lines on top of that CGImage, therefore not drawing ALL previous lines again and saving precious time.
I am registering new lines in touchesBegan() and touchesMoved() methods and then I am calling self.needsDisplay().
This is the code that I am currently using:
var mainImage: CGImage?
var newLines: [Line]
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
//context?.translateBy(x: 0, y: bounds.height)
//context?.scaleBy(x: 1, y: -1)
if let image = mainImage {
context?.draw(image, in: bounds)
}
for line in newLines {
context?.setStrokeColor(UIColor.black.cgColor)
context?.setLineWidth(1)
context?.move(to: line.previousLocation)
context?.addLine(to: line.location)
context?.strokePath()
}
newLines = []
mainImage = context?.makeImage()
}
Now the problem with it is that the lines I draw are divided in two parts and are mirrored vertically. (Image split vertically). I have drawn here two continuous lines - one straight line and one curved line. The weird thing is that if I flipped half of the UIView, the lines would be aligned continuously with no space between them.
If I uncommented the two lines from my previously mentioned code (context?.translateBy(x: 0, y: bounds.height) and context?.scaleBy(x: 1, y: -1)), the image would look like this.
The only wrong thing now is that I was drawing on the lower side of the screen and getting results on the upper one.
How do I correct it? What is the proper solution for my problem?
Thank you very much!
When you comment out those two lines, then you are NOT making any changes to the graphics context. If you un-comment them, then you ARE changing the context at each call and not saving it. So I think you're essentially flipping it each time. Not sure if it's that but you should definitely be pushing/saving your context before drawing (and obviously restoring/popping it).
Check out this post: using UIImage drawInRect still renders it upside down.

Resources