Change background color of TextEditor in SwiftUI

Viewed 15576

TextEditor seems to have a default white background. So the following is not working and it displayed as white instead of defined red:

var body: some View {
    TextEditor(text: .constant("Placeholder"))
        .background(Color.red)
}

Is it possible to change the color to a custom one?

8 Answers

iOS 16

You should hide the default background to see your desired one:

TextEditor(text: .constant("Placeholder"))
    .scrollContentBackground(.hidden) // <- Hide it
    .background(Color.red) // To see this

iOS 15 and below

TextEditor is backed by UITextView. So you need to get rid of the UITextView's backgroundColor first and then you can set any View to the background.

struct ContentView: View {
    init() {
        UITextView.appearance().backgroundColor = .clear
    }

    var body: some View {
        List {
            TextEditor(text: .constant("Placeholder"))
                .background(Color.red)
        }
    }
}

Demo

enter image description here

You can find my simple trick for growing TextEditor here in this answer

Pure SwiftUI solution on iOS and macOS

colorMultiply is your friend.

struct ContentView: View {
    
    @State private var editingText: String = ""
    
    var body: some View {
        
        TextEditor(text: $editingText)
            .frame(width: 400, height: 100, alignment: .center)
            .cornerRadius(3.0)
            .colorMultiply(.gray)
    }
}
extension View {
/// Layers the given views behind this ``TextEditor``.
    func textEditorBackground<V>(@ViewBuilder _ content: () -> V) -> some View where V : View {
        self
            .onAppear {
                UITextView.appearance().backgroundColor = .clear
            }
            .background(content())
    }
}

Custom Background color with SwiftUI on macOS

On macOS, unfortunately, you have to fallback to AppKit and wrap NSTextView.

You need to declare a view that conforms to NSViewRepresentable

This should give you pretty much the same behaviour as SwiftUI's TextEditor-View and since the wrapped NSTextView does not draw its background, you can use the .background-ViewModifier to change the background

struct CustomizableTextEditor: View {
    @Binding var text: String
    
    var body: some View {
        GeometryReader { geometry in
            NSScrollableTextViewRepresentable(text: $text, size: geometry.size)
        }
    }
    
}

struct NSScrollableTextViewRepresentable: NSViewRepresentable {
    typealias Representable = Self
    
    // Hook this binding up with the parent View
    @Binding var text: String
    var size: CGSize
    
    // Get the UndoManager
    @Environment(\.undoManager) var undoManger
    
    // create an NSTextView
    func makeNSView(context: Context) -> NSScrollView {
        
        // create NSTextView inside NSScrollView
        let scrollView = NSTextView.scrollableTextView()
        let nsTextView = scrollView.documentView as! NSTextView
        
        // use SwiftUI Coordinator as the delegate
        nsTextView.delegate = context.coordinator
        
        // set drawsBackground to false (=> clear Background)
        // use .background-modifier later with SwiftUI-View
        nsTextView.drawsBackground = false
        
        // allow undo/redo
        nsTextView.allowsUndo = true
        
        return scrollView
    }
    
    func updateNSView(_ scrollView: NSScrollView, context: Context) {
        // get wrapped nsTextView
        guard let nsTextView = scrollView.documentView as? NSTextView else {
            return
        }
        
        // fill entire given size
        nsTextView.minSize = size
        
        // set NSTextView string from SwiftUI-Binding
        nsTextView.string = text
    }
    
    // Create Coordinator for this View
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    // Declare nested Coordinator class which conforms to NSTextViewDelegate
    class Coordinator: NSObject, NSTextViewDelegate {
        var parent: Representable // store reference to parent
        
        init(_ textEditor: Representable) {
            self.parent = textEditor
        }
        
        // delegate method to retrieve changed text
        func textDidChange(_ notification: Notification) {
            // check that Notification.name is of expected notification
            // cast Notification.object as NSTextView

            guard notification.name == NSText.didChangeNotification,
                let nsTextView = notification.object as? NSTextView else {
                return
            }
            // set SwiftUI-Binding
            parent.text = nsTextView.string
        }
        
        // Pass SwiftUI UndoManager to NSTextView
        func undoManager(for view: NSTextView) -> UndoManager? {
            parent.undoManger
        }

        // feel free to implement more delegate methods...
        
    }
    
}

Usage

ContenView: View {
    @State private var text: String

    var body: some View {
        VStack {
            Text("Enter your text here:")
            CustomizableTextEditor(text: $text)
                .background(Color.red)
        }
            .frame(minWidth: 600, minHeight: 400)

    }
}

Edit:

  • Pass reference to SwiftUI UndoManager so that default undo/redo actions are available.
  • Wrap NSTextView in NSScrollView so that it is scrollable. Set minSize property of NSTextView to enclosing SwiftUIView-Size so that it fills the entire allowed space.

Caveat: Only first line of this custom TextEditor is clickable to enable text editing.

Update iOS 16 / SwiftUI 4.0

You need to use .scrollContentBackground(.hidden) instead of UITextView.appearance().backgroundColor = .clear

https://twitter.com/StuFFmc/status/1556561422431174656

Warning: This is an iOS 16 only so you'll probably need some if #available and potentially two different TextEditor component.

You can use Mojtaba's answer (the approved answer). It works in most cases. However, if you run into this error:

"Return from initializer without initializing all stored properties"

when trying to use the init{ ... } method, try adding UITextView.appearance().backgroundColor = .clear to .onAppear{ ... } instead.

Example:

var body: some View {
    VStack(alignment: .leading) {

        ...

    }
    .onAppear {
        UITextView.appearance().backgroundColor = .clear
    }
}

This works for me on macOS

extension NSTextView {
  open override var frame: CGRect {
    didSet {
      backgroundColor = .clear
      drawsBackground = true
    }
  }
}
struct ContentView: View {
    @State var text = ""
    var body: some View {        
        TextEditor(text: $text)           
            .background(Color.red)    
    }

Reference this answer

Using the Introspect library, you can use .introspectTextView for changing the background color.

TextEditor(text: .constant("Placeholder"))
.cornerRadius(8)
.frame(height: 100)
.introspectTextView { textView in
    textView.backgroundColor = UIColor(Color.red)
}

Result

Related