Drawing lines with core graphics
Try drawing the line using CAShapeLayer like so:
func addLine(fromPoint start: CGPoint, toPoint end:CGPoint) {
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.strokeColor = UIColor.red.cgColor
line.lineWidth = 1
line.lineJoin = kCALineJoinRound
self.view.layer.addSublayer(line)
}
Hope this helps!
How to draw graph using core graphics
Yes, of course it is possible. A graph like that is just a line. You can use a UIBezierPath or CGPath to draw this. Typically you'd have an array of the points on your graph, then just add lines to each point when building your path.
WWDC 2011 session 129 goes through how the graph in the Stocks app is drawn and made "fancy", all using core graphics.
Drawing a rectangle on UIImageView using Core Graphics
You are replacing your image
with a new image composed of the previous image
plus a rectangle drawn over it. Rather than drawing the image from the image view, draw the original image.
Alternatively, you could render the the rectangle as a shape layer and just update that shape layer's path:
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
private let shapeLayer: CAShapeLayer = {
let _shapeLayer = CAShapeLayer()
_shapeLayer.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
_shapeLayer.strokeColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1).cgColor
_shapeLayer.lineWidth = 3
return _shapeLayer
}()
private var startPoint: CGPoint!
override func viewDidLoad() {
super.viewDidLoad()
imageView.layer.addSublayer(shapeLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startPoint = touches.first?.location(in: imageView)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let startPoint = startPoint, let touch = touches.first else { return }
let point: CGPoint
if let predictedTouch = event?.predictedTouches(for: touch)?.last {
point = predictedTouch.location(in: imageView)
} else {
point = touch.location(in: imageView)
}
updatePath(from: startPoint, to: point)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let startPoint = startPoint, let touch = touches.first else { return }
let point = touch.location(in: imageView)
updatePath(from: startPoint, to: point)
imageView.image = imageView.snapshot(afterScreenUpdates: true)
shapeLayer.path = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
shapeLayer.path = nil
}
private func updatePath(from startPoint: CGPoint, to point: CGPoint) {
let size = CGSize(width: point.x - startPoint.x, height: point.y - startPoint.y)
let rect = CGRect(origin: startPoint, size: size)
shapeLayer.path = UIBezierPath(rect: rect).cgPath
}
}
Where:
extension UIView {
func snapshot(afterScreenUpdates: Bool = false) -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
This is not only simpler, but more efficient, too.
That yields:
By the way, I might suggest using predictive touches in touchesMoved
. On a device (not the simulator) that can yield a slightly more responsive UI.
Drawing a circle using Core Graphics Context in iOS - Objective-C or Swift
You mean you only want the outline? Use CGContextStrokeEllipseInRect
in that case.
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(contextRef, 2.0);
CGContextSetStrokeColorWithColor(contextRef, [color CGColor]);
CGRect circlePoint = (CGRectMake(coordsFinal.x, coordsFinal.y, 50.0, 50.0));
CGContextStrokeEllipseInRect(contextRef, circlePoint);
Draw bullet arrow in Core Graphics
If you draw with strokes as suggested by @NSGod, you need to move to the upper or lower tip, and then line to the arrow head, then line to the lower or upper tip. If you want a 45 degree angle like the one you drew, the amount you add to or subtract from the coordinates should be equal.
You will also need to set the line width for the path. This will need to be proportional to the size.
CGPoint origin = CGPointMake(100, 20);
UIBezierPath *path = [UIBezierPath bezierPath];
// Upper tip
[path moveToPoint:CGPointMake(origin.x+20, origin.y-20)];
// Arrow head
[path addLineToPoint:CGPointMake(origin.x, origin.y)];
// Lower tip
[path addLineToPoint:CGPointMake(origin.x+20, origin.y+20)];
[[UIColor redColor] set];
// The line thickness needs to be proportional to the distance from the arrow head to the tips. Making it half seems about right.
[path setLineWidth:10];
[path stroke];
The result is a shape like this.
If you want to fill, not stroke - which may be necessary if you want to outline your arrow - you will need to plot out 6 points and then close the path.
Is this code drawing at the point or pixel level? How to draw retina pixels?
[Note: The code on the github example does not calculate the gradient on a pixel basis. The code on the github example calculates the gradient on a points basis. -Fattie]
The code is working in pixels. First, it fills a simple raster bitmap buffer with the pixel color data. That obviously has no notion of an image scale or unit other than pixels. Next, it creates a CGImage
from that buffer (in a bit of an odd way). CGImage
also has no notion of a scale or unit other than pixels.
The issue comes in where the CGImage
is drawn. Whether scaling is done at that point depends on the graphics context and how it has been configured. There's an implicit transform in the context that converts from user space (points, more or less) to device space (pixels).
The -drawInContext:
method ought to convert the rect using CGContextConvertRectToDeviceSpace()
to get the rect for the image. Note that the unconverted rect should still be used for the call to CGContextDrawImage()
.
So, for a 2x Retina display context, the original rect will be in points. Let's say 100x200. The image rect will be doubled in size to represent pixels, 200x400. The draw operation will draw that to the 100x200 rect, which might seem like it would scale the large, highly-detailed image down, losing information. However, internally, the draw operation will scale the target rect to device space before doing the actual draw, and fill a 200x400 pixel area from the 200x400 pixel image, preserving all of the detail.
Core graphics - Line using midpoint and angle
A unit vector with angle alpha
to the x-axis is
(cos(alpha), sin(alpha))
Now you want to draw a line that is tangential to the circle line, so it is perpendicular to the line from the center of the circle to the point on the circle line.
To get a perpendicular vector, you add 90º = π/2 to the angle:
(cos(alpha + π/2), sin(alpha + π/2)) = (-sin(alpha), cos(alpha))
using basic trigonometric identities.
This (hopefully) explains why the endpoint of the line must be computed as
CGPointMake(newPoint.x - distance * sinf(angleInRadians), newPoint.y + distance * cosf(angleInRadians));
Related Topics
How to Install iOS 7.0 and iOS 8.0 Simulators in Xcode 6.1
How to Do Weak Linking in Swift
How to Decode Aac Audio Buffer to Pcm Buffer in iOS
How to Pass Information Between Storyboard Segues
Custom Back Indicator Image and iOS 11
How to Get a Swift Type Name as a String with Its Namespace (Or Framework Name)
Uibutton Remove All Target-Actions
Wkwebview Causes My View Controller to Leak
Change Tab Bar Tint Color on iOS 7
When Does Awakefromnib Get Called
How to Build Boost-Libraries for Iphone
Launch Other Application Without Url Schema in Iphone
Why Would a 'Scheduledtimer' Fire Properly When Setup Outside a Block, But Not Within a Block
How to Connect Multiple Buttons in a Storyboard to a Single Action
Swift Optional Chaining Doesn't Work in Closure
Block_Release Deallocating UI Objects on a Background Thread
Use Different Googleservice-Info.Plist for Different Build Schemes