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.
Related
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
I try to make an app where the user can draw or add an arrow, and transform this arrow (translate, rotate, ...).
For the moment, I manage to draw the arrow and make all the transformations on it, but what I would like to do now is to be able to modify the arrow by dragging its edges.
To draw an arrow, I just create a UIView with a height of 20px (it is the thickness of the arrow), and a width of 400 (length of the arrow).
func drawArrow(frame: CGRect) {
let thicknessArrow = 20
let viewHorizontalArrow = UIView()
viewHorizontalArrow.frame.origin = CGPoint(x: 100, y: 600)
viewHorizontalArrow.frame.size = CGSize(width: 400, height: thicknessArrow)
drawDoubleArrow(viewHorizontalArrow, startPoint: CGPoint(x: 0, y: thicknessViewWithArrow/2), endPoint: CGPoint(x: viewHorizontalArrow.frame.width, y: thicknessViewWithArrow/2), lineWidth: 10, color: UIColor.blackColor())
}
After that, I transform the UIView thanks to the pan, pinch and rotate gesture.
The function "drawDoubleArrow" create an arrow with BezierPath and add it to the layer of the UIView.
I hope these explanations are clear enough :).
Could you help me to find a solution ?
Thanks !
UIBezierPath on it's own cannot easily be manipulated by gestures because it doesn't recognize them. But a UIView can recognize gestures. Combined with a CALayer's ability to be transformed and it's hitTest() method, I think you can achieve what you want.
// I'm declaring the layer as a shape layer, but depending on how your error thick is, you may need a rectangular one
let panGesture = UIPanGestureRecognizer()
var myLayer = CAShapeLayer()
viewDidLoad:
// I'm assuming you've created myBezierPath, an arrow path
myLayer.path = myBezierPath.cgPath
// Adding the pan gesture to the view's controller
panGesture.addTarget(self, action: #selector(moveLayer))
self.addGestureRecognizer(panGesture)
moveLayer:
func moveLayer(_ recognizer:UIPanGestureRecognizer) {
let p = recognizer.location(in: self)
// You will want to loop through all the layers you wish to transform
if myLayer.hitTest(p) != nil {
myLayer.position = p
}
}
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?
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.
I'm trying to draw a graph using Swift. I have created a custom NSView and a NSView class.
In the ViewController I would like to call a method that updates the NSView and draws the complete graph using values entered by the user.
import Cocoa
class Graph: NSView {
let startPoint = NSPoint(x: 10, y: 10)
override func drawRect(dirtyRect: NSRect) {
NSColor.whiteColor().set() // set white color
NSRectFill(dirtyRect) // fill the Rect in the View
// draw axes lines
NSColor.blackColor().set()
NSBezierPath.strokeLineFromPoint(startPoint, toPoint: NSPoint(x: Double(dirtyRect.width) - 10.0, y: 10.0))
NSBezierPath.strokeLineFromPoint(startPoint, toPoint: NSPoint(x: 10.0, y: Double(dirtyRect.height) - 10.0))
}
func drawGraphicsOfNewteork(arrayOfData: [NSTextField]){
// Here I would draw in the View some lines using the arrayOfData
}
}
Have you read the documentation at all? Searching for "drawing" turns up the Cocoa Drawing Guide.
As others have stated, drawing is done from within drawRect(). The system will call it at the "right time", after you flag it by setting needsDisplay to true/YES.