前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在SwiftUI中使用UIKit视图

在SwiftUI中使用UIKit视图

作者头像
东坡肘子
发布2022-07-28 12:50:56
8.1K0
发布2022-07-28 12:50:56
举报

在SwiftUI中使用UIKit视图

如想获得更好的阅读体验可以访问我的博客www.fatbobman.com,或点击下方的阅读原文

已迈入第三个年头的SwiftUI相较诞生初始已经提供了更多的原生功能,但仍有大量的事情是无法直接通过原生SwiftUI代码来完成的。在相当长的时间中开发者仍需在SwiftUI中依赖UIKit(AppKit)代码。好在,SwiftUI为开发者提供了便捷的方式将UIKit(AppKit)视图(或控制器)包装成SwiftUI视图。

本文将通过对UITextField的包装来讲解以下几点:

•如何在SwiftUI中使用UIKit视图•如何让你的UIKit包装视图具有SwiftUI风格•在SwiftUI使用UIKit视图需要注意的地方

如果你已经对如何使用UIViewRepresentable有所掌握,可以直接从SwiftUI风格化部分阅读

基础

在具体演示包装代码之前,我们先介绍一些与在SwiftUI中使用UIKit视图有关的基础知识。

无需担心是否能立即理解下述内容,在后续的演示中会有更多的内容帮助你掌握相关知识。

生命周期

SwiftUI同UIKit和AppKit的主要区别之一是,SwiftUI的视图(View)是值类型,并不是对屏幕上绘制内容的具体引用。在SwiftUI中,开发者为视图创建描述,而并不实际渲染它们。

在UIKit(或AppKit)中,视图(或视图控制器)有明确的生命周期节点,比如vidwDidloadloadViewviewWillAppeardidAddSubViewdidMoveToSuperview等方法,它们本质上充当了钩子的角色,让开发者能够通过执行一段逻辑来响应系统给定的事件。

SwiftUI的视图,本身没有清晰(可适当描述)的生命周期,它们是值、是声明。SwiftUI提供了几个修改器(modifier)来实现类似UIKit中钩子方法的行为。比如onAppearviewWillAppear的表现很类似。同UIKit的钩子方法的位置有很大的不同, onAppearonDisappear是在当前视图的父视图上声明的。

将UIKit视图包装成SwiftUI的视图时,我们需要了解两者生命周期之间的不同,不要强行试图找到完全对应的方法,要从SwiftUI的角度来思考如何调用UIKit视图。

UIViewRepresentable协议

在SwiftUI中包装UIView非常简单,只需要创建一个遵守UIViewRepresentable协议的结构体就行了。

UIViewControllerRepresentable对应UIViewControllerNSViewRepresentable对应NSViewNSViewControllerRepresentable对应NSViewController。内部的结构和实现逻辑都一致。

UIViewrepresentable的协议并不复杂,只包含:makeUIViewupdateUIViewdismantleUIViewmakeCoordinator四个方法。makeUIViewupdateUIView为必须提供实现的方法。

UIViewRepresentable本身遵守View协议,因此SwiftUI会将任何符合该协议的结构体都当作一般的SwiftUI视图来对待。不过由于UIViewRepresentable的特殊的用途,其内部的生命周期又同标准的SwiftUI视图有所不同。

UIViewRepresentableLifeCycle

•makeCoordinator如果我们声明了Coordinator(协调器),UIViewRepresentable视图会在初始化后首先创建它的实例,以便在其他的方法中调用。Coordinator默认为Void,该方法在UIViewRepresentable的生命周期中只会调用一次,因此只会创建一个协调器实例。•makeUIView创建一个用来包装的UIKit视图实例。该方法在UIViewRepresentable的生命周期中只会调用一次。•updateUIViewSwiftUI会在应用程序的状态(State)发生变化时更新受这些变化影响的界面部分。当UIViewRepresentable视图中的注入依赖发生变化时,SwiftUI会调用updateUIView。其调用时机同标准SwiftUI视图的body一致,最大的不同为,调用body为计算值,而调用updateview仅为通知UIViewRepresentable视图依赖有变化,至于是否需要根据这些变化来做反应,则由开发者来自行处理。该方法在UIViewRepresentable的生命周期中会多次调用,直到视图被移出视图树(更准确地描述是切换到另一个不包含该视图的视图树分支)。在makeUIVIew执行后,updateUIVew必然会执行一次•dismantleUIView在UIViewRepresentable视图被移出视图树之前,SwiftUI会调用dismantleUIView,通常在此方法中可以执行u删除观察器等善后操作。dismantleUIView为类型方法。

下面的代码将创建一个同ProgressView一样的转圈菊花:

代码语言:javascript
复制
struct MyProgrssView: UIViewRepresentable {    func makeUIView(context: Context) -> UIActivityIndicatorView {        let view = UIActivityIndicatorView()        view.startAnimating()        return view    }    func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {}}struct Demo: View {    var body: some View {            MyProgrssView()    }}

黑匣子

SwiftUI在绘制屏幕时,会从视图树的顶端开始对视图的body求值,如果其中还包含子视图则将递归求值,直到获得最终的结果。但SwiftUI无法真正进行无限量的调用来绘制视图,因此它必须以某种方式缩短递归。为了结束递归,SwiftUI包含了很多的原始类型(primitive types)。当SwiftUI递归到这些原始类型时,将结束递归,它将不再关心原始类型的body,而让原始类型自行对其管理的区域进行处理。

SwiftUI框架通过将body定义为Never来标记该View为原始类型。UIViewRepresentable恰巧也为其中之一(TextZStackColorList等也都是所谓的原始类型)。

代码语言:javascript
复制
public protocol UIViewRepresentable : View where Self.Body == Never

事实上几乎所有的原始类型都是对UIKit或AppKit的底层包装。

UIViewRepresentable作为原始类型,SwiftUI对其内部所知甚少(因为无需关心)。通常需要开发者在UIViewRepresentable视图的Coordinator(协调器)中做一些的工作,从而保证两个框架(SwiftUI同UIKit)代码之间的沟通和联系。

协调器

苹果框架很喜欢使用协调器(Coordinator)这个名词,UIKit开发中有协调器设计模式、Core Data中有持久化存储协调器。在UIViewRepresentable中协调器同它们的概念完全不同,主要起到以下几个方面的作用:

•实现UIKit视图的代理UIKit组件通常依赖代理(delegate)来实现一些功能,“代理”是响应其他地方发生的事件的对象。例如,UIKit中我们将一个代理对象附加到Text field视图上,当用户输入时,当用户按下return键时,该代理对象中对应的方法将被调用。通过将协调器声明为UIKit视图对应的代理对象,我们就可以在其中实现所需的代理方法。•同SwiftUI框架保持沟通上文中,我们提到UIViewRepresentable作为原始类型,需要主动承担更多的同SwiftUI框架或其他视图之间的沟通工作。在协调器中,我们可以通过双向绑定(Binding),通知中心(notificationCenter)或其他例如Redux模式的单项数据流等方式,将UIKit视图内部的状态报告给SwiftUI框架或其他需要的模块。同样也可以通过注册观察器、订阅Publisher等方式获取所需的信息。•处理UIKit视图中的复杂逻辑在UIKit开发中,通常会将业务逻辑放置在UIViewController中,SwiftUI没有Controller这个概念,视图仅是状态的呈现。对于一些实现复杂功能的UIKit模组,如果完全按照SwiftUI的模式将其业务逻辑彻底剥离是非常困难的。因此将无法剥离的业务逻辑的实现代码放入协调器中,靠近代理方法,便于相互之间的协调和管理。

包装UITextField

本节中我们将利用上面的知识实现一个具有简单功能的UITextField包装视图——TextFieldWrapper

版本1.0

在第一个版本中,我们要实现一个类似如下原生代码的功能:

代码语言:javascript
复制
TextField("name:",text:$name)

image-20210822184949860

查看源代码

我们在makeUIView中创建了UITextField的实例,并对其placeholder和text进行了设定。在右侧的预览中,我们可以看到placeholder可以正常显示,如果你在其中输入文字,表现的状态也同TextField完全一致。

通过.border,我们看到TextFieldWrapper的视图尺寸没有符合预期,这是由于UITextField在不进行约束的情况下会默认占据全部可用空间。上文关于UIActivityIndicatorView的演示代码并没有出现这个情况。因此对于不同的UIKit组件,我们需要了解其默认设置,酌情对其进行约束设定。

makeUIView中添加如下语句,此时文本输入框的尺寸就和预期一致了:

代码语言:javascript
复制
        textfield.setContentHuggingPriority(.defaultHigh, for: .vertical)        textfield.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

稍微调整一下Demo视图,在.padding()下添加Text("name:\(name)")。如果按照TextField的正常行为,当我们在其中输入任何文本时,下方的Text中应该显示出对应的内容,不过在我们当前的代码版本中,并没有表现出预期的行为。

image-20210822190605447

让我们再次来分析一下代码。

尽管我们声明了一个Binding<String>类型的text,并且在makeUIView中将其赋值给了textfield,不过UITextField并不会将我们录入的内容自动回传给Binding<String>text,这导致Demo视图中的name并不会因为文字录入而发生改变。

UITextfield在每次录入文字时,都会自动调用func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool的代理方法。因此我们需要创建协调器,并在协调器中实现该方法,将录入的内容传递给Demo视图中的name变量。

创建协调器:

代码语言:javascript
复制
extension TextFieldWrapper{    class Coordinator:NSObject,UITextFieldDelegate{        @Binding var text:String        init(text:Binding<String>){            self._text = text        }        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {            if let text = textField.text as NSString? {                let finaltext = text.replacingCharacters(in: range, with: string)                self.text = finaltext as String            }            return true        }    }}

我们需要在textField方法中回传数据,因此在Coordinator中同样需要使用到Binding<String>,如此对text的操作即为对Demo视图中name的操作。

如果UIViewRepresentable视图中的Coordinator不为Void,则必须通过makeCoordinator来创建它的实例。在TextFieldWrapper中添加如下代码:

代码语言:javascript
复制
    func makeCoordinator() -> Coordinator {        .init(text: $text)    }

最后在makeUIView中添加:

代码语言:javascript
复制
    textfield.delegate = context.coordinator

UITextField在发生特定事件后将在协调器中查找并调用对应的代理方法。

image-20210822191834883

查看源代码

至此,我们创建的UITextField包装已经同原生的TextField的表现行为一致了。

你确定?

再度修改一下Demo视图,将其修改为:

代码语言:javascript
复制
struct Demo: View {    @State var name: String = ""    var body: some View {        VStack {            TextFieldWrapper("name:", text: $name)                .border(.blue)                .padding()            Text("name:\(name)")            Button("Random Name"){                name = String(Int.random(in: 0...100))            }        }    }}

按照对原生TextField的表现预期,当我们按下Random Name按钮时,TextTextFieldWrapper中的文字都应该变成由String(Int.random(in: 0...100))产生的随机数字,但是如果你使用上述代码进行测试,TextFieldWrapper中的文字并没有变化。

makeUIView中,我们使用textfield.text = text获取了Demo视图中name的值,但makeUIView只会执行一次。当点击Random Name引起name变化时,SwiftUI将会调用updateUIView,而我们并没有在其中做任何的处理。只需要在updateUIVIew中添加如下代码即可:

代码语言:javascript
复制
    func updateUIView(_ uiView: UIViewType, context: Context) {        DispatchQueue.main.async {            uiView.text = text        }    }

makeUIView方法的参数中有一个context: Context,通过这个上下文,我们可以访问到Coordinator(自定义协调器)、transaction(如何处理状态更新,动画模式)以及environment(当前视图的环境值集合)。我们之后将通过实例演示其用法。该context同样可以在updateUIVIewdismantleUIView中访问。updataUIView的参数_ uiView:UIViewType为我们在makeUIVIew中创建的UIKit视图实例。

查看源代码

现在,我们的TextFieldWrapper的表现已经确实同TextField一致了。

textFieldWrappertest

版本2.0——添加设定

在第一个版本的基础上,我们将为TextFieldWrapper添加colorfontclearButtonModeonCommit以及onEditingChanged的配置设定。

考虑到尽量不将例程复杂化,我们使用UIColorUIFont作为配置类型。将SwiftUI的ColorFont转换成UIKit版本将增加不小的代码量。

colorfont以及我们新增加的clearButtonMode并不需要双向数据流,因此无需采用Binding方式,仅需在updateView中及时响应它们的变化既可。

onCommitonEditingChanged分别对应着UITextField代理的textFieldShouldReturntextFieldDidBeginEditing以及textFieldDidEndEditing方法,我们需要在协调器中分别实现这些方法,并调用对应的Block

首先修改协调器:

代码语言:javascript
复制
extension TextFieldWrapper {    class Coordinator: NSObject, UITextFieldDelegate {        @Binding var text: String        var onCommit: () -> Void        var onEditingChanged: (Bool) -> Void        init(text: Binding<String>,             onCommit: @escaping () -> Void,             onEditingChanged: @escaping (Bool) -> Void) {            self._text = text            self.onCommit = onCommit            self.onEditingChanged = onEditingChanged        }        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {            if let text = textField.text as NSString? {                let finaltext = text.replacingCharacters(in: range, with: string)                self.text = finaltext as String            }            return true        }        func textFieldShouldReturn(_ textField: UITextField) -> Bool {            onCommit()            return true        }        func textFieldDidBeginEditing(_ textField: UITextField) {            onEditingChanged(true)        }        func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {            onEditingChanged(false)        }    }}

TextFieldWrapper进行修改:

代码语言:javascript
复制
struct TextFieldWrapper: UIViewRepresentable {    init(_ placeholder: String,         text: Binding<String>,         color: UIColor = .label,         font: UIFont = .preferredFont(forTextStyle: .body),         clearButtonMode:UITextField.ViewMode = .whileEditing,         onCommit: @escaping () -> Void = {},         onEditingChanged: @escaping (Bool) -> Void = { _ in }    )    {        self.placeholder = placeholder        self._text = text        self.color = color        self.font = font        self.clearButtonMode = clearButtonMode        self.onCommit = onCommit        self.onEditingChanged = onEditingChanged    }    let placeholder: String    @Binding var text: String    let color: UIColor    let font: UIFont    let clearButtonMode: UITextField.ViewMode    var onCommit: () -> Void    var onEditingChanged: (Bool) -> Void    typealias UIViewType = UITextField    func makeUIView(context: Context) -> UIViewType {        let textfield = UITextField()        textfield.setContentHuggingPriority(.defaultHigh, for: .vertical)        textfield.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)        textfield.placeholder = placeholder        textfield.delegate = context.coordinator        return textfield    }    func updateUIView(_ uiView: UIViewType, context: Context) {        DispatchQueue.main.async {            uiView.text = text            uiView.textColor = color            uiView.font = font            uiView.clearButtonMode = clearButtonMode        }    }    func makeCoordinator() -> Coordinator {        .init(text: $text,onCommit: onCommit,onEditingChanged: onEditingChanged)    }}

修改Demo视图:

代码语言:javascript
复制
struct Demo: View {    @State var name: String = ""    @State var color: UIColor = .red    var body: some View {        VStack {            TextFieldWrapper("name:",                             text: $name,                             color: color,                             font: .preferredFont(forTextStyle: .title1),                             clearButtonMode: .whileEditing,                             onCommit: { print("return") },                             onEditingChanged: { editing in print("isEditing \(editing)") })                .border(.blue)                .padding()            Text("name:\(name)")            Button("Random Name") {                name = String(Int.random(in: 0...100))            }            Button("Change Color") {                color = color == .red ? .label : .red            }        }    }}struct TextFieldWrapperPreview: PreviewProvider {    static var previews: some View {        Demo()    }}

查看源代码

textfieldwrapperdemo2

SwiftUI 风格化

我们不仅实现了对字体、色彩的设定,而且增加了原生TextField没有的clearButtonMode设置。按照上述的方法,可以逐步为其添加更多的设置,让TextFieldWrapper获得更多的功能。

代码好像有点不太对劲?!

随着功能配置的增加,上面代码在使用中会愈发的不方便。如何实现类似原生TextFiled的链式调用呢?譬如:

代码语言:javascript
复制
               TextFieldWrapper("name:",text:$name)                .clearMode(.whileEditing)                                .onCommit{print("commit")}                .foregroundColor(.red)                .font(.title)                .disabled(allowEdit)

本节中,我们将重写配置代码,实现UIKit包装风格SwiftUI化。

本节以版本1.0结束时的代码为基础。

所谓的SwfitUI风格化,更确切地说应该是函数式编程的链式调用。将多个操作通过点号(.)链接在一起,增加可读性。作为将函数视为一等公民的Swift,实现上述的链式调用非常方便。不过有以下几点需要注意:

•如何改变View内的的值(View是结构)•如何处理返回的类型(保证调用链继续有效)•如何利用SwiftUI框架现有的数据并与之交互逻辑

为了更全面的演示,下面的例子,采用了不同的处理方式。在实际使用中,可根据实际需求选择适当的方案。

foregroundColor

我们在SwiftUI中经常会用到foregroundColor来设置前景色,比如下面的代码:

代码语言:javascript
复制
            VStack{                Text("hello world")                    .foregroundColor(.red)            }            .foregroundColor(.blue)

不知道大家是否知道上面的两个foregroundColor有什么不同。

代码语言:javascript
复制
extension Text{      public func foregroundColor(_ color: Color?) -> Text}extension View{      public func foregroundColor(_ color: Color?) -> some View}

方法名一样,但作用的对象不同。Text只有在针对本身的foregroundColor没有设置的时候,才会尝试从当前环境中获取foregroundColor(针对View)的设定。原生的TextFiled没有针对本身的foregroundColor,不过我们目前也没有办法获取到SwiftUI针对View的foregroundColor设定的环境值(估计是),因此我们可以使用Text的方式,为TextFieldWrapper创建一个专属的foregroundColor

TextFieldWrapper添加一个变量

代码语言:javascript
复制
private var color:UIColor = .label

updateUIView中增加

代码语言:javascript
复制
uiView.textColor = color

设置配置方法:

代码语言:javascript
复制
extension TextFieldWrapper {    func foregroundColor(_ color:UIColor) -> Self{        var view = self        view.color = color        return view    }}

查看源代码

就这么简单。现在我们就可以使用.foreground(.red)来设置TextFieldWrapper的文字颜色了。

这种写法是为特定视图类型添加扩展的常用写法。有以下两个优点:

•使用private,无需暴露配置变量•仍返回特定类型的视图,有利于维持链式稳定

我们几乎可以使用这种方式完成全部的链式扩展。如果扩展较多时,可以采用下面的方式,进一步清晰、简化代码:

代码语言:javascript
复制
    extension View {        func then(_ body: (inout Self) -> Void) -> Self {            var result = self            body(&result)            return result        }    }        func foregroundColor(_ color:UIColor) -> Self{        then{            $0.color = color        }    }

disabled

SwiftUI针对View预设了非常多的扩展,其中有相当的部分都是通过环境值EnvironmentValue来逐级传递的。通过直接响应该环境值的变化,我们可以在不编写特定TextFieldWrapper扩展的情况下,即可为其增加配置功能。

例如,View有一个扩展.disabled,通常我们会用它来控制交互控件的可操作性(.disable对应的EnviromentValueisEnabled)。

TextFieldWrapper中添加:

代码语言:javascript
复制
@Environment(\.isEnabled) var isEnabled

updateUIView中添加:

代码语言:javascript
复制
uiView.isEnabled = isEnabled

只需要两条语句,TextFieldWrapper便可以直接使用Viewdisable扩展来控制其是否可以录入数据。

还记得上文中介绍的context吗?我们可以直接通过context获取上下文中的环境值。因此支持原生的View扩展将一步简化。

无需添加@Environemnt,只需要在updateUIView中添加一条语句既可:

代码语言:javascript
复制
uiView.isEnabled = context.environment.isEnabled

查看源代码

在写本文时,在iOS15 beta下运行该代码,会出现AttributeGraph: cycle detected through attribute的警告,这个应该是iOS15的Bug,请自行忽略。

通过环境值来设置是一种十分便捷的方式,唯一需要注意的是,它会改变链式结构的返回值。因此,在该节点后的链式方法只能是针对View设置的,像之前我们创建的foregroundColor就只能放置在这个节点之前。

font

我们也可以自己创建环境值来实现对TextFieldWrapper的配置。比如,SwiftUI提供的font环境值的类型为Font,本例中我们将创建一个针对UIFont的环境值设定。

创建环境值myFont

代码语言:javascript
复制
struct MyFontKey:EnvironmentKey{    static var defaultValue: UIFont?}extension EnvironmentValues{    var myFont:UIFont?{        get{self[MyFontKey.self]}        set{self[MyFontKey.self] = newValue}    }}

updateUIVIew中添加:

代码语言:javascript
复制
uiView.font = context.environment.myFont

font方法可以有多种写法:

•同forgroundColor一样的对TextFieldWrapper进行扩展

代码语言:javascript
复制
    func font(_ font:UIFont) -> some View{        environment(\.myFont, font)    }

•对View进行扩展

代码语言:javascript
复制
extension View {    func font(_ font:UIFont?) -> some View{        environment(\.myFont, font)    }}

两种方式的链式节点的返回值都不再是TextFieldWrapper,后面应该接针对View的扩展。

查看源代码

onCommit

在版本2的代码中,我们为TextFieldWrapper添加了onCommit设置,在用户输入return时会触发该段代码。本例中,我们将为onCommit添加一个可修改版本,且不需要通过协调器构造函数传递。

本例中的技巧在之前都出现过,唯一需要提醒的是在updateUIView中,可以通过

代码语言:javascript
复制
context.coordinator.onCommit = onCommitcontext.coordinator.onEditingChanged = onEditingChanged

改变协调器内的变量。这是一种非常有效的在SwiftUI和协调器之间进行沟通的手段。

image-20210823091321562

查看源代码

避免滥用UIKit包装

尽管在SwiftUI中使用UIKit或AppKit并不麻烦,但是当你打算包装一个UIKit控件时(尤其是已有SwiftUI官方原生解决方案),请务必三思。

苹果对SwiftUI的野心非常大,不仅为开发者带来了声明+响应式的编程体验,同时苹果对SwiftUI在跨设备、跨平台上(苹果生态)也做出了巨大的投入了。

苹果为每一个原生控件(比如TextField),针对不同的平台(iOS、macOS、tvOS、watchOS)做了大量的优化。这是其他任何人都很难自己完成的。因此,在你打算为了某个特定功能重新包装一个系统控件时,请先考虑以下几点。

官方的原生方案

SwiftUI这几年发展的很快,每个版本都增加了不少新功能,或许你需要的功能已经被添加。苹果最近两年对SwiftUI的文档支持提高了不少,但还没到令人满意的地步。作为SwiftUI的开发者,我推荐大家最好购买一份javier开发的A Companion for SwiftUI。该app提供了远比官方丰富、清晰的SwiftUI API指南。使用该app你会发现原来SwiftUI提供了如此多的功能。

用原生方法组合解决

在SwiftUI 3.0版本之前,SwiftUI并不提供searchbar,此时会出现两种路线,一种是自己包装一个UIKit的UISearchbar,另外就是通过使用SwiftUI的原生方法来组合一个searchbar。在多数情况下,两种方式都能取得满意的效果。不过用原生方法创建的searchbar在构图上更灵活,同时支持使用LocalizedString作为placeholder。我个人会更倾向于使用组合的方案。

SwiftUI中很多数据类型官方并不提供转换到其他框架类型的方案。比如ColorFont。不过这两个多写点代码还是可以转换的。LocalizedString目前只能通过非正常的手段来转换(使用Mirror),很难保证可以长久使用该转换方式。

Introspect for SwiftUI

在版本2代码中,我们为TextFieldWrapper添加了clearButtonMode的设置,也是我们唯一增加的目前TextField尚不支持的设定。不过,如果我们仅仅是为了添加这个功能就自己包装UITextField那就大错特错了。

Introspect通过自省的方法来尝试查找原生控件背后包装的UIKit(或AppKit)组件。目前官方尚未在SwiftUI中开放的功能多数可以通过此扩展库提供的方法来解决。

比如:下面的代码将为原生的TextField添加clearButtonMode设置

代码语言:javascript
复制
        import Introspect        extension TextField {            func clearButtonMode(_ mode:UITextField.ViewMode) -> some View{                introspectTextField{ tf in                    tf.clearButtonMode = mode                }            }        }        TextField("name:",text:$name)           .clearButtonMode(.whileEditing)

总结

SwiftUI与UIKit和AppKit之间的互操作性为开发者提供了强大的灵活性。学会使用很容易,但想用好确实有一定的难度。在UIKit视图和SwiftUI视图之间共享可变状态和复杂的交互通常相当复杂,需要我们在这两种框架之间构建各种桥接层。

本文并没有涉及包装具有复杂逻辑代码的协调器同SwiftUI或Redux模式沟通交互的话题,里面包含的内容过多,或许需要通过另一篇文章来探讨。

希望本文能对你学习和了解如何将UIKit组件导入SwiftUI提供一点帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 肘子的Swift记事本 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 在SwiftUI中使用UIKit视图
    • 基础
      • 生命周期
      • UIViewRepresentable协议
      • 黑匣子
      • 协调器
    • 包装UITextField
      • 版本1.0
      • 版本2.0——添加设定
    • SwiftUI 风格化
      • foregroundColor
      • disabled
      • font
      • onCommit
    • 避免滥用UIKit包装
      • 官方的原生方案
      • 用原生方法组合解决
      • Introspect for SwiftUI
    • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档