InputAccessoryView / 使用 SwiftUI 将视图固定到键盘
InputAccessoryView / View Pinned to Keyboard with SwiftUI
SwiftUI 中是否有与 InputAccessoryView 等效的东西(或者有任何迹象表明它即将到来?)
如果不是,您将如何模拟 InputAccessoryView(即固定到键盘顶部的视图)的行为?期望的行为类似于 iMessage,其中有一个固定在屏幕底部的视图,当键盘打开并位于键盘正上方时,该视图会动画化。例如:
键盘关闭:
键盘打开:
我得到了一些与想要的结果非常接近的东西。所以起初,仅使用 SwiftUI 是不可能做到这一点的。您仍然必须使用 UIKit 来创建具有所需 "inputAccessoryView" 的 UITextField。 SwiftUI中的textfield没有特定的方法
首先我创建了一个新结构:
import UIKit
import SwiftUI
struct InputAccessory: UIViewRepresentable {
func makeUIView(context: Context) -> UITextField {
let customView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 44))
customView.backgroundColor = UIColor.red
let sampleTextField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
sampleTextField.inputAccessoryView = customView
sampleTextField.placeholder = "placeholder"
return sampleTextField
}
func updateUIView(_ uiView: UITextField, context: Context) {
}
}
有了它,我终于可以在我的视图主体中创建一个新的文本字段:
import SwiftUI
struct Test: View {
@State private var showInput: Bool = false
var body: some View {
HStack{
Spacer()
if showInput{
InputAccessory()
}else{
InputAccessory().hidden()
}
}
}
}
现在您可以隐藏和显示 "showInput" 状态的文本字段。下一个问题是,您必须在特定事件中打开键盘并显示文本字段。这对 SwiftUI 来说也是不可能的,你必须回到 UiKit 并让它成为第一响应者。如果您尝试我的代码,您应该会在键盘上方看到红色背景。现在你只需要将字段向上移动,你就会得到一个工作版本。
总的来说,在当前状态下,无法使用键盘或特定的文本字段方法。
我已经在 iOS 14 上使用 99% 纯 SwiftUI 解决了这个问题。
在工具栏中,您可以显示任何您喜欢的视图。
这是我的实现:
import SwiftUI
struct ContentView: View {
@State private var showtextFieldToolbar = false
@State private var text = ""
var body: some View {
ZStack {
VStack {
TextField("Write here", text: $text) { isChanged in
if isChanged {
showtextFieldToolbar = true
}
} onCommit: {
showtextFieldToolbar = false
}
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
VStack {
Spacer()
if showtextFieldToolbar {
HStack {
Spacer()
Button("Close") {
showtextFieldToolbar = false
UIApplication.shared
.sendAction(#selector(UIResponder.resignFirstResponder),
to: nil, from: nil, for: nil)
}
.foregroundColor(Color.black)
.padding(.trailing, 12)
}
.frame(idealWidth: .infinity, maxWidth: .infinity,
idealHeight: 44, maxHeight: 44,
alignment: .center)
.background(Color.gray)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
在 this post by Swift Student, with quite a lot of modification & addition of functionality you take for granted in UIKit. It is a wrapper around UITextField, but that's completely hidden from the user and it's very SwiftUI in its implementation. You can take a look at it in my GitHub repo 的帮助下,我设法创建了一个很好用的解决方案 - 您可以将它作为 Swift 包引入到您的项目中。
(有太多代码无法放入此答案,因此 link 到 repo)
我有一个可以自定义您的工具栏的实现
public struct InputTextField<Content: View>: View {
private let placeholder: LocalizedStringKey
@Binding
private var text: String
private let onEditingChanged: (Bool) -> Void
private let onCommit: () -> Void
private let content: () -> Content
@State
private var isShowingToolbar: Bool = false
public init(placeholder: LocalizedStringKey = "",
text: Binding<String>,
onEditingChanged: @escaping (Bool) -> Void = { _ in },
onCommit: @escaping () -> Void = { },
@ViewBuilder content: @escaping () -> Content) {
self.placeholder = placeholder
self._text = text
self.onEditingChanged = onEditingChanged
self.onCommit = onCommit
self.content = content
}
public var body: some View {
ZStack {
TextField(placeholder, text: $text) { isChanged in
if isChanged {
isShowingToolbar = true
}
onEditingChanged(isChanged)
} onCommit: {
isShowingToolbar = false
onCommit()
}
.textFieldStyle(RoundedBorderTextFieldStyle())
VStack {
Spacer()
if isShowingToolbar {
content()
}
}
}
}
}
iOS 15.0+
macOS 12.0+,Mac 催化剂 15.0+
ToolbarItemPlacement
在 iOS 15.0+
中有一个新的 属性
keyboard
On iOS, keyboard items are above the software keyboard when present, or at the bottom of the screen when a hardware keyboard is attached.
On macOS, keyboard items will be placed inside the Touch Bar.
https://developer.apple.com
struct LoginForm: View {
@State private var username = ""
@State private var password = ""
var body: some View {
Form {
TextField("Username", text: $username)
SecureField("Password", text: $password)
}
.toolbar(content: {
ToolbarItemGroup(placement: .keyboard, content: {
Text("Left")
Spacer()
Text("Right")
})
})
}
}
iMessage 类似于 iOS 15+.
中的 InputAccessoryView
struct KeyboardToolbar<ToolbarView: View>: ViewModifier {
private let height: CGFloat
private let toolbarView: ToolbarView
init(height: CGFloat, @ViewBuilder toolbar: () -> ToolbarView) {
self.height = height
self.toolbarView = toolbar()
}
func body(content: Content) -> some View {
ZStack(alignment: .bottom) {
GeometryReader { geometry in
VStack {
content
}
.frame(width: geometry.size.width, height: geometry.size.height - height)
}
toolbarView
.frame(height: self.height)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
extension View {
func keyboardToolbar<ToolbarView>(height: CGFloat, view: @escaping () -> ToolbarView) -> some View where ToolbarView: View {
modifier(KeyboardToolbar(height: height, toolbar: view))
}
}
并像往常一样使用 .keyboardToolbar
视图修饰符。
struct ContentView: View {
@State private var username = ""
var body: some View {
NavigationView{
Text("Keyboar toolbar")
.keyboardToolbar(height: 50) {
HStack {
TextField("Username", text: $username)
}
.border(.secondary, width: 1)
.padding()
}
}
}
}
您可以在不使用 UIViewRepresentable 的情况下这样做。
它基于
.onReceive(NotificationCenter.default.publisher(for: UITextField.textDidBeginEditingNotification)) { notification in
if let textField = notification.object as? UITextField {
let yourAccessoryView = UIToolbar()
// set your frame, buttons here
textField.inputAccessoryView = yourAccessoryView
}
}
}
SwiftUI 中是否有与 InputAccessoryView 等效的东西(或者有任何迹象表明它即将到来?)
如果不是,您将如何模拟 InputAccessoryView(即固定到键盘顶部的视图)的行为?期望的行为类似于 iMessage,其中有一个固定在屏幕底部的视图,当键盘打开并位于键盘正上方时,该视图会动画化。例如:
键盘关闭:
键盘打开:
我得到了一些与想要的结果非常接近的东西。所以起初,仅使用 SwiftUI 是不可能做到这一点的。您仍然必须使用 UIKit 来创建具有所需 "inputAccessoryView" 的 UITextField。 SwiftUI中的textfield没有特定的方法
首先我创建了一个新结构:
import UIKit
import SwiftUI
struct InputAccessory: UIViewRepresentable {
func makeUIView(context: Context) -> UITextField {
let customView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 44))
customView.backgroundColor = UIColor.red
let sampleTextField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
sampleTextField.inputAccessoryView = customView
sampleTextField.placeholder = "placeholder"
return sampleTextField
}
func updateUIView(_ uiView: UITextField, context: Context) {
}
}
有了它,我终于可以在我的视图主体中创建一个新的文本字段:
import SwiftUI
struct Test: View {
@State private var showInput: Bool = false
var body: some View {
HStack{
Spacer()
if showInput{
InputAccessory()
}else{
InputAccessory().hidden()
}
}
}
}
现在您可以隐藏和显示 "showInput" 状态的文本字段。下一个问题是,您必须在特定事件中打开键盘并显示文本字段。这对 SwiftUI 来说也是不可能的,你必须回到 UiKit 并让它成为第一响应者。如果您尝试我的代码,您应该会在键盘上方看到红色背景。现在你只需要将字段向上移动,你就会得到一个工作版本。
总的来说,在当前状态下,无法使用键盘或特定的文本字段方法。
我已经在 iOS 14 上使用 99% 纯 SwiftUI 解决了这个问题。 在工具栏中,您可以显示任何您喜欢的视图。
这是我的实现:
import SwiftUI
struct ContentView: View {
@State private var showtextFieldToolbar = false
@State private var text = ""
var body: some View {
ZStack {
VStack {
TextField("Write here", text: $text) { isChanged in
if isChanged {
showtextFieldToolbar = true
}
} onCommit: {
showtextFieldToolbar = false
}
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
VStack {
Spacer()
if showtextFieldToolbar {
HStack {
Spacer()
Button("Close") {
showtextFieldToolbar = false
UIApplication.shared
.sendAction(#selector(UIResponder.resignFirstResponder),
to: nil, from: nil, for: nil)
}
.foregroundColor(Color.black)
.padding(.trailing, 12)
}
.frame(idealWidth: .infinity, maxWidth: .infinity,
idealHeight: 44, maxHeight: 44,
alignment: .center)
.background(Color.gray)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
在 this post by Swift Student, with quite a lot of modification & addition of functionality you take for granted in UIKit. It is a wrapper around UITextField, but that's completely hidden from the user and it's very SwiftUI in its implementation. You can take a look at it in my GitHub repo 的帮助下,我设法创建了一个很好用的解决方案 - 您可以将它作为 Swift 包引入到您的项目中。
(有太多代码无法放入此答案,因此 link 到 repo)
我有一个可以自定义您的工具栏的实现
public struct InputTextField<Content: View>: View {
private let placeholder: LocalizedStringKey
@Binding
private var text: String
private let onEditingChanged: (Bool) -> Void
private let onCommit: () -> Void
private let content: () -> Content
@State
private var isShowingToolbar: Bool = false
public init(placeholder: LocalizedStringKey = "",
text: Binding<String>,
onEditingChanged: @escaping (Bool) -> Void = { _ in },
onCommit: @escaping () -> Void = { },
@ViewBuilder content: @escaping () -> Content) {
self.placeholder = placeholder
self._text = text
self.onEditingChanged = onEditingChanged
self.onCommit = onCommit
self.content = content
}
public var body: some View {
ZStack {
TextField(placeholder, text: $text) { isChanged in
if isChanged {
isShowingToolbar = true
}
onEditingChanged(isChanged)
} onCommit: {
isShowingToolbar = false
onCommit()
}
.textFieldStyle(RoundedBorderTextFieldStyle())
VStack {
Spacer()
if isShowingToolbar {
content()
}
}
}
}
}
iOS 15.0+
macOS 12.0+,Mac 催化剂 15.0+
ToolbarItemPlacement
在 iOS 15.0+
keyboard
On iOS, keyboard items are above the software keyboard when present, or at the bottom of the screen when a hardware keyboard is attached. On macOS, keyboard items will be placed inside the Touch Bar. https://developer.apple.com
struct LoginForm: View {
@State private var username = ""
@State private var password = ""
var body: some View {
Form {
TextField("Username", text: $username)
SecureField("Password", text: $password)
}
.toolbar(content: {
ToolbarItemGroup(placement: .keyboard, content: {
Text("Left")
Spacer()
Text("Right")
})
})
}
}
iMessage 类似于 iOS 15+.
中的 InputAccessoryView
struct KeyboardToolbar<ToolbarView: View>: ViewModifier {
private let height: CGFloat
private let toolbarView: ToolbarView
init(height: CGFloat, @ViewBuilder toolbar: () -> ToolbarView) {
self.height = height
self.toolbarView = toolbar()
}
func body(content: Content) -> some View {
ZStack(alignment: .bottom) {
GeometryReader { geometry in
VStack {
content
}
.frame(width: geometry.size.width, height: geometry.size.height - height)
}
toolbarView
.frame(height: self.height)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
extension View {
func keyboardToolbar<ToolbarView>(height: CGFloat, view: @escaping () -> ToolbarView) -> some View where ToolbarView: View {
modifier(KeyboardToolbar(height: height, toolbar: view))
}
}
并像往常一样使用 .keyboardToolbar
视图修饰符。
struct ContentView: View {
@State private var username = ""
var body: some View {
NavigationView{
Text("Keyboar toolbar")
.keyboardToolbar(height: 50) {
HStack {
TextField("Username", text: $username)
}
.border(.secondary, width: 1)
.padding()
}
}
}
}
您可以在不使用 UIViewRepresentable 的情况下这样做。
它基于
.onReceive(NotificationCenter.default.publisher(for: UITextField.textDidBeginEditingNotification)) { notification in
if let textField = notification.object as? UITextField {
let yourAccessoryView = UIToolbar()
// set your frame, buttons here
textField.inputAccessoryView = yourAccessoryView
}
}
}