iOS 27 introduces NSTextViewportRenderingSurface and NSTextViewportRenderingSurfaceKey protocols, and makes UITextView/NSTextView conform to NSTextViewportLayoutControllerDelegate — enabling developers to override viewport layout lifecycle methods to build rich extensions like line-number gutters without leaving the safety of framework text views.
• UITextView and NSTextView now conform to NSTextViewportLayoutControllerDelegate, exposing willLayout, configureRenderingSurface, and didLayout as overridable methods in subclasses — this was not possible before iOS 27.
• New NSTextViewportRenderingSurface protocol provides a unified abstraction for the visual elements (UIView, NSView, CALayer) that render layout fragments inside the viewport.
• New NSTextViewportRenderingSurfaceKey protocol allows layout fragments and other objects to serve as stable cache keys mapping to rendering surfaces across layout cycles.
• renderingSurfaceFor(_:) method on NSTextViewportLayoutController lets you query the rendering surface for a given key after layout completes.
• UITextView and NSTextView now expose viewport layout delegate methods via subclassing, so you can react to every layout cycle (scroll, edit, selection) without building a fully custom text view from scratch.
• NSTextViewportRenderingSurface gives you a unified protocol to track and cache the views/layers that render each layout fragment, making custom decorations (gutters, highlights, overlays) far easier to implement.
• These APIs close the long-standing gap between framework-text-view convenience and custom-text-view control, eliminating the need to reimplement text input, accessibility, undo/redo, and dictation just to add layout-aware UI.
Shows how to subclass UITextView in iOS 27, override the new viewport layout delegate methods, and render paragraph line numbers in a side gutter by reading NSTextLayoutFragment bounds during each layout cycle.
import UIKit
import UniformTypeIdentifiers
// MARK: - Line-number-aware UITextView subclass (iOS 27+)
final class CodeTextView: UITextView {
// Callback fired after every viewport layout cycle with
// a mapping of line-index -> fragment frame (in text-view coords)
var onViewportDidLayout: (([Int: CGRect]) -> Void)?
// Accumulated during configureRenderingSurface overrides
private var pendingLineFrames: [Int: CGRect] = [:]
// MARK: NSTextViewportLayoutControllerDelegate overrides (new in iOS 27)
override func textViewportLayoutControllerWillLayout(
_ controller: NSTextViewportLayoutController
) {
// Clear state at the start of each layout pass
pendingLineFrames.removeAll()
}
override func textViewportLayoutController(
_ controller: NSTextViewportLayoutController,
configureRenderingSurface renderingSurface: any NSTextViewportRenderingSurface,
for textLayoutFragment: NSTextLayoutFragment
) {
// Resolve which paragraph index this fragment belongs to
guard let contentManager = textLayoutManager?.textContentManager else { return }
var paragraphIndex = 0
contentManager.enumerateTextElements(from: nil) { element in
if element === textLayoutFragment.textElement { return false }
paragraphIndex += 1
return true
}
// Convert fragment frame from text-layout coordinates to view coordinates
let fragmentFrameInView = CGRect(
origin: CGPoint(
x: textLayoutFragment.layoutFragmentFrame.minX + textContainerInset.left,
y: textLayoutFragment.layoutFragmentFrame.minY + textContainerInset.top
),
size: textLayoutFragment.layoutFragmentFrame.size
)
pendingLineFrames[paragraphIndex] = fragmentFrameInView
}
override func textViewportLayoutControllerDidLayout(
_ controller: NSTextViewportLayoutController
) {
// Notify the container view so it can redraw the gutter
onViewportDidLayout?(pendingLineFrames)
}
}
// MARK: - Container view wiring up the gutter
final class CodeEditorContainerView: UIView {
private let gutterWidth: CGFloat = 44
private let gutterView = UIView()
private var lineNumberLabels: [UILabel] = []
private let codeTextView = CodeTextView()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder: NSCoder) { fatalError() }
private func setupViews() {
gutterView.backgroundColor = UIColor.systemGray6
addSubview(gutterView)
addSubview(codeTextView)
codeTextView.font = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular)
codeTextView.autocorrectionType = .no
codeTextView.autocapitalizationType = .none
codeTextView.textContainerInset = UIEdgeInsets(
top: 8, left: gutterWidth + 8, bottom: 8, right: 8
)
codeTextView.text = "func hello() {\n print(\"Hello, TextKit!\")\n}\n"
// iOS 27: react to viewport layout cycles
codeTextView.onViewportDidLayout = { [weak self] lineFrames in
self?.updateGutter(with: lineFrames)
}
}
override func layoutSubviews() {
super.layoutSubviews()
gutterView.frame = CGRect(x: 0, y: 0, width: gutterWidth, height: bounds.height)
codeTextView.frame = bounds
}
private func updateGutter(with lineFrames: [Int: CGRect]) {
lineNumberLabels.forEach { $0.removeFromSuperview() }
lineNumberLabels.removeAll()
for (index, frame) in lineFrames.sorted(by: { $0.key < $1.key }) {
let label = UILabel()
label.font = UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)
label.textColor = .secondaryLabel
label.textAlignment = .right
label.text = "\(index + 1)"
label.frame = CGRect(
x: 4,
y: frame.minY - codeTextView.contentOffset.y,
width: gutterWidth - 8,
height: frame.height
)
gutterView.addSubview(label)
lineNumberLabels.append(label)
}
}
}Liquid Glass is Apple's new material and visual design language introduced in iOS 27, bringing translucent, refractive glass-like surfaces to system and custom UI elements. It replaces the frosted vibrancy aesthetic with a more dynamic, depth-aware material that responds to content beneath it.
PaperKit is Apple's full-featured canvas framework — previously internal-only — now publicly available in iOS/macOS/visionOS 27. It powers the drawing and markup experience in Notes, Preview, and Freeform, giving developers access to a complete pencil, shapes, images, and text canvas with a rich data model.
Xcode 27 introduces a fully customizable toolbar and theme system, untitled scratch projects, coding agent integration directly in the editor, and the new Device Hub for evaluating apps across simulators and physical devices side-by-side.
In-depth guide
SwiftUI & Liquid Glass in iOS 27 →UITextView/NSTextView delegate methods for the viewport controller are only accessible via subclassing and overriding — you cannot assign an external delegate object. Rendering surfaces returned from renderingSurfaceFor are cleared at the start of each viewport layout cycle, so always re-assign them in configureRenderingSurface. The NSTextViewportRenderingSurface protocol requires UIView, NSView, or CALayer conformance; arbitrary objects are not supported.
None — available on all devices running iOS 27 or macOS 2027 releases.
iOS 27 introduces new SwiftUI drag and drop APIs including reorderable, reorderContainer, dragContainer, and configuration modifiers that enable reordering within and across collections, multi-item drag, and fine-grained control over how data is transferred during drag and drop operations.