Jak utworzyć wielowierszowe pole tekstowe w SwiftUI?

85

Próbowałem utworzyć wielowierszowe pole tekstowe w SwiftUI, ale nie wiem, jak to zrobić.

Oto kod, który obecnie posiadam:

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

Ale to jest wynik:

wprowadź opis obrazu tutaj

gabriellanata
źródło
1
Właśnie próbowałem utworzyć wielowierszowe pole tekstowe za pomocą swiftui w Xcode w wersji 11.0 (11A419c), GM, używając lineLimit (). Nadal nie działa. Nie mogę uwierzyć, że Apple jeszcze tego nie naprawiło. Wielowierszowe pole tekstowe jest dość powszechne w aplikacjach mobilnych.
e987

Odpowiedzi:

45

Aktualizacja: Chociaż Xcode11 beta 4 teraz obsługuje TextView, odkryłem, że zawijanie a UITextViewjest nadal najlepszym sposobem na uruchomienie edytowalnego tekstu wielowierszowego. Na przykład TextViewma błędy wyświetlania, w których tekst nie jest prawidłowo wyświetlany w widoku.

Oryginalna (beta 1) odpowiedź:

Na razie możesz opakować a, UITextViewaby utworzyć kompozycję 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

wprowadź opis obrazu tutaj

sas
źródło
Świetne tymczasowe rozwiązanie! Zaakceptuj na razie, dopóki nie będzie można go rozwiązać za pomocą czystego SwiftUI.
gabriellanata
7
To rozwiązanie umożliwia wyświetlanie tekstu, który już zawiera znaki nowej linii, ale nie wydaje się, aby naturalnie łamał / zawijał długie linie. (Tekst po prostu rośnie poziomo w jednej linii, poza ramką). Jakieś pomysły na zawijanie długich linii?
Michael,
5
Jeśli używam State (zamiast EnvironmentObject z wydawcą) i przekażę go jako powiązanie do MultilineTextView, wydaje się, że nie działa. Jak mogę odzwierciedlić zmiany z powrotem do stanu?
szary
Czy istnieje sposób, aby ustawić domyślny tekst w widoku tekstu bez użycia environmentObject?
Learn2Code
77

Ok, zacząłem od podejścia @sas, ale potrzebowałem, żeby naprawdę wyglądało i działało jak wielowierszowe pole tekstowe z dopasowaną zawartością itp. Oto, co mam. Mam nadzieję, że będzie to pomocne dla kogoś innego ... Użyłem Xcode 11.1.

Pod warunkiem, że niestandardowe MultilineTextField ma:
1. dopasowanie treści
2. autofokus
3. symbol zastępczy
4. po zatwierdzeniu

Podgląd wielowierszowego pola tekstowego swiftui z dopasowaną zawartością Dodano symbol zastępczy

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
Asperi
źródło
6
Powinieneś także pomyśleć o ustawieniu backgroundColorUITextField, aby UIColor.clearwłączyć niestandardowe tła za pomocą SwiftUI i o usunięciu auto-pierwszej korespondencji, ponieważ psuje się, gdy używasz wielu MultilineTextFieldsw jednym widoku (każde naciśnięcie klawisza, wszystkie pola tekstowe próbują ponownie uzyskać odpowiedź).
iComputerfreak
2
@ kdion4891 Jak wyjaśniono w tej odpowiedzi z innym pytaniem , można po prostu zrobić textField.textContainerInset = UIEdgeInsets.zero+ textField.textContainer.lineFragmentPadding = 0i działa dobrze 👌🏻 @Asperi Jeśli tak jak wspomniano, będziesz musiał usunąć .padding(.leading, 4)i .padding(.top, 8)inaczej będzie wyglądać złamany. Możesz także zmienić .foregroundColor(.gray)na, .foregroundColor(Color(UIColor.tertiaryLabel))aby dopasować kolor symboli zastępczych w TextFields (nie sprawdzałem, czy aktualizuje się w trybie ciemnym).
Rémi B.
3
Och, a ja również zmienić @State private var dynamicHeight: CGFloat = 100za @State private var dynamicHeight: CGFloat = UIFont.systemFontSizenaprawić mały „usterki”, gdy MultilineTextFieldpojawi się (to pokazuje duży na krótki czas, a następnie kurczy się).
Rémi B.
2
@ q8yas, możesz skomentować lub usunąć kod związany zuiView.becomeFirstResponder
Asperi
3
Dziękuję wszystkim za komentarze! Naprawdę to doceniam. Dostarczona migawka jest demonstracją podejścia, które zostało skonfigurowane w określonym celu. Wszystkie twoje propozycje są poprawne, ale do twoich celów. Możesz skopiować i wkleić ten kod i rekonfigurować go tak daleko, jak chcesz.
Asperi
28

To otacza UITextView w Xcode w wersji 11.0 beta 6 (nadal działa na Xcode 11 GM seed 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()
    }
}
Flet Meo
źródło
1
LineLimit () w Xcode w wersji 11.0 (11A420a), GM Seed 2, wrzesień 2019 r.
e987
2
Działa to dobrze w VStack, ale w przypadku korzystania z List wysokość wiersza nie jest rozszerzana, aby wyświetlić cały tekst w TextView. Próbowałem kilku rzeczy: zmiana isScrollEnabledw TextViewimplementacji; ustawienie stałej szerokości ramki TextView; a nawet umieszczenie TextView i Text w ZStack (w nadziei, że wiersz rozszerzy się, aby dopasować wysokość widoku Text), ale nic nie działa. Czy ktoś ma radę, jak dostosować tę odpowiedź, aby działała również na liście?
MathewS
@Meo Flute umożliwia dopasowanie wysokości do treści.
Abdullah
Zmieniłem isScrollEnabled na false i działa, dzięki.
Abdullah
27

Dzięki a Text()możesz to osiągnąć za pomocą .lineLimit(nil), a dokumentacja sugeruje, że powinno to działać TextField()również. Mogę jednak potwierdzić, że obecnie nie działa to zgodnie z oczekiwaniami.

Podejrzewam błąd - poleciłbym zgłoszenie za pomocą Asystenta opinii. Zrobiłem to i numer identyfikacyjny to FB6124711.

EDYCJA: aktualizacja dla iOS 14: TextEditorzamiast tego użyj nowego .

Andrew Ebling
źródło
Czy istnieje sposób, aby wyszukać błąd przy użyciu identyfikatora FB6124711? Jak sprawdzam asystenta opinii, ale nie jest to zbyt pomocne
CrazyPro007
Nie wierzę, że jest na to sposób. Ale możesz wspomnieć o tym identyfikatorze w swoim raporcie, wyjaśniając, że twój jest naśladownictwem tego samego problemu. Pomaga to zespołowi selekcyjnemu w podniesieniu priorytetu problemu.
Andrew Ebling
2
Potwierdzono, że jest to nadal problem w Xcode w wersji 11.0 beta 2 (11M337n)
Andrew Ebling
3
Potwierdzono, że jest to nadal problem w Xcode w wersji 11.0 beta 3 (11M362v). Możesz ustawić ciąg na „Some \ ntext” i wyświetli się on w dwóch wierszach, ale wpisanie nowej treści spowoduje, że jeden wiersz tekstu urosnie poziomo poza ramkę widoku.
Michael,
3
To wciąż jest problem w Xcode 11.4 - poważnie ??? Jak mamy używać SwiftUI w produkcji z takimi błędami.
Trev14
18

iOS 14

To się nazywa TextEditor

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

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

Dynamiczna wysokość wzrostu:

Jeśli chcesz, aby rosła w miarę pisania, umieść ją z etykietą, jak poniżej:

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

Próbny

Próbny


iOS 13

Korzystanie z natywnego UITextView

możesz użyć natywnego UITextView bezpośrednio w kodzie SwiftUI z tą strukturą:

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

Stosowanie

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

Zalety:

  • Wsparcie dla iOS 13
  • Udostępnione ze starszym kodem
  • Testowany przez lata w UIKit
  • W pełni konfigurowalny
  • Wszystkie inne zalety oryginału UITextView
Mojtaba Hosseini
źródło
3
Jeśli ktoś patrzy na tę odpowiedź i zastanawia się, jak przekazać rzeczywisty tekst do struktury TextView, dodaj następujący wiersz poniżej tego, który ustawia textColor: $ 0.text = "Some text"
Mattl
1
W jaki sposób łączysz tekst ze zmienną? Lub w inny sposób pobrać tekst?
biomiker
1
Pierwsza opcja ma już oprawę tekstową. Drugi to standard UITextView. Możesz z nim wchodzić w interakcje, jak zwykle w UIKit.
Mojtaba Hosseini
12

Obecnie najlepszym rozwiązaniem jest skorzystanie z utworzonego przeze mnie pakietu o nazwie TextView .

Możesz go zainstalować za pomocą Swift Package Manager (wyjaśnione w README). Pozwala na przełączanie stanu edycji i liczne modyfikacje (również szczegółowo opisane w README).

Oto przykład:

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

W tym przykładzie najpierw definiujesz dwie @Statezmienne. Jeden dotyczy tekstu, do którego TextView zapisuje się za każdym razem, gdy jest wpisywany, a drugi dotyczy isEditingstanu TextView.

Po zaznaczeniu TextView przełącza isEditingstan. Kliknięcie przycisku isEditingpowoduje również przełączenie stanu, w którym zostanie wyświetlona klawiatura, i wybranie TextView, kiedy true, i odznaczenie TextView, kiedy false.

Ken Mueller
źródło
1
Dodam problem do repozytorium, ale ma on podobny problem do oryginalnego rozwiązania Asperi, działa świetnie w VStack, ale nie w ScrollView.
RogerTheShrubber
No such module 'TextView'
Alex Bartiş
Edycja: celujesz w macOS, ale platforma obsługuje UIKit tylko z powodu UIViewRepresentable
Alex Bartiş
10

Odpowiedź @Meo Flute jest świetna! Ale to nie działa w przypadku wieloetapowego wprowadzania tekstu. W połączeniu z odpowiedzią @ Asperi, oto rozwiązanie tego problemu, a także dodałem obsługę symbolu zastępczego tylko dla zabawy!

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

Użyj tego w ten sposób:

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)
        }
    }
}
Daniel Tseng
źródło
Lubię to. Symbol zastępczy nie wydaje się działać, ale warto było zacząć od. Sugeruję użycie kolorów semantycznych, takich jak UIColor.tertiaryLabel zamiast UIColor.lightGray i UIColor.label zamiast UIColor.black, aby obsługiwany był zarówno tryb jasny, jak i ciemny.
Helam
@Helam Masz coś przeciwko wyjaśnieniu, w jaki sposób symbol zastępczy nie działa?
Daniel Tseng
@DanielTseng to się nie pojawia. Jak ma się zachowywać? Spodziewałem się, że pokaże, czy tekst jest pusty, ale nigdy się nie wyświetla.
Helam
@Helam, W moim przykładzie mam symbol zastępczy, który ma być pusty. Czy próbowałeś zmienić to na coś innego? („Hello World!” Zamiast „”)
Daniel Tseng
Tak, w moim ustawiłem to na coś innego.
Helam
2

SwiftUI TextView (UIViewRepresentable) z dostępnymi parametrami: 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
    }
}

}

Di Nerd
źródło
Jak dodać domyślny tekst do wieloliniowego pola tekstowego?
Learn2Code
1

Dostępne dla Xcode 12 i iOS14 , to naprawdę łatwe.

import SwiftUI

struct ContentView: View {
    
    @State private var text = "Hello world"
    
    var body: some View {
        TextEditor(text: $text)
    }
}
gandhi Mena
źródło
czy to nie tylko jeśli pracujesz z iOS14, co jeśli użytkownik nadal korzysta z iOS13
Di Nerd
1

Wdrożenie 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
        }
    }
}

i jak używać

struct ContentView: View {

@State var someString = ""

    var body: some View {
         MultilineTextField(text: $someString)
    }
}
Denis Rybkin
źródło