- Published on
- 4 min read
> TextEditor Horizontal Padding in SwiftUI: contentMargins
Adding padding inside a TextEditor has historically been one of the trickier layout challenges in SwiftUI. While you can easily add external padding using the .padding() modifier, adding internal padding that affects the text content without shifting the scrollbar required workarounds. With iOS 17, Apple introduced the contentMargins modifier that solves this problem elegantly.
The Problem with Traditional Padding
When you apply .padding() to a TextEditor, it adds space around the entire view, including the scrollbar. This creates unwanted spacing and doesn't give you the natural text inset behavior you'd expect from a text editor.
struct ContentView: View {
@State private var text = "Lorem ipsum dolor sit amet"
var body: some View {
TextEditor(text: $text)
.padding(.horizontal, 20) // This pushes the scrollbar inward
.border(.red)
}
}
The scrollbar gets pushed inward along with the text, which doesn't look right and wastes valuable screen space.
The Old Solution: UITextView Introspection
Before iOS 17, developers had to resort to accessing the underlying UITextView to modify its textContainerInset property. This typically required creating a custom view using UIViewRepresentable or using third-party libraries to introspect the view hierarchy.
// Pre-iOS 17 workaround - not recommended
extension UITextView {
open override func didMoveToWindow() {
super.didMoveToWindow()
self.textContainerInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
}
}
This approach had several downsides:
- Required accessing UIKit internals
- Global modifications could affect all TextEditor instances
- Fragile and could break with iOS updates
- More complex code that mixed UIKit and SwiftUI paradigms
The Modern Solution: contentMargins
iOS 17 introduced the contentMargins modifier specifically designed for this use case. Unlike padding, which adds space around a view, contentMargins adds space inside scrollable containers, affecting only the content while leaving scroll indicators in their natural position.
struct ContentView: View {
@State private var text = "Lorem ipsum dolor sit amet"
var body: some View {
TextEditor(text: $text)
.contentMargins(.horizontal, 20, for: .scrollContent)
.border(.red)
}
}
Understanding the Parameters
The contentMargins modifier takes three parameters:
- Edge set - Which edges to apply margins to (
.horizontal,.vertical,.all, or specific edges like.leading) - Length - The amount of spacing in points
- Placement (optional) - Where to apply the margins:
.scrollContent- Insets only the content (recommended for most cases).scrollIndicators- Insets only the scroll indicators.automatic- System decides (may create inconsistent spacing)
Why contentMargins is Better
The contentMargins modifier offers several advantages over previous approaches:
- Pure SwiftUI - No UIKit code required
- Scoped - Only affects the specific view you apply it to
- Precise control - Separate control over content and scroll indicators
- Works with all scrollable views - Also works with ScrollView, List, and other scrollable containers
- Forward compatible - Uses Apple's recommended API
Different Edge Configurations
You can apply margins to different edges based on your needs:
// Horizontal padding only
TextEditor(text: $text)
.contentMargins(.horizontal, 20, for: .scrollContent)
// Vertical padding only
TextEditor(text: $text)
.contentMargins(.vertical, 16, for: .scrollContent)
// All sides
TextEditor(text: $text)
.contentMargins(.all, 20, for: .scrollContent)
// Specific edges
TextEditor(text: $text)
.contentMargins(.leading, 20, for: .scrollContent)
.contentMargins(.trailing, 40, for: .scrollContent)
Combining with Other Modifiers
The contentMargins modifier works well with other TextEditor customizations:
struct ContentView: View {
@State private var text = "Start typing..."
var body: some View {
TextEditor(text: $text)
.contentMargins(.horizontal, 20, for: .scrollContent)
.contentMargins(.vertical, 16, for: .scrollContent)
.scrollContentBackground(.hidden)
.background(Color(.systemGray6))
.cornerRadius(8)
.padding()
}
}
Conditional Margins Based on Device
You can apply different margins based on device type or size class to create adaptive layouts:
struct ContentView: View {
@State private var text = "Start typing..."
@Environment(\.horizontalSizeClass) var sizeClass
var horizontalMargin: CGFloat {
sizeClass == .regular ? 40 : 16
}
var body: some View {
TextEditor(text: $text)
.contentMargins(.horizontal, horizontalMargin, for: .scrollContent)
}
}
Conclusion
The contentMargins modifier is the modern, SwiftUI-native way to add internal padding to TextEditor and other scrollable views. It provides precise control over content spacing without the complexity and fragility of UIKit workarounds. If you're targeting iOS 17 or later, this should be your go-to solution for TextEditor padding.
For apps that need to support iOS 16 or earlier, you'll still need to use the UITextView introspection approach, but for new projects, contentMargins offers a clean and elegant solution to a previously frustrating problem.
// Continue_Learning
iPhone Duo Split View: Is Your Window Left or Right?
When two windows share iPhone Duo's inner display, neither one is told which side it's on. I built a proof of concept in Xcode 27.1 and found the fold's ReservedRegion answers it, once you know its frame is clipped to your window.
ReservedRegion: Keep Content Clear of the iPhone Duo Fold
iPhone Duo reserves parts of its displays for the fold and the front cameras. Learn which regions exist, when system views handle them for you, and how to query ReservedRegion in SwiftUI and UIKit to move custom content out of the way.
Building Fold-Aware Layouts with ArrangementView in SwiftUI
ArrangementView is a new iOS 27.1 container that lays out a primary and secondary view side by side or layered, and rearranges them around the iPhone Duo fold. Here's how the split and overlay styles work, how to size them, and when not to use one.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.