How to Create a Multi Line Text Field in Swiftui for MACos

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

demo

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
}
}
}

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

Sample Image

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)

Demo



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.

Handling shift+enter for newline, enter to send in TextEditor SwiftUI

The correct shortcut for new line is actually option+return and TextField supports it, e.g.

TextField("type something...", text: $text, onEditingChanged: { _ in     
print("changed")
}, onCommit: {
print("commit")
})

How to use bold and normal text in same line in SwiftUI

Here a simple example for you, with my example code you can code for iOS 13 or 14 as well:

struct ContentView: View {

var body: some View {

Text("Hello, World!") + Text(" Hello, World!").bold() + Text(" Hello, World!").italic()

}

}

output:

Sample Image

SwiftUI TextField or UITextField with tag in MacOs

Anyways, had a bit of play with this, and found a way (thanks to this StackOverflow question

Basically, I have a TextField that has a ViewModifier attached to it. Looking like this:

TextField(
"Placeholder",
text: .constant("Text")
)
.modifier(TextFieldModifier(text: "TextOverlayWithExtra"))

And the modifier:

struct TextFieldModifier: ViewModifier {

var text: String

func body(content: Content) -> some View {
content
// Clear color for the TextField
.foregroundColor(.clear)
// Overlay with text and extra
.overlay(
HStack(spacing: 0.0) {
// This Swift View splits the text and adds what I need
TextFieldHighlightedVariables(text)
Spacer()
}
.padding(.top, 2)
.padding(.leading, 4)
,
alignment: .topLeading
)
}
}

How can I put multiple lines (breaking lines) in a TextField and Text components on Swift UI?

You can use .lineLimit(nil) to your Text views. It supports multi-line text and allocates space as needed.

struct StackOverFlow : View {
var body: some View {
Text("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.")
.lineLimit(nil)
}
}

Without linelimit

With linelimit

SwiftUI: Different Widths for Multiline Text Views in Boxes

There are two issues at play here:

  1. You've got two modifiers swapped (.frame(...) and .unoBoxRoundPad()); you want the roundedness to apply to the entire stretched thing. By putting .unoBoxRoundPad() first, you're saying "pad this thing" and then "place that rounded thing inside an infinitely-wide box"; you want the reverse: your thing should be placed inside an infinitely-wide box, and it's the infinitely wide box that should have the rounded corners and padding.

  2. You need to specify an alignment: when using that .frame() modifier; when the inner view is placed inside an infinitely-wide box, it's going to default to being centered vertically and horizontally inside it. Based on your screenshots, you probably want to use .topLeading so that the content ("Support", "Help", etc) start in the top-left corner (or top-right in RTL languages).



Related Topics



Leave a reply



Submit