78

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?

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
Lorenzo Fiamingo
  • 3,251
  • 2
  • 17
  • 35
  • 3
    On macOS, for me it worked the solution from [this](https://stackoverflow.com/a/65865881/14728668) accepted answer. – Em. Aug 28 '21 at 21:39

13 Answers13

172

iOS 16

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

TextEditor(text: .constant("Placeholder"))
    .scrollContentBackground(.hidden) // <- Hide it
    .background(.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(.red)
        }
    }
}

Demo

enter image description here

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

Raj
  • 5,895
  • 4
  • 27
  • 48
Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
  • 9
    To extend this question/answer - because SwiftUI also supports macOS apps, how would you do this same thing with a `TextEditor` based on `NSTextView`? – robotsquidward Jul 19 '20 at 19:53
  • 17
    Genius, if only the folks at Apple could have done that for us! – aeskreis Dec 18 '20 at 17:59
  • 1
    Agree, I hope apple fix that soon but there will be no backward compatibility as always. So we should stick to this for a while unfortunately. @AntonínKarásek . – Mojtaba Hosseini Jan 08 '21 at 10:06
  • to answer with NSTextVIew on OS X, seems you dont. Been trying to do this for 2 days. Apple is so great setting a background color is advanced technology. – Jimmy Hough Jr Feb 17 '21 at 14:11
  • 5
    Am I the only one to see the danger of using `UITextView.appearance()`, which changes the configuration of `UITextView` for the whole app, as workaround? – Daniel Feb 04 '22 at 12:40
  • If for whatever reason you can't do `UITextView.appearance().backgroundColor = .clear` in init(), inside onAppear() works for me on iOS 15. – maz Apr 02 '22 at 22:57
  • To add to @maz, this does not work in `.task()`, only in `.onAppear()`. – Mykel May 06 '22 at 06:20
  • 2
    This does not seem to work in iOS 16 beta 4. – Lim Thye Chean Aug 04 '22 at 04:55
  • @LimThyeChean I have added the working method for iOS 16 – Mojtaba Hosseini Aug 04 '22 at 13:14
  • Doesn't work for me with `textFieldStyle(.roundedBorder)` on iOS 16. Any ideas how to fix it? – Agost Biro Dec 18 '22 at 15:44
  • Is this working for you in dark mode on iPad? Cause for me the exact same code on iPhone is working, but on iPad and in dark mode, the background of the TextEditor is still black (.systemBackground) – Georg Jan 19 '23 at 16:32
  • I had a ZStack with Text (for placeholder functionality) and a TextEditor and even though the Text was visible (not the TextEditor) it's background was still leaking. .scrollContentBackground(.hidden) did the job and fixed it. Thanks. – C0D3 Aug 18 '23 at 00:58
17

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)
    }
}
Marc T.
  • 5,090
  • 1
  • 23
  • 40
10

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.

StuFF mc
  • 4,137
  • 2
  • 33
  • 32
6
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())
    }
}
Rebeloper
  • 813
  • 10
  • 22
6

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

voorjaar
  • 101
  • 1
  • 4
6

As many have stated, with iOS 16 you need to use scrollContentBackground. I created an extension method to handle both cases:

struct ContentView: View {
    @State private var editingText: String = ""
    
    var body: some View {
        TextEditor(text: $editingText)
            .transparentScrolling()
            .background(Color.red)
    }
}

public extension View {
    func transparentScrolling() -> some View {
        if #available(iOS 16.0, *) {
            return scrollContentBackground(.hidden)
        } else {
            return onAppear {
                UITextView.appearance().backgroundColor = .clear
            }
        }
    }
}
Niklas
  • 23,674
  • 33
  • 131
  • 170
5

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.

tillhain
  • 51
  • 1
  • 2
  • I voted you up since it works with macOS as well but there is far too much overhead – Marc T. Apr 16 '21 at 05:43
  • Yes, it's a lot of code for simply changing the background color, but a fun exercise though. I tried your solution, but `.colorMultiply()` actually blends all colours together (so e.g. if you multiply by Color.clear you won't see anything). Or I possibly missed something, but your provide code is not working for me – tillhain Apr 18 '21 at 18:15
  • This one works well on macOS and is small and simple: https://stackoverflow.com/a/65865881/345258 – Daniel Jan 03 '22 at 22:19
  • Superb! Thanks for this! – Glenn Posadas Jan 07 '22 at 15:39
5

To achieve this visual design here is the code I used.

enter image description here

iOS 16

TextField(
    "free_form",
    text: $comment,
    prompt: Text("Type your feedback..."),
    axis: .vertical
)
.lineSpacing(10.0)
.lineLimit(10...)
.padding(16)
.background(Color.themeSeashell)
.cornerRadius(16)

iOS 15

ZStack(alignment: .topLeading) {
    RoundedRectangle(cornerRadius: 16)
        .foregroundColor(.gray)
    
    TextEditor(text: $comment)
        .padding()
        .focused($isFocused)
    
    if !isFocused {
        Text("Type your feedback...")
            .padding()
    }
}
.frame(height: 132)
.onAppear() {
    UITextView.appearance().backgroundColor = .clear
}
Braxton Ward
  • 61
  • 1
  • 5
2

Ios 16
Make sure to add .scrollContentBackground(.hidden) this lines then background will apply

TextEditor(text:.constant(hint))
.scrollContentBackground(.hidden)
.background(.red) // To see this

This work for me

1

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

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
1

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
    }
}
0
import SwiftUI

struct AddCommentView: View {
    
    init() {
        UITextView.appearance().backgroundColor = .clear
    }
    
    var body: some View {
           VStack {
                    if #available(iOS 16.0, *) {
                        TextEditor(text: $viewModel.commentText)
                            .scrollContentBackground(.hidden)
                    } else {
                        TextEditor(text: $viewModel.commentText)
                    }
                }
                .background(Color.blue)
                .frame(height: UIScreen.main.bounds.width / 2)
                .overlay(
                    RoundedRectangle(cornerRadius: 5)
                        .stroke(Color.red, lineWidth: 1)
                )
    }
}
Mohammad Razipour
  • 3,643
  • 3
  • 29
  • 49
0

It appears the UITextView.appearance().backgroundColor = .clear trick in IOS 16,

only works for the first time you open the view and the effect disappear when the second time it loads.

So we need to provide both ways in the app. Answer from StuFF mc works.

 var body: some View {
    if #available(iOS 16.0, *) {
        mainView.scrollContentBackground(.hidden)
    } else {
        mainView.onAppear {
            UITextView.appearance().backgroundColor = .clear
        }
    }
}

// rename body to mainView
var mainView: some View {
    TextEditor(text: $notes).background(Color.red)              
}
Qiquan Lu
  • 615
  • 1
  • 5
  • 10