使自定义 SwiftUI 视图适应内置修饰符
Posted
技术标签:
【中文标题】使自定义 SwiftUI 视图适应内置修饰符【英文标题】:Making a custom SwiftUI View adapt to built-in modifiers 【发布时间】:2020-03-06 06:18:45 【问题描述】:我正在努力为 SwiftUI 编写自己的 BetterTextField
视图,因为在几个方面都缺少内置的 TextField
。也就是说,我想支持延迟绑定(仅在定位焦点时更新绑定值,而不是在每次按键后强制重绘)、程序化聚焦/响应器控制以及 SwiftUI 缺乏的 UIKit UITextField
的一些其他功能。
因此,我创建了一个自定义 UIViewRepresentable
,并带有一个协调器作为 UITextFieldDelegate
,并且工作正常。但是,为了与其他视图保持一致,我真的很想让我的自定义文本字段适应某些现有的 SwiftUI 修饰符。
例如:
// Here's my content view
struct ContentView: View
var body: some View
BetterTextField("Username", text: $username)
// I want to adapt the view to this modifier
.textFieldStyle(RoundedBorderTextFieldStyle())
// Here's my (simplified) custom text field view
struct BetterTextField: UIViewRepresentable
var title: String
@Binding var text: String
init(_ title: String, text: Binding<String>)
self.title = title
self._text = text
func makeUIView(context: Context) -> UITextField
let textField = UITextField()
textField.placeholder = title
return textField
func updateUIView(_ view: UITextField, context: Context)
view.text = text
// How can I check for the .textFieldStyle() modifier here and set the corresponding UIKit style accordingly?
view.borderStyle = .roundedRect
正如评论所说,我如何调整 UITextField
的 borderStyle
属性以匹配 View 修饰符?
更一般地说,如何检查修饰符的存在并返回适当样式的自定义视图(例如 .bold()
可能会转换为属性文本)?
【问题讨论】:
【参考方案1】:View
修饰符只是再次返回some View
的函数,因此您可以实现对任何修饰符的支持,符合您决定适合您的自定义类型的任何协议。您的控件在每个已实现的修饰符上的行为取决于您。
下面是对textFieldStyle
修饰符的简单演示支持,它使您的ContentView
按预期呈现BetterTextField
,具体取决于添加或删除的圆形矩形样式修饰符。
struct BetterTextField: UIViewRepresentable
var title: String
@Binding var text: String
private let textField = UITextField()
init(_ title: String, text: Binding<String>)
self.title = title
self._text = text
func makeUIView(context: Context) -> UITextField
textField.placeholder = title
return textField
func updateUIView(_ view: UITextField, context: Context)
view.text = text
extension BetterTextField
func textFieldStyle<S>(_ style: S) -> some View where S : TextFieldStyle
if style is RoundedBorderTextFieldStyle
self.textField.borderStyle = .roundedRect
return self
【讨论】:
是的,但是如果 RoundedBorderTextFieldStyle() 定义的不仅仅是圆角矩形,该怎么办?每次 Apple 改变它,你都必须重写你的代码。最好为此使用自定义修饰符(不同的命名)并且没有 TextFieldStyle 参数! 只是好奇,你为什么把func textFieldStyle<S>
写成扩展名?据我所知,它在功能上是相同的,包括在原始结构中。
@Extragorey,它只是一个界面设计样式,如果你愿意,最好的实践,通过扩展来分隔不同的功能区域,Apple 广泛使用它,如果我没记错的话,推荐一些 WWDC 会议。如果从技术上讲,那么是的,你是对的。
这个答案对我有用,但重要的是.textFieldStyle
是视图中BetterTextField()
声明之后的第一个修饰符,否则使用Apple 的实现而不是这个(我还更改了返回键入从some View
到BetterTextField
以允许链接其他自定义修饰符)。
@Extragorey 为了不必依赖自定义修饰符的顺序,您可以使用可以通过上下文 (UIViewRepresentable.Context.environment
) 访问的 EnvironmentValues
。这样,您可以添加使用 .environment()
修饰符和自定义环境键 (EnvironmentKey
) 的 View 扩展方法。每当该环境值发生变化时,updateUIView
就会被调用。【参考方案2】:
@available(ios 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension View
/// Sets the style for `TextField` within the environment of `self`.
public func textFieldStyle<S>(_ style: S) -> some View where S : TextFieldStyle
看注释
在self
的环境中设置TextField
的样式
UIViewRepresentable 继承自 View 但在 'self' 中没有任何 TextField
.bold, .italic ... 是字体的修饰符,而不是通用视图的修饰符。说吧
Image("image001").italic()
也不行。
去抖见Debounced Property Wrapper
对于“延迟”绑定,请参阅
/// Creates an instance with a `Text` label generated from a localized title
/// string.
///
/// - Parameters:
/// - titleKey: The key for the localized title of `self`, describing
/// its purpose.
/// - text: The text to be displayed and edited.
/// - onEditingChanged: An `Action` that will be called when the user
/// begins editing `text` and after the user finishes editing `text`,
/// passing a `Bool` indicating whether `self` is currently being edited
/// or not.
/// - onCommit: The action to perform when the user performs an action
/// (usually the return key) while the `TextField` has focus.
public init(_ titleKey: LocalizedStringKey, text: Binding<String>, onEditingChanged: @escaping (Bool) -> Void = _ in , onCommit: @escaping () -> Void = )
“延迟”绑定示例
import SwiftUI
struct MyTextField<S>: View where S: StringProtocol
let label: S
@State private var __text = ""
@Binding var text: String
var body: some View
TextField(label, text: $__text, onEditingChanged: (e) in
)
self.text = self.__text
struct ContentView: View
@State var text = " "
var body: some View
VStack
MyTextField(label: "label", text: $text).textFieldStyle(RoundedBorderTextFieldStyle())
Text(text)
.padding()
struct ContentView_Previews: PreviewProvider
static var previews: some View
ContentView()
如果您需要不同的字体和.bold,请使用
MyTextField(label: "label", text: $text).textFieldStyle(RoundedBorderTextFieldStyle()).font(Font.title.bold())
或
MyTextField(label: "label", text: $text).font(Font.title.bold()).textFieldStyle(RoundedBorderTextFieldStyle())
【讨论】:
以上是关于使自定义 SwiftUI 视图适应内置修饰符的主要内容,如果未能解决你的问题,请参考以下文章