视图自动跳转到多行文本字段
View automatically jumps to Multiline Text Field
我的 SwiftUI 视图有点奇怪,因为我添加了一个 MultilineTextField。当按下列表中的项目时,视图种类来回跳转,然后自动跳转到视图中的最后一个文本字段,如 this 视频中所示。这只是在我最后添加了一个 MultilineTextField 之后发生的。
多行文本字段定义:
import Foundation
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 = [=10=]
self.showingPlaceholder = [=10=].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: \([=10=])")
test = [=10=] } )
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
代码:
struct DetailZwei : View {
@State var data : dataTypeZwei
@State var viewModel = GerätEditieren()
@Environment(\.presentationMode) var presentationMode
@State private var showingAlert = false
var body : some View {
NavigationView {
ScrollView {
VStack {
Group {
Section(header: Text("")) {
Text("Seriennummer")
TextField("Seriennummer", text: $data.sn).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Objekt")
TextField("Objekt", text: $data.objekt).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Gerätetyp")
TextField("Gerätetyp", text: $data.typ).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Geräteposition")
TextField("Geräteposition", text: $data.pos).textFieldStyle(RoundedBorderTextFieldStyle())
}
Group {
Text("Installationsdatum")
TextField("Installationsdatum", text: $data.ida).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Leasing oder Gekauft?")
TextField("Leasing oder Gekauft?", text: $data.lg).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ablaufdatum Leasing")
TextField("Ablaufdatum Leasing", text: $data.la).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ablaufdatum Garantie")
TextField("Ablaufdatum Garantie", text: $data.ga).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("")) {
Text("Strasse")
TextField("Strasse", text: $data.str).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Hausnummer")
TextField("Hausnummer", text: $data.nr).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Postleitzahl")
TextField("Postleitzahl", text: $data.plz).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ort")
TextField("Ort", text: $data.ort).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("")) {
Text("Ansprechperson")
TextField("Ansprechperson", text: $data.vp).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Telefonnummer")
TextField("Telefonnummer", text: $data.tel).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("VDS").bold()) {
Text("Eingetragen durch")
TextField("Eingetragen durch", text: $data.ed).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Lieferdatum VDS")
TextField("Lieferdatum VDS", text: $data.ldvds).textFieldStyle(RoundedBorderTextFieldStyle())
}
// This is the Text Field
Section(header: Text("")) {
Text("Zusätzliche Informationen")
MultilineTextField("Zusätzliche Informationen", text: $data.zusatz).overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.black))
}
}.padding()
.navigationBarTitle("Gerät bearbeiten", displayMode: .inline)
.navigationBarItems(leading: Button(action: { self.handleCancelTapped() }, label: {
Text("Abbrechen")
}),
trailing: Button(action: { self.handleDoneTapped() }, label: {
Text("Speichern")
})
// .disabled(!viewModel.modified)
)
}.alert(isPresented: $showingAlert) {
Alert(title: Text("Änderungen gespeichert"), message: Text("Die Änderungen vom Gerät \(data.sn) wurden erfolgreich gespeichert!"), dismissButton: .default(Text("Zurück").bold()){
self.handleCancelTapped()
})
}
}
}
}
当您打开视图时,您的自定义文本字段会调用 firstResponders。只需删除加载时调用 firstResponder,您的视图就会从头开始。
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
if uiView.text != self.text {
uiView.text = self.text
}
if uiView.window != nil, !uiView.isFirstResponder {
//uiView.becomeFirstResponder() << Here calling firstResponder
}
UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
}
SwiftUI TextFields 尚不支持 firstResponder,但是使用 Representable 并使用 UIKit 是可能的,就像在您的解决方案中一样,Grüezi
我的 SwiftUI 视图有点奇怪,因为我添加了一个 MultilineTextField。当按下列表中的项目时,视图种类来回跳转,然后自动跳转到视图中的最后一个文本字段,如 this 视频中所示。这只是在我最后添加了一个 MultilineTextField 之后发生的。
多行文本字段定义:
import Foundation
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 = [=10=]
self.showingPlaceholder = [=10=].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: \([=10=])")
test = [=10=] } )
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
代码:
struct DetailZwei : View {
@State var data : dataTypeZwei
@State var viewModel = GerätEditieren()
@Environment(\.presentationMode) var presentationMode
@State private var showingAlert = false
var body : some View {
NavigationView {
ScrollView {
VStack {
Group {
Section(header: Text("")) {
Text("Seriennummer")
TextField("Seriennummer", text: $data.sn).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Objekt")
TextField("Objekt", text: $data.objekt).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Gerätetyp")
TextField("Gerätetyp", text: $data.typ).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Geräteposition")
TextField("Geräteposition", text: $data.pos).textFieldStyle(RoundedBorderTextFieldStyle())
}
Group {
Text("Installationsdatum")
TextField("Installationsdatum", text: $data.ida).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Leasing oder Gekauft?")
TextField("Leasing oder Gekauft?", text: $data.lg).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ablaufdatum Leasing")
TextField("Ablaufdatum Leasing", text: $data.la).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ablaufdatum Garantie")
TextField("Ablaufdatum Garantie", text: $data.ga).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("")) {
Text("Strasse")
TextField("Strasse", text: $data.str).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Hausnummer")
TextField("Hausnummer", text: $data.nr).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Postleitzahl")
TextField("Postleitzahl", text: $data.plz).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ort")
TextField("Ort", text: $data.ort).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("")) {
Text("Ansprechperson")
TextField("Ansprechperson", text: $data.vp).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Telefonnummer")
TextField("Telefonnummer", text: $data.tel).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("VDS").bold()) {
Text("Eingetragen durch")
TextField("Eingetragen durch", text: $data.ed).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Lieferdatum VDS")
TextField("Lieferdatum VDS", text: $data.ldvds).textFieldStyle(RoundedBorderTextFieldStyle())
}
// This is the Text Field
Section(header: Text("")) {
Text("Zusätzliche Informationen")
MultilineTextField("Zusätzliche Informationen", text: $data.zusatz).overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.black))
}
}.padding()
.navigationBarTitle("Gerät bearbeiten", displayMode: .inline)
.navigationBarItems(leading: Button(action: { self.handleCancelTapped() }, label: {
Text("Abbrechen")
}),
trailing: Button(action: { self.handleDoneTapped() }, label: {
Text("Speichern")
})
// .disabled(!viewModel.modified)
)
}.alert(isPresented: $showingAlert) {
Alert(title: Text("Änderungen gespeichert"), message: Text("Die Änderungen vom Gerät \(data.sn) wurden erfolgreich gespeichert!"), dismissButton: .default(Text("Zurück").bold()){
self.handleCancelTapped()
})
}
}
}
}
当您打开视图时,您的自定义文本字段会调用 firstResponders。只需删除加载时调用 firstResponder,您的视图就会从头开始。
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
if uiView.text != self.text {
uiView.text = self.text
}
if uiView.window != nil, !uiView.isFirstResponder {
//uiView.becomeFirstResponder() << Here calling firstResponder
}
UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
}
SwiftUI TextFields 尚不支持 firstResponder,但是使用 Representable 并使用 UIKit 是可能的,就像在您的解决方案中一样,Grüezi