заранее прошу прощения за длинный пост;попытался включить как можно больше деталей. Вопрос в конце, если вы хотите пропустить детали.
Я пытаюсь получить многострочное 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
для группировки различных частей содержимого формы.
Я сделал то, что указано в документации. Однако, как вы можете видеть на скриншоте выше, Section
, похоже, действует как ZStack
(для справки, зеленый фон - это фон UITextView
, а белые ячейки - это то, что создано Form
). Вот то, что я взял из скриншота выше:
- Кажется,
UITextView
имеют правильный размер. - Ячейки, созданные
Form
, не выглядят правильносами по себе.
Я наблюдал за сеансом WWDC2019 237 (Создание пользовательских представлений с помощью SwiftUI), который был несколько полезен для понимания того, как работает иерархия представлений в SwiftUI, и я понимаю, что поскольку Form
расширяется допокрывая экран, ячейки также должны иметь возможность расширяться в доступное пространство, чтобы вместить все UITextView
s, , но они не .
Я полагал, чтотакже попробуйте VStack
вместо Section
и посмотрите, как это работает, и вот так выглядит иерархия представлений:
Иерархия представлений на скриншоте выше очень похожа на то, что я ожидал бы Section
внутри Form
, но я думаю, что я не прав. Даже с VStack
родительский фрейм имеет неправильный размер.
Вопрос: Как правильно отобразить несколько UITextView
s, которые переносят длинный текст (т.е. многострочный) внутри Form
в SwiftUI?
Мне кажется, я очень близок к тому, чтобы понять это. Учитывая, что UITextView
имеют правильный размер, я подозреваю, что мне нужно выяснить, как правильно определить размер родительских представлений, но я не уверен, как я мог бы это сделать (также не похоже, что это правильный путь SwiftUI)«Делать это). Любое понимание будет оценено.