Open Source · Swift Package
BetterSwiftUITextEditor
A drop-in replacement for SwiftUI’s TextEditor with placeholders, character limits, dynamic height, and custom return-key handling.
The Problem
SwiftUI’s TextEditor ships without placeholder text, character limits, height control, or a way to make Return submit instead of inserting a newline.
Every chat input, comment box, and form that needs these features ends up with the same boilerplate workarounds. I hit that while building ClarifyLM’s chat input and decided to solve it once.
The Solution
A single-file, zero-dependency Swift Package that wraps TextEditor with the missing functionality. Drop it in anywhere TextEditor works, including chat inputs, post composers, notes, and forms, then configure it through SwiftUI-native environment modifiers.
The package is used in production in ClarifyLM and available as open source for other developers via Swift Package Manager.
Placeholder Text
Native placeholder support that disappears when the user starts typing. Color is configurable through environment values.
Character Limit & Count
Maximum character count enforced at the binding level, with an optional visible counter and over-limit styling.
Dynamic Height
Editor grows vertically with content. Optional max height enables scrolling when content overflows.
Return Key Handling
Return submits. Shift+Return inserts a newline. AppKit key monitor on macOS, native keyboard behavior on iOS.
Usage
import BetterSwiftUITextEditor
BetterEditor(
text: $text,
placeholder: "Type something...",
numberOfLines: $numberOfLines
)
.padding()
.background(Color.gray.opacity(0.1))
.clipShape(RoundedRectangle(cornerRadius: 8))
Engineering
Platform-specific keyboard handling. On macOS, MacOSKeyHandler, an NSViewRepresentable, installs a local NSEvent monitor to intercept Return for submit versus Shift+Return for a newline. The monitor is added in viewDidMoveToWindow and cleaned up in dismantleNSView to prevent leaks. On iOS, the system keyboard handles this natively.
Dynamic height measurement. The editor uses fixedSize(horizontal: false, vertical: true) combined with GeometryReader to measure content height and report line count through a binding. When content exceeds maxHeight, it switches to scrolling.
Placeholder alignment. A hidden “A” text provides consistent line-height measurement, then the placeholder renders as a ZStack overlay. This ensures alignment across both platforms, with platform-specific padding offsets for iOS and macOS.
Environment-driven configuration. All customization flows through custom SwiftUI environment keys. Modifiers like .betterEditorPlaceholderColor() and .betterEditorOnSubmit() set environment values that the editor reads at render time, keeping the API fully declarative.
Technologies
SwiftUI
Primary framework for the editor view and layout.
AppKit
NSEvent monitor for macOS return-key interception.
NSViewRepresentable
Bridge for AppKit key handler in SwiftUI views.
GeometryReader
Content height measurement and line counting.
Environment Values
Custom keys for declarative theming and configuration.
SPM
Distributed as a zero-dependency Swift Package.