Не удается получить многострочный UITextView, завернутый в UIViewRepresentable, для правильной визуализации. Что я делаю неправильно? - PullRequest
0 голосов
/ 07 ноября 2019

заранее прошу прощения за длинный пост;попытался включить как можно больше деталей. Вопрос в конце, если вы хотите пропустить детали.

Я пытаюсь получить многострочное UITextView, отображаемое в Form в SwiftUI, однако я не смогзаставить его работать правильно. Вот код, с которым я работал (подробности со скриншотами приведены ниже):

// MultilineTextField.swift

import SwiftUI
import UIKit

fileprivate struct UITextViewWrapper: UIViewRepresentable {
    @Binding var text: String
    var placeholder: String
    @Binding var calculatedHeight: CGFloat

    func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {
        let editableTextView = UITextView()
        editableTextView.delegate = context.coordinator
        if $text.wrappedValue.isEmpty {
            editableTextView.text = placeholder
            editableTextView.textColor = UIColor.lightGray
        } else {
            editableTextView.text = $text.wrappedValue
        }
        editableTextView.backgroundColor = UIColor.green

        editableTextView.isEditable = true
        editableTextView.font = UIFont.preferredFont(forTextStyle: .body)
        editableTextView.isSelectable = true
        editableTextView.isUserInteractionEnabled = true
        editableTextView.isScrollEnabled = false

        editableTextView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        editableTextView.setContentHuggingPriority(.defaultLow, for: .vertical)
        return editableTextView
    }

    func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
        uiView.text = self.text
        UITextViewWrapper.recalculateSize(view: uiView, height: $calculatedHeight)
    }

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

    fileprivate static func recalculateSize(view: UITextView, height: Binding<CGFloat>) {
        let newSize = view.sizeThatFits(
            CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude)
        )
        if height.wrappedValue != newSize.height {
            DispatchQueue.main.async {
                height.wrappedValue = newSize.height
            }
        }
    }

    final class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>
        var placeholder: String
        var calculatedHeight: Binding<CGFloat>

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

        func textViewDidChange(_ textView: UITextView) {
            text.wrappedValue = textView.text
            UITextViewWrapper.recalculateSize(view: textView, height: calculatedHeight)
        }

        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 = placeholder
                textView.textColor = UIColor.lightGray
            }
        }
    }
}

struct MultilineTextField: View {
    @Binding var text: String
    @State private var dynamicHeight: CGFloat = 320

    var placeholder: String

    init(_ placeholder: String = "", text: Binding<String>) {
        self.placeholder = placeholder
        self._text = text
    }

    var body: some View {
        UITextViewWrapper(text: $text, placeholder: placeholder, calculatedHeight: $dynamicHeight)
            .frame(minHeight: dynamicHeight)
    }
}

struct MultilineTextField_Previews: PreviewProvider {
    static var test1: String = "Fix leaky faucet in third bedroom's bathroom"
    static var testBinding1 = Binding<String>(get: { test1 }, set: { test1 = $0 })
    static var test2: String = "The seal for the faucet needs to change. It seems to be leaking regardless of water pressure in the lines."
    static var testBinding2 = Binding<String>(get: { test2 }, set: { test2 = $0 })

    static var previews: some View {
        Form {
            Section {
                MultilineTextField("Placeholder", text: testBinding1)
                MultilineTextField("Placeholder", text: testBinding2)
            }
            .border(Color.black)
        }
        .border(Color.black)
    }
}

// SceneDelegate.swift

import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    // ...

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Create the SwiftUI view that provides the window contents.
        var test1: String = "Fix leaky faucet in third bedroom's bathroom"
        let testBinding1 = Binding<String>(get: { test1 }, set: { test1 = $0 })
        var test2: String = "The seal for the faucet needs to change. It seems to be leaking regardless of water pressure in the lines."
        let testBinding2 = Binding<String>(get: { test2 }, set: { test2 = $0 })
        let contentView =
            Form {
                VStack {
                    MultilineTextField("Placeholder", text: testBinding1)
                    MultilineTextField("Placeholder", text: testBinding2)
                }
                .border(Color.black)
            }
            .border(Color.black)

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

    // ...
}

Когда у меня есть один MultilineTextView в приведенной выше форме, кажется, что все работает нормально, но добавление более чем одного ломает его. В документации Apple для SwiftUI Form написано :

Используйте Section для группировки различных частей содержимого формы.

Form with Section View Hierarchy with Section

Я сделал то, что указано в документации. Однако, как вы можете видеть на скриншоте выше, Section, похоже, действует как ZStack (для справки, зеленый фон - это фон UITextView, а белые ячейки - это то, что создано Form). Вот то, что я взял из скриншота выше:

  1. Кажется, UITextView имеют правильный размер.
  2. Ячейки, созданные Form, не выглядят правильносами по себе.

Я наблюдал за сеансом WWDC2019 237 (Создание пользовательских представлений с помощью SwiftUI), который был несколько полезен для понимания того, как работает иерархия представлений в SwiftUI, и я понимаю, что поскольку Form расширяется допокрывая экран, ячейки также должны иметь возможность расширяться в доступное пространство, чтобы вместить все UITextView s, , но они не .

Я полагал, чтотакже попробуйте VStack вместо Section и посмотрите, как это работает, и вот так выглядит иерархия представлений:

Form with VStack View Hierarchy with VStack

Иерархия представлений на скриншоте выше очень похожа на то, что я ожидал бы Section внутри Form, но я думаю, что я не прав. Даже с VStack родительский фрейм имеет неправильный размер.

Вопрос: Как правильно отобразить несколько UITextView s, которые переносят длинный текст (т.е. многострочный) внутри Form в SwiftUI?

Мне кажется, я очень близок к тому, чтобы понять это. Учитывая, что UITextView имеют правильный размер, я подозреваю, что мне нужно выяснить, как правильно определить размер родительских представлений, но я не уверен, как я мог бы это сделать (также не похоже, что это правильный путь SwiftUI)«Делать это). Любое понимание будет оценено.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...