How do I create a multiline TextField in SwiftUI?
Update: While Xcode11 beta 4 now does support TextView
, I've found that wrapping a UITextView
is still be best way to get editable multiline text to work. For instance, TextView
has display glitches where text does not appear properly inside the view.
Original (beta 1) answer:
For now, you could wrap a UITextView
to create a composable View
:
import SwiftUI
import Combine
final class UserData: BindableObject {
let didChange = PassthroughSubject<UserData, Never>()
var text = "" {
didSet {
didChange.send(self)
}
}
init(text: String) {
self.text = text
}
}
struct MultilineTextView: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextView {
let view = UITextView()
view.isScrollEnabled = true
view.isEditable = true
view.isUserInteractionEnabled = true
return view
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
}
struct ContentView : View {
@State private var selection = 0
@EnvironmentObject var userData: UserData
var body: some View {
TabbedView(selection: $selection){
MultilineTextView(text: $userData.text)
.tabItemLabel(Image("first"))
.tag(0)
Text("Second View")
.font(.title)
.tabItemLabel(Image("second"))
.tag(1)
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(UserData(
text: """
Some longer text here
that spans a few lines
and runs on.
"""
))
}
}
#endif
How do I create a multiline Textfiled in SwiftUI?
Here is a 90% solution. It deletes further text after the 3rd line break, but it cannot detect line wraps in long texts.
struct ContentView: View {
@State private var notes = ""
var body: some View {
VStack (alignment: .leading) {
Text("Notes")
.fontWeight(.bold)
CustomTextEditor("Enter your note", text: $notes)
}
.padding()
}
}
struct CustomTextEditor: View {
init(_ prompt: LocalizedStringKey, text: Binding<String>) {
self.prompt = prompt
self._text = Binding(projectedValue: text)
}
let prompt: LocalizedStringKey
@Binding var text: String
var body: some View {
ZStack(alignment: .topLeading ) {
Text(prompt)
.foregroundColor(text == "" ? .secondary : .clear)
.padding(EdgeInsets(top: 7, leading: 3, bottom: 0, trailing: 0))
TextEditor(text: $text)
// deleting everything after the 3rd linebreak
.onChange(of: text) { _ in
let stringArray = text.map { $0 }
let pos = stringArray.indices.filter { stringArray[$0] == "\n"}
if pos.count > 2 {
text = String(stringArray.prefix(upTo: pos[2]))
}
}
}
.frame(height: 82)
.padding(10)
.background(Color(.systemGray5))
.cornerRadius(20)
// getting rid of TextEditor standard background
.onAppear {
UITextView.appearance().backgroundColor = .clear
}
.onDisappear {
UITextView.appearance().backgroundColor = .systemGray5
}
}
}
Make the TextField Multiline - SwiftUI
Since iOS 16
The lineLimit
modifier works as expected if you choose .vertical
value for the axis
parameter. And it also supports range
now:
TextField("Title", text: $text, axis: .vertical)
.lineLimit(5...10)
Since iOS 14
Form iOS 14 and with Xcode 12, it's available as TextEditor
struct ContentView: View {
@State var text: String = "Multiline \ntext \nis called \nTextEditor"
var body: some View {
TextEditor(text: $text)
}
}
iOS 13
Also, if you want to support iOS 13, you can take at look this answer to port UITextField
inside SwiftUI with full access to all of its properties.
How do I create a Multi Line Text Field in SwiftUI for MacOS?
Here is some initial demo of component like iOS14 TextEditor.
Demo prepared & tested with Xcode 11.7 / macOS 10.15.6
struct TestTextArea: View {
@State private var text = "Placeholder: Enter some text"
var body: some View {
VStack {
TextArea(text: $text)
.border(Color.black)
// Text(text) // uncomment to see mirror of enterred text
}.padding()
}
}
struct TextArea: NSViewRepresentable {
@Binding var text: String
func makeNSView(context: Context) -> NSScrollView {
context.coordinator.createTextViewStack()
}
func updateNSView(_ nsView: NSScrollView, context: Context) {
if let textArea = nsView.documentView as? NSTextView, textArea.string != self.text {
textArea.string = self.text
}
}
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
class Coordinator: NSObject, NSTextViewDelegate {
var text: Binding<String>
init(text: Binding<String>) {
self.text = text
}
func textView(_ textView: NSTextView, shouldChangeTextIn range: NSRange, replacementString text: String?) -> Bool {
defer {
self.text.wrappedValue = (textView.string as NSString).replacingCharacters(in: range, with: text!)
}
return true
}
fileprivate lazy var textStorage = NSTextStorage()
fileprivate lazy var layoutManager = NSLayoutManager()
fileprivate lazy var textContainer = NSTextContainer()
fileprivate lazy var textView: NSTextView = NSTextView(frame: CGRect(), textContainer: textContainer)
fileprivate lazy var scrollview = NSScrollView()
func createTextViewStack() -> NSScrollView {
let contentSize = scrollview.contentSize
textContainer.containerSize = CGSize(width: contentSize.width, height: CGFloat.greatestFiniteMagnitude)
textContainer.widthTracksTextView = true
textView.minSize = CGSize(width: 0, height: 0)
textView.maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
textView.isVerticallyResizable = true
textView.frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
textView.autoresizingMask = [.width]
textView.delegate = self
scrollview.borderType = .noBorder
scrollview.hasVerticalScroller = true
scrollview.documentView = textView
textStorage.addLayoutManager(layoutManager)
layoutManager.addTextContainer(textContainer)
return scrollview
}
}
}
backup
SwiftUI: How do I make TextField fit multi-line content?
It seems there's no direct argument to manage multiline padding correctly. They are maybe underdevelopping. But the following will give you a straight workaround solution to what you are expecting.
extension String{
var extraLines : String{ get{
return self + String(repeating:"\n", count: self.components(separatedBy: "\n").count - 1)
}}
}
struct TextFieldDemo: View {
var content: Binding<String>
init(content: Binding<String>) {
self.content = content
}
@State var height : CGFloat? //current height
let constHeightRatio : CGFloat = 0.55 //use for assembly with other fonts.
let defaultHeight : CGFloat = 250 //use for assembly with other views.
var body: some View {
TextField("Custom placeholder", text: content).environment(\.multilineTextAlignment, .center).alignmentGuide(.bottom) { (ViewDimensions) -> CGFloat in
if self.height == nil {self.height = ViewDimensions.height}
return ViewDimensions.height
}.frame( height: (height ?? defaultHeight) * constHeightRatio, alignment: .bottom).background(Color.yellow)
}
}
#if DEBUG
struct TextInputRowPreviews: PreviewProvider {
static var previews: some View {
let content = "content\ncontent\ncontent".extraLines
return
TextFieldDemo(content: .constant(content))
}
}
#endif
This works fine for single view. If view assembly is required (with other stacking views, etc), you may adjust defaultHeight
and/or constHeightRatio
to achieve what you want. Hopefully it works for you too.
Related Topics
Fatal Error: Unexpectedly Found Nil While Unwrapping an Optional Values
Objective-C Arc: Strong VS Retain and Weak VS Assign
Uiscrollview Scrollable Content Size Ambiguity
Programmatically Set the Initial View Controller Using Storyboards
How to Localise a String Inside the iOS Info.Plist File
How to Create a Uicolor from a Hex String
Iphone: Detecting User Inactivity/Idle Time Since Last Screen Touch
How to Convert String Date to Nsdate
Move View With Keyboard Using Swift
Is [Uiscreen Mainscreen].Bounds.Size Becoming Orientation-Dependent in Ios8
How to Use Scnetworkreachability in Swift
Change User Agent in Uiwebview
Iphone 6 Plus Resolution Confusion: Xcode or Apple'S Website? For Development
Setting Action For Back Button in Navigation Controller
Objective-C and Swift Url Encoding