Learn Courses My Dashboard

TextEditor is blocked by keyboard

When I type into a TextEditor, if the number of lines brings the text below the keyboard, you can’t see the text that’s being typed:

Is there a way to get the text to scroll instead. I tried setting LineLimit, but it was ignored.

        Text("Notes:")
          .font(.caption)
          .padding(.top)
        TextEditor(text: $notes)
          .lineLimit(4)

Thanks in advance.

Try adding the modifier
.ignoresSafeArea(.keyboard)

That had no effect. My search found lots of info about a similar problem with TextField, but nothing relating to TextEditor.

This works if you are interested.

struct ContentView: View {
    @State private var text = ""

    var body: some View {
        NavigationView {
            GeometryReader { gr in
                VStack {
                    TextEditor(text: $text)
                        .padding()
                }
                .frame(width: gr.size.width, height: gr.size.height)
                .navigationTitle("Text Editor")
            }
        }
    }
}

The GeometryReader adjusts the VStack frame height when the keyboard appears.

I tried this:

        GeometryReader
        { geo in
          VStack
          {
            TextEditor(text: $notes)
              .padding()
          }
          .frame(width: geo.size.width, height: geo.size.height)
        }

The result is that I can’t get the cursor into the TextEditor. If I comment out GeometryReader, it’s back the way it was. Also, .navigationTitle makes my Cancel button on the top left go away.

Did I miss something?

If you have other elements in the View then that will further complicate how GeometryReader reacts for that specific VStack View due to space requirements the other Views need. Share your code for that View otherwise it’s pretty hard to suggest what you might need to do.

OK, here’s the whole thing:

struct AddView: View
{
  @State private var name = ""
  @State private var expirationDate = Date()
  @State private var price = ""
  @State private var notes = ""
  
  @State private var addingItem = false    // to prevent flashing of duplicate item message
  @ObservedObject var periodicals: Periodicals
  @Environment(\.presentationMode) var presentationMode

  var trimmedName: String   // name with leading and trailing whitespace removed
  { name.trimmingCharacters(in: .whitespacesAndNewlines) }

  var trimmedPrice: String   // price with leading and trailing whitespace removed
  { price.trimmingCharacters(in: .whitespacesAndNewlines) }

  var duplicateItem: Bool
  { periodicals.itemIndex(name: trimmedName) != -1 }

  // The form is complete when:
  // 1) a name has been entered and
  // 2) if a price is entered, it is a valid, non-negative double.

  var formIsComplete: Bool
  {
    let price = Double(trimmedPrice) ?? -1
    return trimmedName != "" && (price >= 0 || trimmedPrice == "")
  }

  var body: some View
  {
    NavigationView
    {
      Form
      {
        TextField("Name", text: $name)

        if duplicateItem && !addingItem
        {
          // Warn if the name is the same as an existing item.
          Text("This name already exists.")
            .foregroundColor(.red)
            .font(.callout)
        }

        DatePicker("Due Date:",
                   selection: $expirationDate, in: Date()...,
                   displayedComponents: .date)

        HStack()
        {
          Button("Today")
          { expirationDate = Date() }
          .buttonStyle(DateButtonStyle())

          Button("+ 1 month")
          {
            expirationDate = Calendar.current.date(byAdding: .month,
                                                   value: 1,
                                                   to: expirationDate )!
          }
          .buttonStyle(DateButtonStyle())

          Button("+ 1 year")
          { expirationDate = Calendar.current.date(byAdding: .year,
                                                   value: 1,
                                                   to: expirationDate )!
          }
          .buttonStyle(DateButtonStyle())

          Spacer()
        }


        HStack
        {
          Text("Price: $")
          TextField("", text: $price)
            .keyboardType(.decimalPad)
        }

        Text("Notes:")
          .font(.caption)
          .padding(.top)

        GeometryReader
        { geo in
          VStack
          {
            TextEditor(text: $notes)
              .padding()
          }
          .frame(width: geo.size.width, height: geo.size.height)
        }
      }
      .navigationBarTitle("Add new bill or subscription", displayMode: .inline)
      .navigationBarItems(leading: Button("Cancel")   // Cancel - do not save data
      {
        self.presentationMode.wrappedValue.dismiss()
      },
      trailing: Button("Save", action: saveItem)      // Save - save the data
        .disabled(!formIsComplete))
    }
  }

  // Add the new item to the list.

  func saveItem()
  {
    let actualPrice = Double(trimmedPrice)
    let item = Periodical(name: self.trimmedName,
                          expirationDate: self.expirationDate,
                          price: actualPrice,
                          notes: notes)

    // Add the item to the list.
    addingItem = true
    self.periodicals.items.append(item)
    self.periodicals.items.sort()
    { $0.expirationDate < $1.expirationDate }
    self.presentationMode.wrappedValue.dismiss()
  }
}

Hmmmm, can’t make that work at all. Sorry Peter.

You may have to do it by presenting a separate View to capture notes. Maybe have an AddNotes button and have the notes field connected by Binding to it in the presented view. Just a thought.

Thanks for trying, Chris. I’m going to consider it an Xcode bug. I reported it to Apple. We’ll see what happens.

Looks like I have a solution. I simply added .frame(height: 100) to the TextEditor. Now, when you type past the lower edge of the field, the data scrolls up to leave room for the next line.