PDFAnnotation not rendered properly on iOS27

I'm developing an iPad app where I want to annotate things on a pdf. I subclassed PDFAnnotation to drag text on a pdf. When I use Xcode 26 everything is working fine and everything is rendered perfect. But when I run the same code with Xcode 27 on iOS27 I get a pixelated annotation.

iOS26

iOS27

The code:

import UIKit

final class ManualPlacementPreviewAnnotation: PDFAnnotation, DraggablePreviewAnnotation {
    private let text: String
    private let textFont: UIFont
    private let textColor: UIColor
    var isGrabbed = false

    init(bounds: CGRect, text: String, font: UIFont, color: UIColor) {
        self.text = text
        self.textFont = font
        self.textColor = color
        super.init(
            bounds: bounds.insetBy(dx: -PreviewAnnotationChrome.padding, dy: -PreviewAnnotationChrome.padding),
            forType: .freeText,
            withProperties: nil
        )
    }

    required init?(coder: NSCoder) {
        text = ""
        textFont = .systemFont(ofSize: 12)
        textColor = .black
        super.init(coder: coder)
    }

    override func draw(with box: PDFDisplayBox, in context: CGContext) {
        context.saveGState()

        context.translateBy(x: 0, y: bounds.minY + bounds.maxY)
        context.scaleBy(x: 1, y: -1)

        let content = contentBounds

        UIGraphicsPushContext(context)
        NSAttributedString(string: text, attributes: [
            .font: textFont,
            .foregroundColor: textColor,
        ]).draw(in: content)
        UIGraphicsPopContext()

        PreviewAnnotationChrome.draw(context, around: content, grabbed: isGrabbed)

        context.restoreGState()
    }
}

Is this a bug in iOS27 or am I doing something wrong?

I'm seeing the exact same regression. My custom PDFAnnotation subclass overrides draw(with:in:) to draw a hand-drawn vector stroke (UIBezierPath) plus a CGImage icon via context.draw(_:in:). Same code renders perfectly sharp on iOS 26 and earlier, but on iOS 27 it's visibly blurry/pixelated in PDFView — even at default zoom, no pinch needed.

I'm having exactly the same problem.

The problem you're seeing is a problem with PDFKit introduced with iOS/iPadOS/macOS 27.0.

The same thing occurs with widget annotations as well: PDF Widget Annotations appear Pixelated/Rasterized

We hit the same regression and traced it. Filed as FB24882188, with a small repro project.

What is happening: on iOS/iPadOS/macOS 27, PDFKit draws annotations into a private layer, PDFPageLayerAnnotationEffect, and configures it with contentsScale 1.0 and magnificationFilter "nearest". The PDFPageLayerTile layers that draw the page right beside it get the real on-screen scale (3.86 in our repro) and "linear". So annotations are rasterized at 1 pixel per point and then stretched with nearest-neighbour sampling, which is the pixelation. On iOS 26.5 the same layer gets the tile scale and "linear", which is why the same build is crisp there.

It is not caused by custom draw(with:in:) code: standard ink and free-text annotations show it too, and widgets as well (FB24843022, same defect).

Setting those two values on the live layer fixes it immediately, and PDFKit's next draw then uses the new scale. PDFKit resets the layer on every redraw, though, so the correction has to run right before each one. We do that by replacing -display on that one class (never on CALayer itself):

import UIKit

// iOS/iPadOS/macOS 27 workaround for pixelated PDFKit annotations (FB24882188, FB24843022).
// Call once at launch, before any PDFView draws. No-op if the private class is absent.
func installAnnotationLayerFix() {
    guard #available(iOS 27.0, *),
          let cls = NSClassFromString("PDFPageLayerAnnotationEffect"),
          let method = class_getInstanceMethod(cls, #selector(CALayer.display)) else { return }
    typealias Display = @convention(c) (CALayer, Selector) -> Void
    let original = unsafeBitCast(method_getImplementation(method), to: Display.self)
    let block: @convention(block) (CALayer) -> Void = { layer in
        if Thread.isMainThread {
            MainActor.assumeIsolated { correctScale(of: layer) }
        }
        original(layer, #selector(CALayer.display))
    }
    // Adds an override on the subclass only; CALayer itself is untouched.
    class_replaceMethod(cls, #selector(CALayer.display),
                        imp_implementationWithBlock(block), method_getTypeEncoding(method))
}

/// Gives the layer its real on-screen density (screen scale x its magnification in the window)
/// and linear filtering, which is what PDFKit gives the page tiles beside it.
@MainActor
private func correctScale(of layer: CALayer) {
    guard let window = hostView(of: layer)?.window else { return }
    let bounds = layer.bounds
    guard bounds.width > 0, bounds.height > 0 else { return }
    let inWindow = layer.convert(bounds, to: window.layer)
    let magnification = sqrt((inWindow.width * inWindow.height) / (bounds.width * bounds.height))
    let scale = magnification * window.traitCollection.displayScale
    guard scale.isFinite, scale > 0 else { return }
    layer.contentsScale = scale
    layer.magnificationFilter = .linear
}

@MainActor
private func hostView(of layer: CALayer) -> UIView? {
    var current = layer.superlayer
    while let l = current {
        if let view = l.delegate as? UIView { return view }
        current = l.superlayer
    }
    return nil
}

It depends on a private class name, so treat it as a stopgap until Apple fixes the regression; if the class is renamed or removed, it silently does nothing. In our app it fixed everything on iPadOS 27 and Mac Catalyst (macOS 27): new annotations, annotations loaded from saved files, zooming, page changes, rotation. It compiles under both Swift 5 and Swift 6 language modes.

If you're affected, please file your own report and mention FB24882188 and FB24843022 in it, so they get linked on Apple's side.

PDFAnnotation not rendered properly on iOS27
 
 
Q