The completely different coordinate programs of Cocoa and UIKit trigger downside when drawing a string in a UIGraphicsContext.
I’ve written code for macOS
, the place I draw a mixture of primitives, attributedText, and single CGGlyphs
onto a CGContext
. Subsequently I first create a container object that holds a number of arrays of objects. These objects verify to a protocol DrawingContent
. The one protocol requirement is a technique .draw(inContext: CGContext)
.
My customized NSView
is holding such a container, and the view’s draw methodology then iterates over the container parts, calling their .draw
methodology with the present CGContext
. I’ve stripped the code right down to the minimal and can present it on the finish of this query. The results of the pattern code earlier than being flipped appears to be like like this:
My precise code is far more complicated, because the objects to be drawn are a results of a number of algorithms. The rationale I put the objects right into a container is that from the beginning on I meant to additionally create an iOS
model, so the outcomes of the required calculations first go right into a framework-agnostic summary class. I used to be conscious of the truth that UIKit
Views use a flipped coordinate system and that I ultimately must take care of that. Now I’m trying to put in writing the iOS
model – and I’m at a loss.
The preliminary outcome once I simply name the code with none alteration from an UIView appears to be like like this:
This was – type of – anticipated, though the textual content not being flipped is odd. So to flip the entire imagery I set a CGAffineTransform
to the context earlier than passing it to the drawing container like this:
currentContext.translateBy(x: 0, y: rect.peak)
currentContext.scaleBy(x: 1, y: -1)
The outcome appears to be like like this:
As you possibly can see the canvas is flipped as anticipated, however, sadly, the AttributedString
is drawn the other way up. Apparently, although, the only CGGlyph
, though utilizing the identical font, is drawn accurately.
WHAT I HAVE TRIED SO FAR:
-
Solutions to related questions recommend to use n
CGAffineTransform
to the contexts’s textMatrix (along with the context itself). This causes theCGGlyph
(which had been drawn accurately) now to be the other way up and at a unsuitable location, whereas theAttributedText
stays untouched. -
In one other place it was advised to briefly flip the context only for the drawing of the string like this:
context.saveGState()
context.scaleBy(x: 1, y: -1)
context.translateBy(x: 0, y: -bounds.peak)
// draw string right here...
context.restoreGState()
The issue with that is, that the container doesn’t know in regards to the body’s peak. Once I (for testing) hardcode the peak the textual content now attracts accurately, however at its „authentic“ unsuitable place on the prime of the view. The 2 similar transformations clearly cancel out one another.
- I additionally tried to manually apply the scaling and tranlating to the y-position of the textual content, along with what I did in step 2. This revealed one other downside: The textual content now exhibits at about the appropriate place, however not fairly: It now dangles from the imaginary baseline as an alternative of sitting on it.
I additionally need to state, that even when I’d learn the way the font metrics come into play right here, making use of this to my precise code can be an amazing quantity of labor, as – as said – the drawing strategies of my objects have no idea the peak of the view they attract, so I might must move that as an extra argument, and – most significantly – I must write all of that scaling and translating conditionally when compiling foriOS
– which might counteract my strategy to maintain the container-code framework-agnostic.
I may go down two completely different fundamental routes:
-
I may rewrite my whole code to work within the flipped coordinate system of
iOS
, and formacOS
I set the .isFlipped parameter on my customizedNSView
to return true. This is able to contain numerous work, and it nonetheless wouldn’t clear up the issue of theAttributedStrings
being the other way up. -
I proceed looking for a option to flip EVERYTHING on the canvas.
I would like the second route, clearly.
QUESTION:
How do I draw Objects, which can be positioned for a cartesian coordinate system (like Cocoa/Quartz 2D makes use of), right into a CGContext
that makes use of a flipped coodinate system (like UIKIT does), so that each one objects of various varieties (primitives, Glyphs and Strings) are drawn on the appropriate place and within the appropriate orientation?
Right here is my code. For comfort, as an alternative of displaying the UIView
code I present a self containing model for macOS
with the NSView
set to .isFlipped
– the behaviour is – so far as I may study – precisely the identical.
import AppKit
public protocol DrawingContent {
func draw(inContext context: CGContext)
}
public struct TestPath: DrawingContent {
public func draw(inContext context: CGContext) {
context.beginPath()
context.setLineWidth(CGFloat(2))
context.transfer(to: CGPoint(x: 200, y: 30))
context.addLine(to: CGPoint(x: 250, y: 30))
context.addLine(to: CGPoint(x: 250, y: 60))
context.addLine(to: CGPoint(x: 230, y: 60))
context.strokePath()
}
}
public struct TestText: DrawingContent {
public func draw(inContext context: CGContext) {
let font = CTFontCreateWithName("Helvetica" as CFString, 24, nil)
var attributes: [NSAttributedString.Key: AnyObject] = [NSAttributedString.Key.font : font,
NSAttributedString.Key.foregroundColor : NSColor.black]
let attributedText = NSAttributedString(string: "Testtext", attributes: attributes)
let descender = CTFontGetDescent(font)
let textOrigin = CGPoint(x: 100, y: (30-descender))
attributedText.draw(at: textOrigin)
}
}
public struct TestGlyph: DrawingContent {
public func draw(inContext context: CGContext) {
var font = CTFontCreateWithName("Helvetica" as CFString, 24, nil)
var place = CGPoint(x: 30, y: 30)
var glyph = CGGlyph(36) // Capital Letter A
CTFontDrawGlyphs(font, &glyph, &place, 1, context)
}
}
public class FlippedTestView: NSView {
var drawingContent: [DrawingContent]
override public var isFlipped: Bool {return true}
override public init(body: CGRect) {
let drawingContent: [DrawingContent] = [TestGlyph(), TestText(), TestPath()]
self.drawingContent = drawingContent
tremendous.init(body: body)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been carried out")
}
override open func draw(_ rect: CGRect) {
let currentContext = NSGraphicsContext.present!.cgContext
currentContext.translateBy(x: 0, y: rect.peak)
currentContext.scaleBy(x: 1, y: -1)
for factor in drawingContent {
factor.draw(inContext: currentContext)
}
}
}
@major
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code right here to initialize your utility
let testView = FlippedTestView(body: CGRect(x: 0, y: 0, width: 300, peak: 100))
self.window.contentView = testView
self.window.setFrame(CGRect(x: 200, y: 200, width: 300, peak: 100), show: true)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code right here to tear down your utility
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}