I'm trying to build a drawing tool on Xcode building for Mac Catalyst, using the UIKit Framework, but I'm running into issues with cursor location accuracy:
After initializing an iOS project configured for Swift and Storyboard, here's an absolutely minimal ViewController.swift that produces the bug:
import UIKit
class CanvasView: UIView {
var drawPoint: CGPoint = .zero
let radius = 12.0
override func draw(_: CGRect) {
UIGraphicsGetCurrentContext()!
.fillEllipse(in: CGRect(x: drawPoint.x - radius,
y: drawPoint.y - radius,
width: radius * 2,
height: radius * 2))
}
override func touchesBegan(_ touches: Set<UITouch>, with _: UIEvent?) {
drawPoint = touches.first!.location(in: self)
setNeedsDisplay()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view = CanvasView()
view.backgroundColor = .white
let hover = UIHoverGestureRecognizer(target: self, action: #selector(hover))
view.addGestureRecognizer(hover)
}
@objc func hover(_ recognizer: UIHoverGestureRecognizer) {
switch recognizer.state {
case .began, .changed: NSCursor.crosshair.set()
default: NSCursor.arrow.set()
}
}
}
All this does is
- Turn your cursor into a crosshair icon upon hovering
- Draw a circle centered at the cursor upon clicking
True enough, this draws a circle centered at the cursor but it's always off by a pixel or two, and moreover this inaccuracy is inconsistent.
Sometimes the circle drawn in offset by 1 pixel to the right and 1 pixel down, sometimes it's 2, and this is a major issue when it comes to users trying to draw things precisely.
I've tried using CAShapeLayer and its corresponding func draw(_: CALayer, in: CGContext), but this exact same inaccuracy is reproduced there.
I've also tried using preciseLocation(in:) instead of location(in:) but again the bug is still there.
Notably, when I tried building for iPhone/iPad and Xcode opens the same code in a Simulator, this bug vanishes, and the circle is centered perfectly on the touch point.
Any help is appreciated!
EDIT:
This time I tried using a minimal working example with Cocoa only, no Mac Catalyst nor UIKit, and somehow, the problem still persists. Here's the ViewController.swift code:
import Cocoa
class View: NSView {
var point: CGPoint = .zero
let radius: CGFloat = 20
override func draw(_: NSRect) {
NSColor.blue.setStroke()
let cross = NSBezierPath()
let v = radius * 2
cross.move(to: CGPoint(x: point.x - v, y: point.y))
cross.line(to: CGPoint(x: point.x + v, y: point.y))
cross.move(to: CGPoint(x: point.x, y: point.y - v))
cross.line(to: CGPoint(x: point.x, y: point.y + v))
cross.lineWidth = 0.8
cross.stroke()
}
override func mouseDown(with event: NSEvent) {
point = event.locationInWindow
setNeedsDisplay(bounds)
NSCursor.crosshair.set()
}
}
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let sub = View(frame: view.frame)
sub.autoresizingMask = [.height, .width]
view.addSubview(sub)
}
}
Only this time I use a cross to show more clearly that the alignment is off.
I thought this might be a result of the build target being Debug, but this bug still shows up in an Archive built for Release.