How do I create an onClick drawLine function in swift? - swift

I need to create a function that will make a line connecting two buttons when they are pressed. However, the only way I can figure out how to create a line currently is with a predetermined start and endpoint.
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContext(view.frame.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 3.0) /* SETTING LINE WIDTH: Set to the let and then the width next (let, width) */
CGContextSetStrokeColorWithColor(context, UIColor.purpleColor().CGColor)
CGContextMoveToPoint(context, oldLocation.x, oldLocation.y)
CGContextAddLineToPoint(context, newLocation.x, newLocation.y)
CGContextStrokePath(context)
UIGraphicsEndImageContext()
}
When I call this function, in my class, by testing, I have figured out that the entire thing runs, and oldLocation and newLocation have the appropriate values to create a line. However, no line is created (or at least not visible). Is there a command I am neglecting to run, or is something else the problem?

Related

Swift: NSBezier

This is the code inside my custom view class:
func drawTestingPoint(_ point: CGPoint, target: Int, output: Int) {
let path = NSBezierPath()
path.appendArc(withCenter: point, radius: 5, startAngle: 0, endAngle: 360)
NSColor.black.setStroke()
if target == output {
NSColor.green.setFill()
} else {
NSColor.red.setFill()
}
path.lineWidth = 3
path.fill()
path.stroke()
}
override func draw(_ dirtyRect: NSRect) {
//If I call the drawTestingPoint function here it works
}
Inside my viewDidLoad method in my NSViewController class I set up the custom view and try to draw the testing point:
let size = getDataViewSize()
let origin = CGPoint(x: view.frame.width/2-size.width/2, y: view.frame.height/2-size.height/2)
dataView = DataView(frame: CGRect(origin: origin, size: size))
view.addSubview(dataView)
dataView.drawTestingPoint(CGPoint(x: view.frame.width/2 y: view.frame.height/2), target: target, output: output)
dataView.needsDisplay = true
My problem is that no point is getting drawn. I think there can't be anything wrong with my drawTestingPoint function because when I call it inside my draw(_ dirtyRect: NSRect) function in my custom NSView class, it works. What can I do so I can call this function inside my viewDidLoad function how you can see in the codes snippets above so my point gets drawn
You can't just draw any time you want. Normally you set up a view and implement draw(_:) as you've done. The system calls the draw method when it needs the view to draw its contents. Before calling your draw(_:) method it sets up the drawing context correctly to draw inside your view and clip if you draw outside of the view. That's the bit you're missing.
As a general rule you should NOT draw outside of the view's draw(_:) method. I've done drawing outside of the draw(_:) method so infrequently that I don't remember what you'd need to do to set up the drawing context correctly. (To be fair I do mostly iOS development these days and my MacOS is getting rusty.)
So the short answer is "Don't do that."
EDIT:
Instead, set up your custom view to save the information it needs to draw itself. As others have suggested, when you make changes to the view, set needsDisplay=true on the view. That will cause the system to call the view's draw(_:) method on the next pass through the event loop

Get drawRect to redraw shape every x seconds

I come from javascript where drawing on the canvas or with divs is as easy as setting up the attributes of the object to be drawn and then calling a setInterval or requestAnimationFrame to update position and redraw.
I want to be able to do this with swift in xcode basically but I can't find any tutorials that have helped me enough to do it alone.
I have been able to draw whatever I want using drawRect but I have no idea how to set it up so it will redraw the shape I made as the coordinates change, for example, every 30ms. I just want to do it in the simplest way possible.
Let's say I have a class, drawExample, with this code inside it:
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 3.0)
CGContextSetStrokeColorWithColor(context, UIColor.purpleColor().CGColor)
CGContextMoveToPoint(context, shapePosX, shapePosY + 0)
CGContextAddLineToPoint(context, shapePosX + 250, shapePosY + 320)
CGContextAddLineToPoint(context, shapePosX + 300, shapePosY + 320)
CGContextAddLineToPoint(context, shapePosX + 0, shapePosY + 0)
CGContextSetFillColorWithColor(context,UIColor.purpleColor().CGColor)
CGContextFillPath(context)
}
And I just want to update the shapePosX and shapePosY using a timer
You'll want to leverage UIView & UIViewControllers's built in redraw as well as a timer. You'll do this from within the UIViewController:
Timer.scheduledTimer(withTimeInterval: 1.0,
repeats: true,
block: { (timer) in
self.view.setNeedsDisplay()
})
Note** it is setNeedsDisplay() that triggers the UIView to redraw.

Drawing multiple circles on a single line CGGraphicsContext

I have a custom class that manages a custom view that has a horizontal center on screen (which will represent a solid line). And I've drawn the line like this:
override func drawRect(rect: CGRect)
{
let line = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(line, 3.0)
CGContextSetStrokeColorWithColor(line, UIColor.redColor().CGColor)
CGContextMoveToPoint(line, 0, self.bounds.midY)
CGContextAddLineToPoint(line, self.bounds.width, self.bounds.midY)
CGContextStrokePath(line)
}
However, I need to draw a multiple solid circles on this line and try to look it like a point in a chart. I'm trying to make a mini chart representation in fact. How can I draw the circles? With a nested 'for in' loop or? Is there any official chart API from Apple?
UIGraphicsGetCurrentContext() gives you a context that you can draw multiple things into. Calling it line is not the right idea, because it can contain multiple lines, or circles, or all sorts of other things.
Think of the context as an artist sitting in front of a blank canvas. You give the artist instructions, like "draw a red line, then draw a blue circle". The artist follows the instructions, and afterwards, you look at the canvas.
Here's how you might draw a line and then a circle.
let context = UIGraphicsGetCurrentContext()
// Tell the context what stroked paths should look like
CGContextSetLineWidth(context, 3.0)
CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor)
// Draw a single line
CGContextMoveToPoint(context, 0, self.bounds.midY)
CGContextAddLineToPoint(context, self.bounds.width, self.bounds.midY)
CGContextStrokePath(context)
// Now draw a circle by filling a path.
// First, set the fill color:
CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)
// Specify how big the circle is, and where its center is:
let circleRadius = CGFloat(5.0)
let circleCenter = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
// Then add a circle to the context, by specifying the rectangle that surrounds it:
let circleRect = CGRect(x: circleCenter.x - circleRadius,
y: circleCenter.y - circleRadius,
width: circleRadius * 2,
height: circleRadius * 2)
CGContextAddEllipseInRect(context, circleRect)
// And fill that circle:
CGContextFillPath(context)
If you want to draw more circles but in different places, just call CGContextAddEllipseInRect and CGContextFillPath again, but with different values for circleRect. Depending on what you want, a for loop might be appropriate. It's entirely up to you.
If you don't want to write it yourself, there are lots of 3rd-party chart libraries available, just do a search. Apple does not provide an "official" one.

SpriteKit and programmatically draw simple graphics

Any suggestion on using SpriteKit rendering engine, but have the ability to draw game graphic programmatically. Those graphics are simple but slightly complex than just rectangular and oval.
I recently found out the SKShapeNode have a cap on the complexity when using custom path. I basically have a function that take a number to generate random CGPath shape, The larger the number the more complex and details about the shape. When I use a small number, there is no problem, but when I use a larger number to generate more complex path, SKShapeNode give me this error. (I tested on real device with iOS9.2.1 as well)
Assertion failed: (length + offset <= _length)
Related Question
Now I don't think using SKShapeNode is a good idea, so any suggestion on programmatically draw graphics that able to work nicely with SpriteKit?
I read somewhere else which is draw using the any graphics API available as long as it can convert to a format that SKTexture can initialize, is the right way to go?
If you will use SKTexture you can draw your chart with CGContext.
First of all you need create array of chart's points like:
let pathPoints: [CGPoint] = [...]
Then you need to call UIGraphicsBeginImageContext(rect.size) where rect.size is CGSize object which determines the size of the region in which will be inscribed the chart and rect is CGRect object. Next step is creation of CGContextRef and it setting:
UIGraphicsBeginImageContext(size)
let context: CGContextRef = UIGraphicsGetCurrentContext()!
CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor);
CGContextSetLineWidth(context, lineWidth)
where lineWidth is CGFloat type value.
After this you can create your chart in context like this:
if pathPoints.count > 1 {
CGContextMoveToPoint(context, pathPoints.first!.x, pathPoints.first!.y)
for var i = 1; i < pathPoints.count; ++i {
CGContextAddLineToPoint(context, pathPoints[i].x, pathPoints[i].y)
}
}
CGContextStrokePath(context)
Then you need to create image form context like this:
let image = UIGraphicsGetImageFromCurrentImageContext()
Then create texture form image and use it for SKSpriteNode object that will be added to the scene:
let pathTexture = SKTexture(image: image)
let pathNode = SKSpriteNode(texture: pathTexture)
pathNode.position = CGPointMake(rect.origin.x + rect.width/2 - lineWidth/2, rect.origin.y + rect.height/2 - lineWidth/2)
pathNode.zPosition = 0
someParrentNodeThatOnScene.addChild(pathNode)
UIGraphicsEndImageContext()
That's all you need to create chart.

Erasing lines in pdf context

I'm trying to generate a pdf of my UIView. In the view I can simply erase lines using CGContextSetBlendMode(context, CGBlendMode.Clear). While this works properly on the context of the UIView when I draw to the pdf context (as generated using UIGraphicsBeginPDFContextToData) the same code results in a line with the same color as set in CGContextSetStrokeColorWithColor. Does anybody know of an alternative way to achieve erasing in a pdf context?
For instance, when I run the code below a context from drawrect it results in a faint red line (alpha = 0.5) with a small gap running through the red line. However, if I supply a pdfcontext it becomes a faint redline with a hard red line running through it (as another thin red line with alpha 0.5 is superimposed)
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, 10, 10)
CGPathAddLineToPoint(path, nil, 10,100)
CGContextAddPath(context, path)
CGContextSetLineCap(context, CGLineCap.Round)
CGContextSetStrokeColorWithColor(context, UIColor.redColor().colorWithAlphaComponent(0.5).CGColor)
CGContextSetLineWidth(context, 10.0)
CGContextStrokePath(context)
CGPathMoveToPoint(path, nil, 10, 10)
CGPathAddLineToPoint(path, nil, 10,100)
CGContextAddPath(context, path)
CGContextSetBlendMode(context, CGBlendMode.Clear)
CGContextSetLineWidth(context, 2.0)
CGContextStrokePath(context)
I ended up solving my problem with a workaround. For the benefit of anyone else with this issue, here goes.
I draw subviews individually in the PDFContext. For each I check if it contains erasing. If this is the case I open a separate ImageContext and generate an image of the view as such.
UIGraphicsBeginImageContext(objectView.frame.size)
if let imageContext = UIGraphicsGetCurrentContext() {
objectView.drawObjectInContext(imageContext,rect: objectView.bounds)
self.applyEraser(imageContext, eraserShape: objectView.eraserLayer.eraseShape)
let image = UIGraphicsGetImageFromCurrentImageContext()
CGContextTranslateCTM(pdfContext, 0, objectView.bounds.height)
CGContextScaleCTM(pdfContext, 1.0, -1.0)
CGContextDrawImage(pdfContext, objectView.bounds, image.CGImage)
}
UIGraphicsEndImageContext()
Note that the image is upside compared to regular drawing in CGContext, which is why I invoke CGContextTranslate en CGContextScale.
The downside is that you get images of subviews in your PDF rather than perhaps simple text. However, if you do it for each view separately you can draw the views without erasing properly as text or whatever you want.