11.4 C
New York
Thursday, March 27, 2025

ios – How do I re-use code that pulls onto a macOS GraphicsContext to attract onto an UIKit context?


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:

Result of original code

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:

result of flipped coordinate system

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:

result of flipped system transformed back

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:

  1. Solutions to related questions recommend to use n CGAffineTransform to the contexts’s textMatrix (along with the context itself). This causes the CGGlyph (which had been drawn accurately) now to be the other way up and at a unsuitable location, whereas the AttributedText stays untouched.

  2. 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.

  1. 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 for iOS – which might counteract my strategy to maintain the container-code framework-agnostic.

I may go down two completely different fundamental routes:

  1. I may rewrite my whole code to work within the flipped coordinate system of iOS, and for macOS I set the .isFlipped parameter on my customized NSView to return true. This is able to contain numerous work, and it nonetheless wouldn’t clear up the issue of the AttributedStrings being the other way up.

  2. 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
    }
}

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles