6

When drawing on screen using finger/pencil with the PencilKit API, I would like to set the width of strokes to be a constant. Currently, the width setting in say PKInkingTool only sets the baseline width when drawing with finger or pencil, and the width varies if one does slow or fast strokes with their finger/pencil.

I'm not sure how to setup a minimum example, there is a lot of code to get a PencilKit View working. You can use this example from Apple to setup a simple drawing app.

Here's my code to choose a default tool for drawing:

canvas.tool = PKInkingTool(.pen, color: .white, width: 10)

where canvas is a PKCanvasView object. There is a validWidthRange property within each InkType (link to docs) but I'm unsure if this is what can help me achieve what I want.

Venkat
  • 446
  • 1
  • 4
  • 15

2 Answers2

9

I have found a solution (thanks to Will Bishop), however I'm not sure it's the best for everyone. I solve it by redrawing the stroke after its been completed with a constant width. Apple released new APIs for PKStroke and PKStrokePoint at WWDC2020 for iOS 14. Here's the relevant section of my code (newDrawing is the new drawing with constant stroke width, while canvasView refers to the current drawing on my PKCanvas):

var newDrawingStrokes = [PKStroke]()
    for stroke in canvasView.drawing.strokes {
        var newPoints = [PKStrokePoint]()
        stroke.path.forEach { (point) in
        let newPoint = PKStrokePoint(location: point.location, 
                                     timeOffset: point.timeOffset, 
                                     size: CGSize(width: 5,height: 5), 
                                     opacity: CGFloat(1), force: point.force,
                                     azimuth: point.azimuth, altitude: point.altitude)
        newPoints.append(newPoint)
        }
        let newPath = PKStrokePath(controlPoints: newPoints, creationDate: Date())
        newDrawingStrokes.append(PKStroke(ink: PKInk(.pen, color: UIColor.white), path: newPath))
     }
let newDrawing = PKDrawing(strokes: newDrawingStrokes)

This is some preliminary code that I threw together, so if you find any bugs/mistakes do let me know!

Venkat
  • 446
  • 1
  • 4
  • 15
  • Yes, PKStroke and PKStrokePoint are in the new Betas. I'll add that to my answer. – Venkat Sep 08 '20 at 15:05
  • where did you put that code? I tried putting inside canvasViewDidEndUsingTool delegate method, but then on the last line when I assign canvasView.drawing = newDrawing, it throws a warning and reverts my stroke. Any help will be highly appreciated! – Roger Oba Jul 03 '21 at 20:21
  • 1
    Well I put it in the `canvasViewDrawingDidChange` delegate method, but that's specific to my use case. Regarding your error, I'm not sure you can reassign the canvas drawing like that - I use `newDrawing` for some background processing, but I don't change the canvas drawing display itself. – Venkat Jul 04 '21 at 16:19
  • Ahh I see :( I wanted to modify the drawing on the cavas itself and when using canvasViewDrawingDidChange, it'd trigger a recursion. I'll probably have to use a custom solution instead of PencilKit. Thank you for your response! – Roger Oba Jul 05 '21 at 17:09
1

There is a way to make it fixed via swizzling UITouch force and timestamp methods.

You can add this Objective-C category in your project:

#import <objc/runtime.h>

@implementation UITouch (UITouch_Overrides)

+ (void)load {
    [super load];

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleForce];
        [self swizzleTimestamp];
    });
}

+ (void)swizzleForce {
    [self swizzle:@selector(force) with:@selector(ed_force)];
}

+ (void)swizzleTimestamp {
    [self swizzle:@selector(timestamp) with:@selector(ed_timestamp)];
}

+ (void)swizzle:(SEL)originalSelector with:(SEL)swizzledSelector {
    Class class = [self class];
    
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    BOOL didAddMethod =
        class_addMethod(class,
            originalSelector,
            method_getImplementation(swizzledMethod),
            method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(class,
            swizzledSelector,
            method_getImplementation(originalMethod),
            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

- (CGFloat)ed_force {
    return 1.0;
}

- (NSTimeInterval)ed_timestamp {
    return 0;
}

@end
uson1x
  • 407
  • 3
  • 16
  • The load function is called only once per app launch and I am unable to change the width to original PKInkingTool style dynamically later. How can i switch between constant width and default PKInkingTool behaviour dynamically based on a constant variable from swift? – abdul rahuman Dec 20 '21 at 17:05