我一直在尝试在SwiftUI中创建多行TextField,但是我不知道怎么做。
这是我目前拥有的代码:
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
Run Code Online (Sandbox Code Playgroud)
但这是输出:
Moj*_*ini 69
它被称为 TextEditor
struct ContentView: View {
@State var text: String = "Multiline \ntext \nis called \nTextEditor"
var body: some View {
TextEditor(text: $text)
}
}
Run Code Online (Sandbox Code Playgroud)
如果您希望它随着您的输入而增长,请将其嵌入到 a 中ZStack
,Text
如下所示:
您可以在 SwiftUI 代码中使用本机 UITextView 使用此结构:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
struct ContentView: View {
var body: some View {
TextView() {
$0.textColor = .red
// Any other setup you like
}
}
}
Run Code Online (Sandbox Code Playgroud)
UIKit
UITextView
sas*_*sas 18
更新:虽然Xcode11 beta 4现在支持TextView
,但我发现换行UITextView
仍然是使可编辑多行文本生效的最佳方法。例如,TextView
具有显示故障,其中文本在视图内部无法正确显示。
原始(测试版1)答案:
现在,您可以包装UITextView
以创建一个composable 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
Run Code Online (Sandbox Code Playgroud)
小智 14
@Meo Flute 的回答很棒!但它不适用于多级文本输入。并结合@Asperi 的回答,这是修复的问题,我还添加了对占位符的支持,只是为了好玩!
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
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
像这样使用它:
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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
Ken*_*ler 13
目前,最好的解决方案是使用我创建的名为TextView 的包。
您可以使用 Swift Package Manager(在 README 中解释)安装它。它允许切换编辑状态和大量自定义(也在自述文件中详细说明)。
下面是一个例子:
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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
在该示例中,您首先定义两个@State
变量。一个用于文本,TextView 在输入时写入文本,另一个用于isEditing
TextView的状态。
TextView 在被选中时会切换isEditing
状态。当您单击按钮时,还会切换isEditing
状态,该状态将显示键盘并在 时选择 TextView true
,并在 时取消选择 TextView false
。
And*_*ing 10
有了Text()
你可以做到这一点使用.lineLimit(nil)
,并且该文档表明,这应该为之工作TextField()
过。但是,我可以确认这目前无法正常工作。
我怀疑有一个错误-建议您向“反馈助手”提交报告。我已经做到了,ID是FB6124711。
小智 10
SwiftUI 有TextEditor
,它类似于TextField
但提供了包含多行的长格式文本输入:
var body: some View {
NavigationView{
Form{
Section{
List{
Text(question6)
TextEditor(text: $responseQuestion6).lineLimit(4)
Text(question7)
TextEditor(text: $responseQuestion7).lineLimit(4)
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这将UITextView包装在Xcode 11.0 beta 6中(仍在Xcode 11 GM种子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()
}
}
Run Code Online (Sandbox Code Playgroud)
我只是尝试使用lineLimit()在GM的Xcode版本11.0(11A419c)中使用swiftui创建多行文本字段。它仍然不起作用。我不敢相信苹果还没有解决这个问题。多行文本字段在移动应用程序中相当普遍。
好的,我从@sas方法开始,但是实际上需要它看起来和感觉像内容适合的多行文本字段,等等。这就是我所得到的。希望对其他人有所帮助。使用过Xcode 11.1。
提供的自定义MultilineTextField具有:
1.适合内容
2.自动对焦
3.占位符
4.提交时
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
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)
.overlay(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
Run Code Online (Sandbox Code Playgroud)
SwiftUI TextView(UIViewRepresentable) 具有以下可用参数:fontStyle、isEditable、backgroundColor、borderColor 和 border Width
TextView(文本:self.$viewModel.text,fontStyle:.body,isEditable:true,backgroundColor:UIColor.white,borderColor:UIColor.lightGray,borderWidth:1.0).padding()
文本视图(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
}
}
}
Run Code Online (Sandbox Code Playgroud)
只是想分享我的UITextView
解决方案,而不是协调员。我注意到 SwiftUI 调用时UITextView.intrinsicContentSize
没有告诉它应该适合什么宽度。默认情况下UITextView
,假设它具有无限的宽度来布局内容,因此如果它只有一行文本,它将返回适合该一行所需的大小。
为了解决这个问题,只要视图的宽度发生变化,我们就可以子类化UITextView
并使固有尺寸无效,并在计算固有尺寸时考虑宽度。
struct TextView: UIViewRepresentable {
var text: String
public init(_ text: String) {
self.text = text
}
public func makeUIView(context: Context) -> UITextView {
let textView = WrappedTextView()
textView.backgroundColor = .clear
textView.isScrollEnabled = false
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = 0
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
textView.setContentHuggingPriority(.defaultHigh, for: .vertical)
return textView
}
public func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
}
class WrappedTextView: UITextView {
private var lastWidth: CGFloat = 0
override func layoutSubviews() {
super.layoutSubviews()
if bounds.width != lastWidth {
lastWidth = bounds.width
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
let size = sizeThatFits(
CGSize(width: lastWidth, height: UIView.layoutFittingExpandedSize.height))
return CGSize(width: size.width.rounded(.up), height: size.height.rounded(.up))
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
5895 次 |
最近记录: |