【问题标题】:How to add placeholder text to TextEditor in SwiftUI?如何在 SwiftUI 中向 TextEditor 添加占位符文本?
【发布时间】:2020-10-25 17:44:08
【问题描述】:

使用 SwiftUI 的新 TextEditor 时,您可以直接使用 @State 修改其内容。但是,我还没有看到添加占位符文本的方法。现在可行吗?

我添加了一个 Apple 在他们自己的翻译应用程序中使用的示例。这似乎是一个支持占位符文本的多行文本编辑器视图。

【问题讨论】:

  • 我认为现在不可能。它仍然是测试版,所以它可能会改变。
  • 我几乎不相信它会永远存在,它是 TextEditor,而不是 TextField。 UITextView 中也没有占位符。
  • @Asperi 我从 Apple 的翻译应用程序中添加了一个示例,它似乎有一个支持占位符的 TextEditor 视图。我正在努力实现同样的目标。
  • 关键字是似乎 ...看这个解决方案How do I create a multiline TextField in SwiftUI?
  • 我创建了一个反馈助手,要求在最终的 Xcode 12 版本中提供它???? (FB8118309)

标签: ios swift user-interface swiftui uikit


【解决方案1】:

我们可以创建一个自定义视图来在 TextEditor 中添加占位符文本。

这是我的解决方案:

AppTextEditor.swift

 import SwiftUI

// MARK: - AppTextEditor

struct AppTextEditor: View {

  @Binding var message: String
  let placeholder: LocalizedStringKey

  var body: some View {
    ZStack(alignment: .topLeading) {
      if message.isEmpty {
        Text(placeholder)
          .padding(8)
          .font(.body)
          .foregroundColor(Color.placeholderColor)
      }
      TextEditor(text: $message)
        .frame(height: 100)
        .opacity(message.isEmpty ? 0.25 : 1)

    }
    .overlay(
      RoundedRectangle(cornerRadius: 8)
        .stroke(Color.placeholderColor, lineWidth: 0.5))
  }
}

// MARK: - AppTextEditor_Previews

struct AppTextEditor_Previews: PreviewProvider {
  static var previews: some View {
    AppTextEditor(message: .constant(""), placeholder: "Your Message")
      .padding()
  }
}

Color+Extensions.swift

extension Color {
  static let placeholderColor = Color(UIColor.placeholderText)
}

用法:

struct YourView: View {

  @State var message = ""

  var body: some View {
    AppTextEditor(message: $message, placeholder: "Your message")
      .padding()
  }
}

【讨论】:

    【解决方案2】:

    结合@grey的回答,但是有白色背景覆盖,需要去掉背景才有效果

    struct TextArea: View {
        private let placeholder: String
        @Binding var text: String
        
        init(_ placeholder: String, text: Binding<String>) {
            self.placeholder = placeholder
            self._text = text
            // Remove the background color here
            UITextView.appearance().backgroundColor = .clear
        }
        
        var body: some View {
            TextEditor(text: $text)
                .background(
                    HStack(alignment: .top) {
                        text.isBlank ? Text(placeholder) : Text("")
                        Spacer()
                    }
                    .foregroundColor(Color.primary.opacity(0.25))
                    .padding(EdgeInsets(top: 0, leading: 4, bottom: 7, trailing: 0))
                )
        }
    }
    
    extension String {
        var isBlank: Bool {
            return allSatisfy({ $0.isWhitespace })
        }
    }
    

    【讨论】:

      【解决方案3】:

      我已经阅读了上面所有的 cmets(以及互联网上的所有内容),结合了其中的一些并决定提出这个解决方案:

      1. 创建自定义绑定包装器
      2. 使用此绑定创建 TextEditor 和 Text
      3. 添加一些修改以使所有这些像素完美。

      让我们从创建包装器开始:

           extension Binding where Value: Equatable {
      init(_ source: Binding<Value?>, replacingNilWith nilProxy: Value) {
          self.init(
              get: { source.wrappedValue ?? nilProxy },
              set: { newValue in
                  if newValue == nilProxy {
                      source.wrappedValue = nil
                  } else {
                      source.wrappedValue = newValue
                  }
              })
      }
      }
      

      下一步是像往常一样初始化我们的绑定:

      @State private var yourTextVariable: String?

      然后将 TextEditor 和 Text 放入 ZStack:

      ZStack(alignment: .topLeading) {
                  Text(YOUR_HINT_TEXT)
                      .padding(EdgeInsets(top: 6, leading: 4, bottom: 0, trailing: 0))
                      .foregroundColor(.black)
                      .opacity(yourTextVariable == nil ? 1 : 0)
      
                  TextEditor(text: Binding($yourTextVariable, replacingNilWith: ""))
                      .padding(.all, 0)
                      .opacity(yourTextVariable != nil ? 1 : 0.8)
              }
      

      这将为我们提供具有所需功能的像素级 UI:

      https://youtu.be/T1TcSWo-Mtc

      【讨论】:

        【解决方案4】:

        使用覆盖,您将无法允许用户触摸占位符文本以在 textEditor 中写入。 你最好在背景上工作,这是一个视图。

        所以,创建它,同时停用默认背景:

        struct PlaceholderBg: View {
        
        let text: String?
        
        init(text:String? = nil) {
                UITextView.appearance().backgroundColor = .clear // necessary to remove the default bg
            
            self.text = text
         }
        
        var body: some View {
            VStack {
            HStack{
            
            Text(text!)
                  
            Spacer()
            }
            Spacer()
            }
        }
            
        }
        

        然后,在您的文本编辑器中:

         TextEditor(text: $yourVariable)
                                
                                .frame(width: x, y)
                                .background(yourVariable.isEmpty ? PlaceholderBg(texte: "my placeholder text") : PlaceholderBG(texte:""))
        

        【讨论】:

          【解决方案5】:

          我喜欢 Umayanga 的方法,但他的代码不可重用。 这是可重用视图的代码:

          struct TextEditorPH: View {
              
              private var placeholder: String
              @Binding var text: String
              
              init(placeholder: String, text: Binding<String>) {
                  self.placeholder = placeholder
                  self._text = text
              }
              
              var body: some View {
                  TextEditor(text: self.$text)
                      // make the color of the placeholder gray
                      .foregroundColor(self.text == placeholder ? .gray : .primary)
                      
                      .onAppear {
                          // create placeholder
                          self.text = placeholder
          
                          // remove the placeholder text when keyboard appears
                          NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { (noti) in
                              withAnimation {
                                  if self.text == placeholder {
                                      self.text = ""
                                  }
                              }
                          }
                          
                          // put back the placeholder text if the user dismisses the keyboard without adding any text
                          NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { (noti) in
                              withAnimation {
                                  if self.text == "" {
                                      self.text = placeholder
                                  }
                              }
                          }
                      }
              }
          }
          

          【讨论】:

            【解决方案6】:

            您可以使用带有已禁用 TextEditor 的 ZStack,其中包含您的占位符文本。例如:

            ZStack {
                if self.content.isEmpty {
                        TextEditor(text:$placeholderText)
                            .font(.body)
                            .foregroundColor(.gray)
                            .disabled(true)
                            .padding()
                }
                TextEditor(text: $content)
                    .font(.body)
                    .opacity(self.content.isEmpty ? 0.25 : 1)
                    .padding()
            }
            

            【讨论】:

              【解决方案7】:

              SwiftUI TextEditor 尚不支持占位符。因此,我们不得不“伪造”它。

              其他解决方案存在对齐错误或颜色问题等问题。这是我最接近模拟真实占位符的方法。此解决方案将TextField“覆盖”在TextEditor 上。 TextField 包含占位符。只要在TextEditor 中输入字符,TextField 就会被隐藏。

              import SwiftUI
              
              struct Testing: View {
                @State private var textEditorText = ""
                @State private var textFieldText = ""
              
                var body: some View {
                  VStack {
                    Text("Testing Placeholder Example")
                    ZStack(alignment: Alignment(horizontal: .center, vertical: .top)) {
                      TextEditor(text: $textEditorText)
                        .padding(EdgeInsets(top: -7, leading: -4, bottom: -7, trailing: -4)) // fix padding not aligning with TextField
                      if textEditorText.isEmpty {
                        TextField("Placeholder text here", text: $textFieldText)
                          .disabled(true) // don't allow for it to be tapped
                      }
                    }
                  }
                }
              }
              
              struct Testing_Previews: PreviewProvider {
                static var previews: some View {
                  Testing()
                }
              }
              

              【讨论】:

                【解决方案8】:

                这里有一些很好的答案,但我想提出一个特殊情况。将 TextEditor 放置在 Form 中时,会出现一些问题,主要是间距。

                1. TextEditor 不与其他表单元素(例如 TextField)水平对齐
                2. 占位符文本未与 TextEditor 光标水平对齐。
                3. 当添加了空格或回车/换行符时,占位符重新定位到垂直中间(可选)。
                4. 添加前导空格会导致占位符消失(可选)。

                解决这些问题的一种方法:

                Form {
                    TextField("Text Field", text: $text)
                
                    ZStack(alignment: .topLeading) {
                        if comments.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                            Text("Long Text Field").foregroundColor(Color(UIColor.placeholderText)).padding(.top, 8)
                        }
                        TextEditor(text: $comments).padding(.leading, -3)
                    }
                }
                

                【讨论】:

                  【解决方案9】:

                  我构建了一个可以像这样使用的自定义视图(直到 TextEditor 正式支持它——也许明年)

                  TextArea("This is my placeholder", text: $text)
                  

                  完整解决方案如下:

                  struct TextArea: View {
                      private let placeholder: String
                      @Binding var text: String
                      
                      init(_ placeholder: String, text: Binding<String>) {
                          self.placeholder = placeholder
                          self._text = text
                      }
                      
                      var body: some View {
                          TextEditor(text: $text)
                              .background(
                                  HStack(alignment: .top) {
                                      text.isBlank ? Text(placeholder) : Text("")
                                      Spacer()
                                  }
                                  .foregroundColor(Color.primary.opacity(0.25))
                                  .padding(EdgeInsets(top: 0, leading: 4, bottom: 7, trailing: 0))
                              )
                      }
                  }
                  
                  extension String {
                      var isBlank: Bool {
                          return allSatisfy({ $0.isWhitespace })
                      }
                  }
                  

                  我在这里使用 TextEditor 的默认填充,但您可以根据自己的喜好随意调整。

                  【讨论】:

                  • 不知何故,有一个白色的平面覆盖了占位符?
                  • 仍在 iOS 14.2(明暗模式)上使用它,到目前为止没有任何问题。但是,如果您将它与其他自定义视图一起使用,您可能需要稍微更改代码以满足您的需要。随意分享您的屏幕截图和代码?
                  • 你可以使用 TextEditor 关闭键盘,类似于 TextField 的那一天是我高兴的一天。
                  【解决方案10】:

                  据我所知,这是在 SwiftUI 中向 TextEditor 添加占位符文本的最佳方式

                  struct ContentView: View {
                      @State var text = "Type here"
                      
                      var body: some View {
                  
                          TextEditor(text: self.$text)
                              // make the color of the placeholder gray
                              .foregroundColor(self.text == "Type here" ? .gray : .primary)
                              
                              .onAppear {
                  
                                  // remove the placeholder text when keyboard appears
                                  NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { (noti) in
                                      withAnimation {
                                          if self.text == "Type here" {
                                              self.text = ""
                                          }
                                      }
                                  }
                                  
                                  // put back the placeholder text if the user dismisses the keyboard without adding any text
                                  NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { (noti) in
                                      withAnimation {
                                          if self.text == "" {
                                              self.text = "Type here"
                                          }
                                      }
                                  }
                              }
                      }
                  }
                  

                  【讨论】:

                    【解决方案11】:

                    在我们获得一些 API 支持之前,可以选择使用绑定字符串作为占位符并使用 onTapGesture 将其删除

                    TextEditor(text: self.$note)
                                    .padding(.top, 20)
                                    .foregroundColor(self.note == placeholderString ? .gray : .primary)
                                    .onTapGesture {
                                        if self.note == placeholderString {
                                            self.note = ""
                                        }
                                    }
                    

                    【讨论】:

                      【解决方案12】:

                      不可能开箱即用,但您可以使用 ZStack.overlay 属性实现此效果。

                      你应该做的是检查持有你的状态的财产。如果它是empty,则显示您的占位符文本。如果不是,则显示输入的文本。

                      这是一个代码示例:

                      ZStack(alignment: .leading) {
                          if email.isEmpty {
                              Text(Translation.email)
                                  .font(.custom("Helvetica", size: 24))
                                  .padding(.all)
                          }
                          
                          TextEditor(text: $email)
                              .font(.custom("Helvetica", size: 24))
                              .padding(.all)
                      }
                      

                      注意:我特意保留了 .font 和 .padding 样式,以便您查看它应该在 TextEditor 和 Text 上匹配。

                      编辑:记住 Legolas Wang 的评论中提到的两个问题是如何处理对齐和不透明度问题:

                      • 为了使文本从视图的左侧开始,只需将其包装在 HStack 中并在其后立即附加 Spacer,如下所示:
                      HStack {
                         Text("Some placeholder text")
                         Spacer()
                      }
                      
                      • 为了解决不透明问题,您可以使用条件不透明度 - 最简单的方法是使用三元运算符,如下所示:
                      TextEditor(text: stringProperty)        
                              .opacity(stringProperty.isEmpty ? 0.25 : 1)
                      

                      当然,在添加对 TextEditors 的支持之前,此解决方案只是一种愚蠢的解决方法。

                      【讨论】:

                      • 这是一个绝妙的想法,但不幸的是它遇到了两个问题。第一个是 TextEditor 视图,它是不透明的,因此当在 ZStack 中分层时它会阻挡占位符视图。在这种情况下,调整不透明度可能会有所帮助。第二个问题是Text和TextEditor的框架逻辑,TextEditor从左上角开始,Text从视图中心开始。这使得它们很难完全覆盖在顶部。您对对齐问题有什么想法吗?
                      • @LegolasWang 我不想包含任何关于样式的超级具体内容,而是仅保留字体和填充以证明样式、对齐等应该匹配。我正在对我的答案进行编辑,以演示如何处理上述两个问题。
                      • 您实际上可以将HStack 下面 放在TextEditor 并给它一个.contentShape of NoShape: ``` struct NoShape: Shape { func path (在 rect: CGRect) -> Path { return Path() } } // ... HStack { Text("Some placeholder text") .contentShape(NoShape()) } ``
                      • 对于占位符文本颜色,您可以使用:.foregroundColor(Color(UIColor.placeholderText))
                      猜你喜欢
                      • 2023-04-08
                      • 1970-01-01
                      • 2018-12-23
                      • 2016-09-23
                      • 2014-05-17
                      • 2018-11-27
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多