SwiftUI TextEditor 初始内容大小错误
SwiftUI TextEditor Initial Content Size Is Wrong
iOS 14.4 + Xcode 12.4
我想在 iOS 上的 SwiftUI 中制作一个简单的清单,其中每个项目的文本是 TextEditor
。
首先,我创建了基本的应用程序结构并在其中填充了一些演示内容:
import SwiftUI
@main
struct TestApp: App {
@State var alpha = "Alpha"
@State var bravo = "Bravo is a really long one that should wrap to multiple lines."
@State var charlie = "Charlie"
init(){
//Remove the default background of the TextEditor/UITextView
UITextView.appearance().backgroundColor = .clear
}
var body: some Scene {
WindowGroup {
ScrollView{
VStack(spacing: 7){
TaskView(text: $alpha)
TaskView(text: $bravo)
TaskView(text: $charlie)
}
.padding(20)
}
.background(Color.gray)
}
}
}
然后每个TaskView
代表列表中的一个任务(白框):
struct TaskView:View{
@Binding var text:String
var body: some View{
HStack(alignment:.top, spacing:8){
Button(action: {
print("Test")
}){
Circle()
.strokeBorder(Color.gray,lineWidth: 1)
.background(Circle().foregroundColor(Color.white))
.frame(width:22, height: 22)
}
.buttonStyle(PlainButtonStyle())
FieldView(name: $text)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(EdgeInsets(top:10, leading:10, bottom: 10, trailing: 30))
.background(Color.white)
.cornerRadius(5)
}
}
最后,每个 TextEditor
都在 FieldView
中,如下所示:
struct FieldView: View{
@Binding var name: String
var body: some View{
ZStack{
Text(name)
.padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -3))
.opacity(0)
TextEditor(text: $name)
.fixedSize(horizontal: false, vertical: true)
.padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -3))
}
}
}
如您在上面的屏幕截图中所见,TextEditor
的初始高度不会自动调整大小以适合文本。但是只要我输入它,它就会适当地调整大小。这是一个视频,显示:
如何让视图具有正确的初始高度?在我输入之前,TextEditor
垂直滚动,所以它似乎有错误的固有内容大小。
注意:视图保持半透明并带有边框,因此您可以see/debug了解发生了什么。
struct FieldView: View{
@Binding var name: String
@State private var textEditorHeight : CGFloat = 100
var body: some View{
ZStack(alignment: .topLeading) {
Text(name)
.background(GeometryReader {
Color.clear
.preference(key: ViewHeightKey.self,
value: [=10=].frame(in: .local).size.height)
})
//.opacity(0)
.border(Color.pink)
.foregroundColor(Color.red)
TextEditor(text: $name)
.padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -7))
.frame(height: textEditorHeight + 12)
.border(Color.green)
.opacity(0.4)
}
.onPreferenceChange(ViewHeightKey.self) { textEditorHeight = [=10=] }
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
print("Reporting height: \(value)")
}
}
首先,我使用 PreferenceKey
将“不可见”文本视图的高度传递到视图层次结构中。然后,我用该值设置 TextEditor
框架的高度。
请注意,视图现在对齐 topLeading
-- 在您的初始示例中,不可见文本居中对齐。
我不喜欢的一件事是边缘插图的使用——这些感觉就像神奇的数字(好吧,它们是……)我宁愿有一个没有它们的解决方案仍然保持Text
和 TextEditor
完全对齐。但是,这暂时有效。
更新,将 UIViewRepresentable 与 UITextView 一起使用
这似乎有效并避免了滚动问题:
struct TaskView:View{
@Binding var text:String
@State private var textHeight : CGFloat = 40
var body: some View{
HStack(alignment:.top, spacing:8){
Button(action: {
print("Test")
}){
Circle()
.strokeBorder(Color.gray,lineWidth: 1)
.background(Circle().foregroundColor(Color.white))
.frame(width:22, height: 22)
}
.buttonStyle(PlainButtonStyle())
FieldView(text: $text, heightToTransmit: $textHeight)
.frame(height: textHeight)
.border(Color.red)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(EdgeInsets(top:10, leading:10, bottom: 10, trailing: 30))
.background(Color.white)
.cornerRadius(5)
}
}
struct FieldView : UIViewRepresentable {
@Binding var text : String
@Binding var heightToTransmit: CGFloat
func makeUIView(context: Context) -> UIView {
let view = UIView()
let textView = UITextView(frame: .zero, textContainer: nil)
textView.delegate = context.coordinator
textView.backgroundColor = .yellow // visual debugging
textView.isScrollEnabled = false // causes expanding height
context.coordinator.textView = textView
textView.text = text
view.addSubview(textView)
// Auto Layout
textView.translatesAutoresizingMaskIntoConstraints = false
let safeArea = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: safeArea.topAnchor),
textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor)
])
return view
}
func updateUIView(_ view: UIView, context: Context) {
context.coordinator.heightBinding = $heightToTransmit
context.coordinator.textBinding = $text
DispatchQueue.main.async {
context.coordinator.runSizing()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator : NSObject, UITextViewDelegate {
var textBinding : Binding<String>?
var heightBinding : Binding<CGFloat>?
var textView : UITextView?
func runSizing() {
guard let textView = textView else { return }
textView.sizeToFit()
self.textBinding?.wrappedValue = textView.text
self.heightBinding?.wrappedValue = textView.frame.size.height
}
func textViewDidChange(_ textView: UITextView) {
runSizing()
}
}
}
iOS 14.4 + Xcode 12.4
我想在 iOS 上的 SwiftUI 中制作一个简单的清单,其中每个项目的文本是 TextEditor
。
首先,我创建了基本的应用程序结构并在其中填充了一些演示内容:
import SwiftUI
@main
struct TestApp: App {
@State var alpha = "Alpha"
@State var bravo = "Bravo is a really long one that should wrap to multiple lines."
@State var charlie = "Charlie"
init(){
//Remove the default background of the TextEditor/UITextView
UITextView.appearance().backgroundColor = .clear
}
var body: some Scene {
WindowGroup {
ScrollView{
VStack(spacing: 7){
TaskView(text: $alpha)
TaskView(text: $bravo)
TaskView(text: $charlie)
}
.padding(20)
}
.background(Color.gray)
}
}
}
然后每个TaskView
代表列表中的一个任务(白框):
struct TaskView:View{
@Binding var text:String
var body: some View{
HStack(alignment:.top, spacing:8){
Button(action: {
print("Test")
}){
Circle()
.strokeBorder(Color.gray,lineWidth: 1)
.background(Circle().foregroundColor(Color.white))
.frame(width:22, height: 22)
}
.buttonStyle(PlainButtonStyle())
FieldView(name: $text)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(EdgeInsets(top:10, leading:10, bottom: 10, trailing: 30))
.background(Color.white)
.cornerRadius(5)
}
}
最后,每个 TextEditor
都在 FieldView
中,如下所示:
struct FieldView: View{
@Binding var name: String
var body: some View{
ZStack{
Text(name)
.padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -3))
.opacity(0)
TextEditor(text: $name)
.fixedSize(horizontal: false, vertical: true)
.padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -3))
}
}
}
如您在上面的屏幕截图中所见,TextEditor
的初始高度不会自动调整大小以适合文本。但是只要我输入它,它就会适当地调整大小。这是一个视频,显示:
如何让视图具有正确的初始高度?在我输入之前,TextEditor
垂直滚动,所以它似乎有错误的固有内容大小。
注意:视图保持半透明并带有边框,因此您可以see/debug了解发生了什么。
struct FieldView: View{
@Binding var name: String
@State private var textEditorHeight : CGFloat = 100
var body: some View{
ZStack(alignment: .topLeading) {
Text(name)
.background(GeometryReader {
Color.clear
.preference(key: ViewHeightKey.self,
value: [=10=].frame(in: .local).size.height)
})
//.opacity(0)
.border(Color.pink)
.foregroundColor(Color.red)
TextEditor(text: $name)
.padding(EdgeInsets(top: -7, leading: -3, bottom: -5, trailing: -7))
.frame(height: textEditorHeight + 12)
.border(Color.green)
.opacity(0.4)
}
.onPreferenceChange(ViewHeightKey.self) { textEditorHeight = [=10=] }
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
print("Reporting height: \(value)")
}
}
首先,我使用 PreferenceKey
将“不可见”文本视图的高度传递到视图层次结构中。然后,我用该值设置 TextEditor
框架的高度。
请注意,视图现在对齐 topLeading
-- 在您的初始示例中,不可见文本居中对齐。
我不喜欢的一件事是边缘插图的使用——这些感觉就像神奇的数字(好吧,它们是……)我宁愿有一个没有它们的解决方案仍然保持Text
和 TextEditor
完全对齐。但是,这暂时有效。
更新,将 UIViewRepresentable 与 UITextView 一起使用
这似乎有效并避免了滚动问题:
struct TaskView:View{
@Binding var text:String
@State private var textHeight : CGFloat = 40
var body: some View{
HStack(alignment:.top, spacing:8){
Button(action: {
print("Test")
}){
Circle()
.strokeBorder(Color.gray,lineWidth: 1)
.background(Circle().foregroundColor(Color.white))
.frame(width:22, height: 22)
}
.buttonStyle(PlainButtonStyle())
FieldView(text: $text, heightToTransmit: $textHeight)
.frame(height: textHeight)
.border(Color.red)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(EdgeInsets(top:10, leading:10, bottom: 10, trailing: 30))
.background(Color.white)
.cornerRadius(5)
}
}
struct FieldView : UIViewRepresentable {
@Binding var text : String
@Binding var heightToTransmit: CGFloat
func makeUIView(context: Context) -> UIView {
let view = UIView()
let textView = UITextView(frame: .zero, textContainer: nil)
textView.delegate = context.coordinator
textView.backgroundColor = .yellow // visual debugging
textView.isScrollEnabled = false // causes expanding height
context.coordinator.textView = textView
textView.text = text
view.addSubview(textView)
// Auto Layout
textView.translatesAutoresizingMaskIntoConstraints = false
let safeArea = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: safeArea.topAnchor),
textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor)
])
return view
}
func updateUIView(_ view: UIView, context: Context) {
context.coordinator.heightBinding = $heightToTransmit
context.coordinator.textBinding = $text
DispatchQueue.main.async {
context.coordinator.runSizing()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator : NSObject, UITextViewDelegate {
var textBinding : Binding<String>?
var heightBinding : Binding<CGFloat>?
var textView : UITextView?
func runSizing() {
guard let textView = textView else { return }
textView.sizeToFit()
self.textBinding?.wrappedValue = textView.text
self.heightBinding?.wrappedValue = textView.frame.size.height
}
func textViewDidChange(_ textView: UITextView) {
runSizing()
}
}
}