Làm cách nào để tạo Trường văn bản nhiều dòng trong SwiftUI?


85

Tôi đã cố gắng tạo Trường văn bản nhiều dòng trong SwiftUI, nhưng tôi không thể tìm ra cách.

Đây là mã tôi hiện có:

struct EditorTextView : View {
    @Binding var text: String

    var body: some View {
        TextField($text)
            .lineLimit(4)
            .multilineTextAlignment(.leading)
            .frame(minWidth: 100, maxWidth: 200, minHeight: 100, maxHeight: .infinity, alignment: .topLeading)
    }
}

#if DEBUG
let sampleText = """
Very long line 1
Very long line 2
Very long line 3
Very long line 4
"""

struct EditorTextView_Previews : PreviewProvider {
    static var previews: some View {
        EditorTextView(text: .constant(sampleText))
            .previewLayout(.fixed(width: 200, height: 200))
    }
}
#endif

Nhưng đây là đầu ra:

nhập mô tả hình ảnh ở đây


1
Tôi vừa cố gắng tạo một trường văn bản nhiều dòng bằng swiftui trong Xcode Phiên bản 11.0 (11A419c), GM, sử dụng lineLimit (). Nó vẫn không hoạt động. Tôi không thể tin rằng Apple vẫn chưa sửa lỗi này. Trường văn bản nhiều dòng khá phổ biến trong Ứng dụng di động.
e987 11/09/19

Câu trả lời:


45

Cập nhật: Mặc dù Xcode11 beta 4 hiện đã hỗ trợ TextView, nhưng tôi thấy rằng gói a UITextViewvẫn là cách tốt nhất để làm cho văn bản nhiều dòng có thể chỉnh sửa hoạt động. Ví dụ: TextViewcó trục trặc hiển thị trong đó văn bản không xuất hiện đúng bên trong dạng xem.

Câu trả lời gốc (beta 1):

Hiện tại, bạn có thể bọc một UITextViewđể tạo một tác phẩm có thể kết hợp 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

nhập mô tả hình ảnh ở đây


Giải quyết tạm thời tuyệt vời! Chấp nhận cho đến khi nó có thể được giải quyết bằng SwiftUI thuần túy.
gabriellanata

7
Giải pháp này cho phép bạn hiển thị văn bản đã có các dòng mới trong đó, nhưng nó dường như không bị đứt / quấn các dòng dài tự nhiên. (Văn bản tiếp tục phát triển theo chiều ngang trên một dòng, bên ngoài khung.) Bất kỳ ý tưởng nào làm thế nào để có được các dòng dài để quấn?
Michael

5
Nếu tôi sử dụng State (thay vì EnvironmentObject với Publisher) và chuyển nó dưới dạng ràng buộc với MultilineTextView, nó có vẻ không hoạt động. Làm cách nào để phản ánh những thay đổi về trạng thái?
màu xám,

Có cách nào để đặt văn bản mặc định trong chế độ xem văn bản mà không sử dụng đối tượng môi trường không?
Learn2Code

77

Được rồi, tôi đã bắt đầu với cách tiếp cận @sas, nhưng cần nó thực sự trông & cảm thấy như trường văn bản nhiều dòng với nội dung phù hợp, v.v. Đây là những gì tôi có. Hy vọng nó sẽ hữu ích cho người khác ... Đã sử dụng Xcode 11.1.

Được cung cấp MultilineTextField tùy chỉnh có:
1. phù hợp với nội dung
2. tự động lấy nét
3. giữ chỗ
4. khi cam kết

Xem trước trường văn bản nhiều dòng swiftui với nội dung phù hợp Đã thêm trình giữ chỗ

import SwiftUI
import UIKit

fileprivate struct UITextViewWrapper: UIViewRepresentable {
    typealias UIViewType = UITextView

    @Binding var text: String
    @Binding var calculatedHeight: CGFloat
    var onDone: (() -> Void)?

    func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {
        let textField = UITextView()
        textField.delegate = context.coordinator

        textField.isEditable = true
        textField.font = UIFont.preferredFont(forTextStyle: .body)
        textField.isSelectable = true
        textField.isUserInteractionEnabled = true
        textField.isScrollEnabled = false
        textField.backgroundColor = UIColor.clear
        if nil != onDone {
            textField.returnKeyType = .done
        }

        textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        return textField
    }

    func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
        if uiView.text != self.text {
            uiView.text = self.text
        }
        if uiView.window != nil, !uiView.isFirstResponder {
            uiView.becomeFirstResponder()
        }
        UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
    }

    fileprivate static func recalculateHeight(view: UIView, result: Binding<CGFloat>) {
        let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
        if result.wrappedValue != newSize.height {
            DispatchQueue.main.async {
                result.wrappedValue = newSize.height // !! must be called asynchronously
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(text: $text, height: $calculatedHeight, onDone: onDone)
    }

    final class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>
        var calculatedHeight: Binding<CGFloat>
        var onDone: (() -> Void)?

        init(text: Binding<String>, height: Binding<CGFloat>, onDone: (() -> Void)? = nil) {
            self.text = text
            self.calculatedHeight = height
            self.onDone = onDone
        }

        func textViewDidChange(_ uiView: UITextView) {
            text.wrappedValue = uiView.text
            UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight)
        }

        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            if let onDone = self.onDone, text == "\n" {
                textView.resignFirstResponder()
                onDone()
                return false
            }
            return true
        }
    }

}

struct MultilineTextField: View {

    private var placeholder: String
    private var onCommit: (() -> Void)?

    @Binding private var text: String
    private var internalText: Binding<String> {
        Binding<String>(get: { self.text } ) {
            self.text = $0
            self.showingPlaceholder = $0.isEmpty
        }
    }

    @State private var dynamicHeight: CGFloat = 100
    @State private var showingPlaceholder = false

    init (_ placeholder: String = "", text: Binding<String>, onCommit: (() -> Void)? = nil) {
        self.placeholder = placeholder
        self.onCommit = onCommit
        self._text = text
        self._showingPlaceholder = State<Bool>(initialValue: self.text.isEmpty)
    }

    var body: some View {
        UITextViewWrapper(text: self.internalText, calculatedHeight: $dynamicHeight, onDone: onCommit)
            .frame(minHeight: dynamicHeight, maxHeight: dynamicHeight)
            .background(placeholderView, alignment: .topLeading)
    }

    var placeholderView: some View {
        Group {
            if showingPlaceholder {
                Text(placeholder).foregroundColor(.gray)
                    .padding(.leading, 4)
                    .padding(.top, 8)
            }
        }
    }
}

#if DEBUG
struct MultilineTextField_Previews: PreviewProvider {
    static var test:String = ""//some very very very long description string to be initially wider than screen"
    static var testBinding = Binding<String>(get: { test }, set: {
//        print("New value: \($0)")
        test = $0 } )

    static var previews: some View {
        VStack(alignment: .leading) {
            Text("Description:")
            MultilineTextField("Enter some text here", text: testBinding, onCommit: {
                print("Final text: \(test)")
            })
                .overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.black))
            Text("Something static here...")
            Spacer()
        }
        .padding()
    }
}
#endif

6
Ngoài ra, bạn nên nghĩ đến việc đặt backgroundColorUITextField để UIColor.clearbật nền tùy chỉnh bằng SwiftUI và về việc loại bỏ tự động trả lời đầu tiên, vì nó bị hỏng khi sử dụng nhiều MultilineTextFieldstrong một chế độ xem (mỗi lần gõ phím, tất cả các trường văn bản đều cố gắng lấy lại phản hồi).
iComputerfreak

2
@ kdion4891 Như đã giải thích trong câu trả lời này từ một câu hỏi khác , bạn chỉ có thể làm textField.textContainerInset = UIEdgeInsets.zero+ textField.textContainer.lineFragmentPadding = 0và nó hoạt động tốt 👌🏻 @Asperi Nếu bạn làm như đã đề cập, sau đó bạn sẽ cần phải xóa .padding(.leading, 4).padding(.top, 8)nếu không nó sẽ bị hỏng. Ngoài ra, bạn có thể thay đổi .foregroundColor(.gray)cho .foregroundColor(Color(UIColor.tertiaryLabel))phù hợp với màu sắc của các placeholders trong TextFields (Tôi đã không kiểm tra xem nếu đang cập nhật với chế độ tối).
Rémi B.

3
Oh, và, tôi cũng thay đổi @State private var dynamicHeight: CGFloat = 100cho @State private var dynamicHeight: CGFloat = UIFont.systemFontSizeđến sửa chữa một "trục trặc" nhỏ khi MultilineTextFieldxuất hiện (nó cho thấy lớn trong một thời gian ngắn và sau đó co lại).
Rémi B.

2
@ q8yas, bạn có thể nhận xét hoặc xóa mã liên quan đếnuiView.becomeFirstResponder
Asperi

3
Cảm ơn mọi người cho ý kiến! Tôi thực sự đánh giá cao điều đó. Ảnh chụp nhanh được cung cấp là bản trình diễn cách tiếp cận, được định cấu hình cho một mục đích cụ thể. Tất cả các đề xuất của bạn đều đúng, nhưng cho mục đích của bạn. Bạn có thể tự do sao chép-dán mã này và cấu hình lại nó bao nhiêu tùy ý cho mục đích của bạn.
Asperi

28

Điều này kết thúc UITextView trong Xcode Phiên bản 11.0 beta 6 (vẫn hoạt động ở Xcode 11 GM hạt giống 2):

import SwiftUI

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

       var body: some View {
        VStack {
            Text("text is: \(text)")
            TextView(
                text: $text
            )
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        }

       }
}

struct TextView: UIViewRepresentable {
    @Binding var text: String

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> UITextView {

        let myTextView = UITextView()
        myTextView.delegate = context.coordinator

        myTextView.font = UIFont(name: "HelveticaNeue", size: 15)
        myTextView.isScrollEnabled = true
        myTextView.isEditable = true
        myTextView.isUserInteractionEnabled = true
        myTextView.backgroundColor = UIColor(white: 0.0, alpha: 0.05)

        return myTextView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
    }

    class Coordinator : NSObject, UITextViewDelegate {

        var parent: TextView

        init(_ uiTextView: TextView) {
            self.parent = uiTextView
        }

        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            return true
        }

        func textViewDidChange(_ textView: UITextView) {
            print("text now: \(String(describing: textView.text!))")
            self.parent.text = textView.text
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

1
TextField vẫn không bị ảnh hưởng bởi lineLimit () trong Xcode Phiên bản 11.0 (11A420a), GM Seed 2, Tháng 9 năm 2019
e987

2
Điều này hoạt động tốt trong VStack, nhưng khi sử dụng Danh sách, chiều cao của hàng không mở rộng để hiển thị tất cả văn bản trong TextView. Tôi đã thử một số điều: thay đổi isScrollEnabledtrong quá trình TextViewtriển khai; thiết lập chiều rộng cố định trên khung TextView; và thậm chí đặt TextView và Text trong ZStack (với hy vọng rằng hàng sẽ mở rộng để phù hợp với chiều cao của Text view) nhưng không có gì hoạt động. Có ai có lời khuyên về cách điều chỉnh câu trả lời này để cũng hoạt động trong một Danh sách không?
MathewS

@Meo Flute ở đó để làm cho chiều cao phù hợp với nội dung.
Abdullah

Tôi đã thay đổi isScrollEnabled thành false và nó hoạt động, cảm ơn.
Abdullah

26

Với một, Text()bạn có thể đạt được điều này bằng cách sử dụng .lineLimit(nil)và tài liệu cho thấy điều này cũng sẽ hoạt động TextField(). Tuy nhiên, tôi có thể xác nhận điều này hiện không hoạt động như mong đợi.

Tôi nghi ngờ có lỗi - bạn nên gửi báo cáo với Hỗ trợ phản hồi. Tôi đã làm điều này và ID là FB6124711.

CHỈNH SỬA: Cập nhật cho iOS 14: sử dụng cái mới TextEditorthay thế.


Có cách nào để tôi có thể tìm kiếm lỗi bằng id FB6124711 không? Như tôi đang kiểm tra trên phụ tá phản hồi nhưng nó không phải là rất nhiều hữu ích
CrazyPro007

Tôi không tin rằng có một cách để làm điều đó. Nhưng bạn có thể đề cập đến ID đó trong báo cáo của mình, giải thích của bạn là một bản dupe của cùng một vấn đề. Điều này giúp nhóm phân tích nâng cao mức độ ưu tiên của vấn đề.
Andrew Ebling

2
Đã xác nhận đây vẫn là sự cố trong phiên bản Xcode 11.0 beta 2 (11M337n)
Andrew Ebling

3
Đã xác nhận rằng đây vẫn là sự cố trong phiên bản Xcode 11.0 beta 3 (11M362v). Bạn có thể đặt chuỗi thành "Some \ ntext" và chuỗi này sẽ hiển thị trên hai dòng, nhưng việc nhập nội dung mới sẽ chỉ khiến một dòng văn bản phát triển theo chiều ngang, bên ngoài khung xem của bạn.
Michael

3
Đây vẫn là một vấn đề trong Xcode 11.4 - Nghiêm túc đấy ??? Làm thế nào chúng ta phải sử dụng SwiftUI trong sản xuất với các lỗi như thế này.
Trev 14

16

iOS 14

Nó được gọi là TextEditor

struct ContentView: View {
    @State var text: String = "Multiline \ntext \nis called \nTextEditor"

    var body: some View {
        TextEditor(text: $text)
    }
}

Chiều cao phát triển năng động:

Nếu bạn muốn nó phát triển khi bạn nhập, hãy nhúng nó với một nhãn như bên dưới:

ZStack {
    TextEditor(text: $text)
    Text(text).opacity(0).padding(.all, 8) // <- This will solve the issue if it is in the same ZStack
}

Bản giới thiệu

Bản giới thiệu


iOS 13

Sử dụng Native UITextView

bạn có thể sử dụng UITextView gốc ngay trong mã SwiftUI với cấu trúc này:

struct TextView: UIViewRepresentable {
    
    typealias UIViewType = UITextView
    var configuration = { (view: UIViewType) in }
    
    func makeUIView(context: UIViewRepresentableContext<Self>) -> UIViewType {
        UIViewType()
    }
    
    func updateUIView(_ uiView: UIViewType, context: UIViewRepresentableContext<Self>) {
        configuration(uiView)
    }
}

Sử dụng

struct ContentView: View {
    var body: some View {
        TextView() {
            $0.textColor = .red
            // Any other setup you like
        }
    }
}

Ưu điểm:

  • Hỗ trợ cho iOS 13
  • Được chia sẻ với mã kế thừa
  • Đã được kiểm tra trong nhiều năm trong UIKit
  • Hoàn toàn tùy biến
  • Tất cả các lợi ích khác của bản gốc UITextView

3
Nếu bất kỳ ai đang xem câu trả lời này và tự hỏi làm thế nào để chuyển văn bản thực tế vào cấu trúc TextView thì hãy thêm dòng sau vào bên dưới dòng đặt textColor: $ 0.text = "Một số văn bản"
Mattl

1
Làm cách nào để bạn liên kết văn bản với một biến? Hoặc lấy lại văn bản?
biomiker

1
Tùy chọn đầu tiên đã có liên kết văn bản. Cái thứ hai là một tiêu chuẩn UITextView. Bạn có thể tương tác với nó như bạn thường làm trong UIKit.
Mojtaba Hosseini

12

Hiện tại, giải pháp tốt nhất là sử dụng gói này mà tôi đã tạo có tên là TextView .

Bạn có thể cài đặt nó bằng Swift Package Manager (được giải thích trong README). Nó cho phép chuyển đổi trạng thái chỉnh sửa và nhiều tùy chỉnh (cũng được trình bày chi tiết trong README).

Đây là một ví dụ:

import SwiftUI
import TextView

struct ContentView: View {
    @State var input = ""
    @State var isEditing = false

    var body: some View {
        VStack {
            Button(action: {
                self.isEditing.toggle()
            }) {
                Text("\(isEditing ? "Stop" : "Start") editing")
            }
            TextView(text: $input, isEditing: $isEditing)
        }
    }
}

Trong ví dụ đó, trước tiên bạn xác định hai @State biến. Một là dành cho văn bản mà TextView ghi vào bất cứ khi nào nó được nhập vào, và một dành cho isEditingtrạng thái của TextView.

TextView, khi được chọn, sẽ chuyển đổi isEditingtrạng thái. Khi bạn nhấp vào nút, nút này cũng sẽ chuyển đổi isEditingtrạng thái sẽ hiển thị bàn phím và chọn TextView khi nào truevà bỏ chọn TextView khi nào false.


1
Tôi sẽ thêm một vấn đề trên repo, nhưng nó có một vấn đề tương tự như giải pháp gốc của Asperi, nó hoạt động tốt trong VStack, nhưng không phải trong ScrollView.
RogerTheShrubber,

No such module 'TextView'
Alex Bartiş

Chỉnh sửa: bạn đang nhắm mục tiêu macOS nhưng khung công tác chỉ hỗ trợ UIKit vì UIViewRepresentable
Alex Bartiş

10

Câu trả lời của @Meo Flute rất hay! Nhưng nó không hoạt động đối với kiểu nhập văn bản nhiều tầng. Và kết hợp với câu trả lời của @ Asperi, đây là bản sửa lỗi cho điều đó và tôi cũng đã thêm hỗ trợ cho trình giữ chỗ chỉ cho vui!

struct TextView: UIViewRepresentable {
    var placeholder: String
    @Binding var text: String

    var minHeight: CGFloat
    @Binding var calculatedHeight: CGFloat

    init(placeholder: String, text: Binding<String>, minHeight: CGFloat, calculatedHeight: Binding<CGFloat>) {
        self.placeholder = placeholder
        self._text = text
        self.minHeight = minHeight
        self._calculatedHeight = calculatedHeight
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator

        // Decrease priority of content resistance, so content would not push external layout set in SwiftUI
        textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

        textView.isScrollEnabled = false
        textView.isEditable = true
        textView.isUserInteractionEnabled = true
        textView.backgroundColor = UIColor(white: 0.0, alpha: 0.05)

        // Set the placeholder
        textView.text = placeholder
        textView.textColor = UIColor.lightGray

        return textView
    }

    func updateUIView(_ textView: UITextView, context: Context) {
        textView.text = self.text

        recalculateHeight(view: textView)
    }

    func recalculateHeight(view: UIView) {
        let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
        if minHeight < newSize.height && $calculatedHeight.wrappedValue != newSize.height {
            DispatchQueue.main.async {
                self.$calculatedHeight.wrappedValue = newSize.height // !! must be called asynchronously
            }
        } else if minHeight >= newSize.height && $calculatedHeight.wrappedValue != minHeight {
            DispatchQueue.main.async {
                self.$calculatedHeight.wrappedValue = self.minHeight // !! must be called asynchronously
            }
        }
    }

    class Coordinator : NSObject, UITextViewDelegate {

        var parent: TextView

        init(_ uiTextView: TextView) {
            self.parent = uiTextView
        }

        func textViewDidChange(_ textView: UITextView) {
            // This is needed for multistage text input (eg. Chinese, Japanese)
            if textView.markedTextRange == nil {
                parent.text = textView.text ?? String()
                parent.recalculateHeight(view: textView)
            }
        }

        func textViewDidBeginEditing(_ textView: UITextView) {
            if textView.textColor == UIColor.lightGray {
                textView.text = nil
                textView.textColor = UIColor.black
            }
        }

        func textViewDidEndEditing(_ textView: UITextView) {
            if textView.text.isEmpty {
                textView.text = parent.placeholder
                textView.textColor = UIColor.lightGray
            }
        }
    }
}

Sử dụng nó như thế này:

struct ContentView: View {
    @State var text: String = ""
    @State var textHeight: CGFloat = 150

    var body: some View {
        ScrollView {
            TextView(placeholder: "", text: self.$text, minHeight: self.textHeight, calculatedHeight: self.$textHeight)
            .frame(minHeight: self.textHeight, maxHeight: self.textHeight)
        }
    }
}

Tôi thích điều này. Trình giữ chỗ dường như không hoạt động nhưng nó rất hữu ích để bắt đầu. Tôi đề xuất sử dụng các màu ngữ nghĩa như UIColor.tertiaryLabel thay vì UIColor.lightGray và UIColor.label thay vì UIColor.black để cả chế độ sáng và tối đều được hỗ trợ.
Helam

@Helam Bạn có phiền giải thích cách trình giữ chỗ không hoạt động không?
Daniel Tseng

@DanielTseng nó không hiển thị. Nó phải cư xử như thế nào? Tôi đã mong đợi nó hiển thị nếu văn bản trống nhưng nó không bao giờ hiển thị cho tôi.
Helam

@Helam, Trong ví dụ của tôi, tôi có trình giữ chỗ để trống. Bạn đã thử thay đổi nó thành thứ khác chưa? ("Hello World!" Thay vì "")
Daniel Tseng

Có trong tôi, tôi đặt nó là một cái gì đó khác.
Helam

2

SwiftUI TextView (UIViewRepresentable) có sẵn các thông số sau: fontStyle, isEditable, backgroundColor, borderColor & border Width

TextView (text: self. $ ViewModel.text, fontStyle: .body, isEditable: true, backgroundColor: UIColor.white, borderColor: UIColor.lightGray, borderWidth: 1.0) .padding ()

TextView (UIViewRepresentable)

struct TextView: UIViewRepresentable {

@Binding var text: String
var fontStyle: UIFont.TextStyle
var isEditable: Bool
var backgroundColor: UIColor
var borderColor: UIColor
var borderWidth: CGFloat

func makeCoordinator() -> Coordinator {
    Coordinator(self)
}

func makeUIView(context: Context) -> UITextView {

    let myTextView = UITextView()
    myTextView.delegate = context.coordinator

    myTextView.font = UIFont.preferredFont(forTextStyle: fontStyle)
    myTextView.isScrollEnabled = true
    myTextView.isEditable = isEditable
    myTextView.isUserInteractionEnabled = true
    myTextView.backgroundColor = backgroundColor
    myTextView.layer.borderColor = borderColor.cgColor
    myTextView.layer.borderWidth = borderWidth
    myTextView.layer.cornerRadius = 8
    return myTextView
}

func updateUIView(_ uiView: UITextView, context: Context) {
    uiView.text = text
}

class Coordinator : NSObject, UITextViewDelegate {

    var parent: TextView

    init(_ uiTextView: TextView) {
        self.parent = uiTextView
    }

    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        return true
    }

    func textViewDidChange(_ textView: UITextView) {
        self.parent.text = textView.text
    }
}

}


1

Có sẵn cho Xcode 12iOS14 , nó thực sự dễ dàng.

import SwiftUI

struct ContentView: View {
    
    @State private var text = "Hello world"
    
    var body: some View {
        TextEditor(text: $text)
    }
}

Điều này không phải chỉ nếu bạn đang làm việc với iOS14, điều gì sẽ xảy ra nếu người dùng vẫn đang sử dụng iOS13
Di Nerd

1

Triển khai MacOS

struct MultilineTextField: NSViewRepresentable {
    
    typealias NSViewType = NSTextView
    private let textView = NSTextView()
    @Binding var text: String
    
    func makeNSView(context: Context) -> NSTextView {
        textView.delegate = context.coordinator
        return textView
    }
    func updateNSView(_ nsView: NSTextView, context: Context) {
        nsView.string = text
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    class Coordinator: NSObject, NSTextViewDelegate {
        let parent: MultilineTextField
        init(_ textView: MultilineTextField) {
            parent = textView
        }
        func textDidChange(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else { return }
            self.parent.text = textView.string
        }
    }
}

và cách sử dụng

struct ContentView: View {

@State var someString = ""

    var body: some View {
         MultilineTextField(text: $someString)
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.