PDFKit iOS 11: How to change the line width of Ink annotation? - ios

I'm drawing some ink annotations on a PDF file using PDFKit. But I can't change the width of the lines. I thought that doing:
let path = UIBezierPath()
path.lineWidth = 20 // important line
path.move(to: originPoint)
path.addLine(...)
annotation.add(path)
would be enough since modifying the lineWidth of a Bezier path works when drawing in Core Graphics. But here, it does not change anything, so how to change the line width of an annotation ?

Use border property of PDFAnnotation to change thickness of UIBezierPath added to it.
let p = UIBezierPath()
p.move(to: CGPoint(x: 400, y: 200))
p.addLine(to: CGPoint(x: 500, y: 100))
p.addLine(to: CGPoint(x: 400, y: 0))
p.close()
let b = PDFBorder()
b.lineWidth = 10.0
let pageBounds = page.bounds(for: .artBox)
let inkAnnotation = PDFAnnotation(bounds: pageBounds, forType: PDFAnnotationSubtype.ink, withProperties: nil)
inkAnnotation.add(p)
inkAnnotation.border = b
inkAnnotation.color = .green
page.addAnnotation(inkAnnotation)

Related

How do I make sure that my label is always centered on the line?

I tried to take the coordinates of the LinePath.bounds.Max and line Path.bounds.Maxi , but it does not give the desired result
enter code here
private func addLine(start:CGPoint, end: CGPoint) {
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.strokeColor = UIColor.white.cgColor
line.lineWidth = 4
distanceLabel.text = String(distanceForLabel)
distanceLabel.textColor = .black
distanceLabel.frame = CGRect(x: linePath.bounds.midX,
y: line.bounds.midY,
width: linePath.bounds.width / 2,
height: 20)
uiImageView.addSubview(distanceLabel)
One problem is that your are setting the y value for the label's frame to:
y: line.bounds.midY
but it should be:
y: linePath.bounds.midY
However, your frame code puts the top-left corner of the label at the midpoint of the line.
If you want the center of the label to be centered on the line, use this for your label's frame:
distanceLabel.frame.size = CGSize(width: linePath.bounds.width / 2.0, height: 20.0)
distanceLabel.center = CGPoint(x: linePath.bounds.midX, y: linePath.bounds.midY)

Why the edges of UIView are not smooth for huge corner radius?

class AttributedView: UIView {
private let gradient = CAGradientLayer()
var cornerRadius: CGFloat = 3 {
didSet {
layer.cornerRadius = cornerRadius
}
}
}
I simply use it for both: button view (corner radius: 20) and background circle (corner radius: 600).
Why button is smooth, and background is not?
With iOS 13.0 you can simple do, in addition to setting corner radius
yourView.layer.cornerCurve = .continuous
You should use bezzier paths and draw circle. After that you will receive nice, smooth edges.
let context = UIGraphicsGetCurrentContext()!
let gradient = CGGradient(colorsSpace: nil, colors: [UIColor.red.cgColor, UIColor.white.cgColor] as CFArray, locations: [0, 1])!
let ovalPath = UIBezierPath(ovalIn: CGRect(x: 64, y: 9, width: 111, height: 93))
context.saveGState()
ovalPath.addClip()
context.drawLinearGradient(gradient, start: CGPoint(x: 119.5, y: 9), end: CGPoint(x: 119.5, y: 102), options: [])
context.restoreGState()
UIBezierPath is a simple and efficient class for drawing shapes using Swift, which you can then put into CAShapeLayer, SKShapeNode, or other places. It comes with various shapes built in, so you can write code like this to create a rounded rectangle or a circle:
let rect = CGRect(x: 0, y: 0, width: 256, height: 256)
let roundedRect = UIBezierPath(roundedRect: rect, cornerRadius: 50)
let circle = UIBezierPath(ovalIn: rect)
You can also create custom shapes by moving a pen to a starting position then adding lines:
let freeform = UIBezierPath()
freeform.move(to: .zero)
freeform.addLine(to: CGPoint(x: 50, y: 50))
freeform.addLine(to: CGPoint(x: 50, y: 150))
freeform.addLine(to: CGPoint(x: 150, y: 50))
freeform.addLine(to: .zero)
If your end result needs a CGPath, you can get one by accessing the cgPath property of your UIBezierPath.
You probably should clip bounds of this view:
attributedView.clipsToBounds = true

CAShaperLayer as mask show only 1/4 of UIView

I try to make UIView to show zig-zag bottom edge. Something like http://www.shutterstock.com/pic-373176370/stock-vector-receipt-vector-icon-invoice-flat-illustration-cheque-shadow-bill-with-total-cost-amount-and-dollar-symbol-abstract-text-receipt-paper-isolated-on-green.html?src=zMGBKj_5etMCcRB3cKmCoA-1-2
I have method that create a path and set as mask, but it show as 1/4 of the view. Do I need to set something else? Look like a retina problem or coordinate problem, but don't sure which one.
func layoutZigZag(bounds: CGRect) -> CALayer {
let maskLayer = CAShapeLayer()
maskLayer.bounds = bounds
let path = UIBezierPath()
let width = bounds.size.width
let height = bounds.size.height
let topRight = CGPoint(x: width , y: height)
let topLeft = CGPoint(x: 0 , y: height)
let bottomRight = CGPoint(x: width , y: 0)
let bottomLeft = CGPoint(x: 0 , y: 0)
let zigzagHeight: CGFloat = 10
let numberOfZigZag = Int(floor(width / 23.0))
let zigzagWidth = width / CGFloat(numberOfZigZag)
path.move(to: topLeft)
path.addLine(to: bottomLeft)
// zigzag
var currentX = bottomLeft.x
var currentY = bottomLeft.y
for i in 0..<numberOfZigZag {
let upper = CGPoint(x: currentX + zigzagWidth / 2, y: currentY + zigzagHeight)
let lower = CGPoint(x: currentX + zigzagWidth, y: currentY)
path.addLine(to: upper)
path.addLine(to: lower)
currentX += zigzagWidth
}
path.addLine(to: topRight)
path.close()
maskLayer.path = path.cgPath
return maskLayer
}
and
let rect = CGRect(x: 0, y: 0, width: 320, height: 400)
let view = UIView(frame: rect)
view.backgroundColor = UIColor.red
let zigzag = layoutZigZag(bounds: rect)
view.layer.mask = zigzag
Path look correct
Result is 1/4 of the view
Change maskLayer.bounds = bounds to maskLayer.frame = bounds
Update:
Upside down is because of difference between the UI and CG, we are creating the path in UIBezierPath and converting that path as a CGPath (maskLayer.path = path.cgPath). First we have to know the difference, where CGPath is Quartz 2D and origin is at the bottom left while in UIBezierPath is UIKit origin is at the top-left. As per your code, applied coordinates are as per the top-left ie UIBezierPath when we transform to CGPath (origin at bottom left) it becomes upside down. so change the code as below to get the desired effect.
let topRight = CGPoint(x: width , y: 0)
let topLeft = CGPoint(x: 0 , y: 0)
let bottomLeft = CGPoint(x: 0 , y: (height - zigzagHeight))
Quartz 2D Coordinate Systems
UIBezierPath Coordinate Systems

CGPath-based CPTPlotSymbol inverted and distorted

I'm currently working with custom markers on a scatter plot and found myself with an issue that results in CPTPlotSymbol created from a CGPath upside down and distorted.
I've tested the path-creating code in a playground and it works without issues, drawing the path with the correct shape and orientation.
Here's the path drawing code:
private func getOuterPathInRect(rect: CGRect) -> CGPath {
let circlePath: CGPath = {
let p = CGMutablePath()
let topHundred = CGRect(x: 0, y: 0, width: 100, height: 100)
p.addEllipse(in: topHundred)
return p
}()
let arrowPath: CGPath = {
let p = CGMutablePath()
p.move(to: CGPoint(x: rect.midX, y: rect.maxY - 5.0))
p.addLine(to: CGPoint(x: rect.midX - 7.5, y: rect.maxY - 15.0))
p.addLine(to: CGPoint(x: rect.midX + 7.5, y: rect.maxY - 15.0))
return p
}()
let path = CGMutablePath()
path.addPath(circlePath)
path.addPath(arrowPath)
return path
}
And the code that creates the CPTPlotSymbol is:
func symbol(for plot: CPTScatterPlot, record idx: UInt) -> CPTPlotSymbol? {
let index = Int(idx)
guard items[index].requiresMarker else { return nil }
let rect = CGRect(x: 0, y: 0, width: 120, height: 120)
let marker = BallMarkerView()
marker.contentMode = .center
let path = marker.pathIn(rect: rect)
let symbol = CPTPlotSymbol.customPlotSymbol(with: path)
symbol.size = rect.size
symbol.fill = CPTFill(color: CPTColor.red())
return symbol
}
My goal was to use a custom UIView as a marker, but I couldn't find an API to do so, so I resorted to providing a path-based marker and fill it with an image representation of the marker.
Is this the proper way of doing it?
Why is my path being drawn distorted and upside down? The path being upside down could be explained by the difference in the coordinate system between UIKit and CoreGraphics, but that doesn't explain the distorsion.
Thanks!
Because Core Plot shares drawing code between the Mac and iOS, it uses the same drawing coordinate system on both platforms where (0, 0) is the lower-left corner of the drawing canvas. This is flipped from the normal drawing coordinate system on iOS.

Creating Triangle with UIBezierPath in Swift

I am trying to understand how to create a triangle shape with Swift. I found this code that creates a triangle.
class TriangleLayer: CAShapeLayer {
let innerPadding: CGFloat = 30.0
override init() {
super.init()
fillColor = Colors.red.CGColor
strokeColor = Colors.red.CGColor
lineWidth = 7.0
lineCap = kCALineCapRound
lineJoin = kCALineJoinRound
path = trianglePathSmall.CGPath
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var trianglePathSmall: UIBezierPath {
let trianglePath = UIBezierPath()
trianglePath.moveToPoint(CGPoint(x: 5.0 + innerPadding, y: 95.0)) // #1
trianglePath.addLineToPoint(CGPoint(x: 50.0, y: 12.5 + innerPadding)) // #2
trianglePath.addLineToPoint(CGPoint(x: 95.0 - innerPadding, y: 95.0)) // #3
trianglePath.closePath()
return trianglePath
}
And this code creates a shape like this
in the middle of the screen.
I tried to tweak and play around with it to understand how it works; however, at this point I realised that I got lost with the logic quite a bit. I placed the CGPoints of above triangle on an x-y axis in my head and it seems something like:
#1 x:35, y:95 #3 x:65, y:95
  #2 x:50, y: 42.5
But the triangle is created upside-down if I place the dots on the x-y axis.
What I want to achieve is what the axis tells, and I want to achieve..
. . .
<like this. not this>
. . .
You just have the axes in your head upside down. The coordinate system starts at 0,0 and extends right in X and down in Y.
So your points are really:
#2 x:50, y: 42.5
#1 x:35, y:95 #3 x:65, y:95
to get your desired triangle you'd have something like:
#1 x:35, y:95 #3 x:65, y:95
#2 x:50, y: 147.5
Result triangles
Code in swift5
//TriangleView
extension UIView {
func setRightTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width //you can use triangleView.frame.size.height
let path = CGMutablePath()
path.move(to: CGPoint(x: heightWidth/2, y: 0))
path.addLine(to: CGPoint(x:heightWidth, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setLeftTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: heightWidth/2, y: 0))
path.addLine(to: CGPoint(x:0, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setUpTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth, y:heightWidth))
path.addLine(to: CGPoint(x:0, y:heightWidth))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setDownTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth, y:0))
path.addLine(to: CGPoint(x:0, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
}
Swift 4.*
The easiest way of doing it by using AutoLayout:
Open your Storyboard and drag a UIView in UIViewController, position it and set the size as you wish (that's the place where the triangle will be). Set the view background to be transparent.
Create a new class, you can name it however you want (I named mine TriangleView). This will be the content of that class:
class TriangleView: UIView {
// predefined variables that can be changed
var startPoint: CGPoint = CGPoint(x: 0.0, y: 0.5)
var endPoint: CGPoint = CGPoint(x: 1.0, y: 0.5)
var firstGradientColor: UIColor = UIColor.white
var secondGradientColor: UIColor = UIColor.blue
let gradient: CAGradientLayer = CAGradientLayer()
override func draw(_ rect: CGRect) {
let height = self.layer.frame.size.height
let width = self.layer.frame.size.width
// draw the triangle
let path = UIBezierPath()
path.move(to: CGPoint(x: width / 2, y: 0))
path.addLine(to: CGPoint(x: width, y: height))
path.addLine(to: CGPoint(x: 0, y: height))
path.close()
// draw the triangle 'upside down'
// let path = UIBezierPath()
// path.move(to: CGPoint(x: 0, y: 0))
// path.addLine(to: CGPoint(x: width, y: 0))
// path.addLine(to: CGPoint(x: width / 2, y: height))
// path.close()
// add path to layer
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.lineWidth = 1.0
// Add the gradient for the view background if needed
gradient.colors = [firstGradientColor.cgColor, secondGradiendColor.cgColor]
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.frame = self.bounds
gradient.mask = shapeLayer
self.layer.addSublayer(gradient)
}
}
Go to your Storyboard, select the UIView and in Identity Inspector write the class name TriangleView
Enjoy your triangle! :)

Resources