SwiftUI是Apple的声明式UI框架,可在其所有软件平台上运行,它虽然很年轻,但充满了问题,但是一旦掌握了它,魔术般的简单和疯狂的快速构建即可。
因此,我们真的不应该问它是否完全是“准备生产”的。相反,我们必须根据我们的具体情况和项目评估这是否是一项战略选择。
您应权衡一些变量,以决定是否在生产应用程序中使用SwiftUI。
如果您以前从未使用过SwiftUI(或其他声明性UI框架),请不要指望实际运行。尽管您可能已经听说过,它比UIKit等传统框架更易于使用,但仍有大约一百万个警告。
首先,如此简单的声明式风格也具有陡峭的学习曲线。如果您习惯了UIKit(或几乎在任何其他上下文中进行编程),您的习惯将以您甚至没有想到的方式抵制拥抱该范式。
看起来很简单,对不对?您在初始化程序中传递按钮的标题,并传递按钮动作的结束符。大。
但是现在让我们说您想在点击按钮时模态显示视图,如下所示:
换句话说,按钮正在执行“呈现”视图控制器的动作。但是在SwiftUI中,所有内容都声明为视图状态的函数。除了作为已声明的视图状态范围的修改之外,没有“做某事”的概念。因此,首先,我们需要将按钮添加到视图中:
struct ParentView:View {var body:some View {Button(“ Present View”){///在此处插入按钮动作}}}
然后,我们需要一个变量来跟踪呈现的视图的状态,并且我们必须在按钮的闭合处修改该变量。
struct ParentView:View {@State private var isPresenting:Bool = false var body:一些View {Button(“ Present View”){self.isPresenting = true}}}
但是,视图实际显示在哪里?我们还需要在主体中声明它,并将其附加到我们的变量中。 SwiftUI使用“工作表”修饰符来模态呈现视图:
struct ParentView:View {@State private var isPresenting:Bool = false var body:一些View {Button(“ Present View”){self.isPresenting = true} .sheet(isPresented:$ isPresenting){ChildView()}}}
而且sheet修改器实际上可以附加到视图声明中的多个位置上,它与按钮本身无关。尽管并非所有以下选项都可取,但它们各自的功能相同(在撰写本文时):
struct ParentView:View {@State private var isPresenting:Bool = false var body:some View {VStack {Spacer()Image(systemName:“ sunrise”)Button(“ Present View”){self.isPresenting = true} Text(“轻按以模态显示视图。“)Spacer()} .sheet(isPresented:$ isPresenting){ChildView()}}}
struct ParentView:View {@State private var isPresenting:Bool = false var body:some View {VStack {Spacer()Image(systemName:“ sunrise”).sheet(isPresented:$ isPresenting){ChildView()} Button(“ Present View“){self.isPresenting = true} Text(”点击以模态显示视图“)Spacer()}}}
关键是,如果您习惯了UIKit和其他类型的命令式编程(就像我们大多数人一样),以这种方式呈现视图就感觉很奇怪和不自然。
尽管这个特定示例可能看起来不算什么,但随着您的应用程序变得更加复杂,并且您发现自己尝试实现较少的基本功能,感觉会更加复杂。
在考虑在生产应用程序中使用它之前,我在SwiftUI上进行了六个月的辅助项目。而且我遇到了各种与它的声明性及其他相关的怪异问题。直到我对它有了直觉,我才能够利用它所能提供的生产率提高。
如果您还不在那里,则应避免尝试在时间紧张的功能或项目上使用SwiftUI。您可能需要花费更多的时间才能将头转向实现非平凡功能所需的范式转换。但是,一旦这样做,它就可以让您(在某些事情上)前所未有地燃烧。
SwiftUI的另一个明显问题是它还很不完整(有时很古怪),特别是支持iOS 13的版本。因此,如果您选择在某些情况下使用它,则必须采用多种变通方法来获得。一些非常基本的UIKit功能。
假设您需要在应用程序中显示货币列表。这很简单。
但是,如果您希望插入行之间的分隔线,使它们从文本的开头而不是图标开始(在“设置”和许多其他应用中就是这种情况),该怎么办?你不能您也无法将背景图片或颜色添加到整个列表,也无法添加其他基本设置(例如自定义滑动操作)。好吧,严格来说,您可以,但是您不能直接做到这一点。您将需要使用诸如UIAppearance之类的API或诸如Introspect之类的第三方工具来采用变通方法,该工具会迭代视图层次结构以查找相关的UIKit视图。谁知道随着iOS更新,什么解决方法将失效。最重要的是,如果要加载指示器(UIActivityIndicatorView)或共享表(UIActivityViewController)或其他各种标准组件,则需要使用UIViewControllerRepresentable或UIViewRepresentable自己移植。一段时间后,它可能会变老。综上所述,SwiftUI仍然可能是您应用程序的合理选择。如果您可以满足某些设计要求,或者可以采取一些变通办法承担较小的风险,则某些类型的应用程序在开发方面的速度提升可能仍会超出您遇到的所有怪癖。而且,如果有某些视图您绝对不能弯曲,那么您仍然可以在UIKit中实现孤立的视图并从SwiftUI呈现它们。无论如何,您可能都不得不混合搭配(例如,对于活动指标之类的东西),因此拥有少量并非纯粹是SwiftUI的自定义视图并不是一件容易的事。
在某些情况下,使用SwiftUI构建的视图的性能将不如它们的UIKit。根据您要用视图完成的工作,这可能不行。例如,假设您要显示一组项目,这些项目可以像在Netflix或Apple Music应用程序中看到的那样水平滚动:
您可能会考虑使用UICollectionView,它会自动回收移出屏幕的视图。但是为了做到这一点,您必须使用UIViewRepresentable将其引入SwiftUI,这是可行的,但可能很耗时。或者,您可以仅使用带有水平堆栈的滚动视图,例如:
struct HorizontalCollection:View {///要在集合中显示的项目让item:[Item] var body:一些View {ScrollView(.horizontal){HStack {ForEach(items){ItemView(item)中的项目}}}} }}
与收集视图不同,所有项目将立即加载到内存中。这意味着,如果您有1k项,则1k视图将在加载时驻留在内存中–确实会降低性能。即使您使用iOS 14中引入的LazyHStack,也不会在视图出现之前加载视图,但是此后它们将保留在内存中。
就是说,如果您一次只收集少量便宜的商品,那么简单的SwiftUI方法可能是完全合理的。它可能不会对UX产生明显影响,也不会由于内存使用过多而导致iOS终止应用程序的风险。另一方面,如果您的收藏集正在显示一堆图像或大量甚至看似便宜的物品,则您可能不得不依靠某些UIKit组件的开箱即用的性能优势。
关键是要意识到要尽早在实现中考虑使用的SwiftUI组件的性能差异。如果您的应用只有几个视图,需要像集合视图这样的视图,那么偶尔的UIViewRepresentable实现可能是可行的方法。但是,如果您的大多数应用程序需要大量的单元重用和延迟加载以及许多细微的自定义,则与专门使用UIKit相比,与SwiftUI战斗所花费的时间可能更多。
始终存在某些功能的实现可能会从一个版本的iOS中断到另一个版本的风险。但是,使用SwiftUI时,此类错误的程度似乎更加严重。
例如,我最近用Xcode 12构建了一个可以在iOS 13上完美运行的SwiftUI应用,其中一堆东西破了,其中包括两个大项目:
从同一屏幕显示的模态视图不正确。也就是说,提出了错误的观点。
事实证明,SwiftUI重新实现了其呈现和管理工作表的方式。因此,以前从单个视图呈现和消除多个模态的方法不再起作用。
struct ParentView:视图{@State私有var isPresenting:Bool = false @State私有var当前:PresentedViewOption var主体:一些视图{VStack {Button(“ Present View 1”){self.current = .option1 self.isPresentingView = true} Button(“ Present View 2”){self.current = .option2 self.isPresentingView = true} Button(“ Present View 3”){self.current = .option3 self.isPresentingView = true}} .sheet(isPresented:$ isPresentingView ){self.view(for:current)}} ...}
使用上面的代码,“ isPresenting”和“ selection”状态变量都没有按预期进行更新,因此我不得不改用另一个工作表修饰符重新实现代码,并调整将状态传递给子视图的方式。
由于我的应用程序相对较小,因此只花了几个小时就解决了iOS 13和iOS 14的所有错误。但是,如果您的应用程序很大且具有大量视图(和开发人员),则成本和对于您的约束来说,这类修复程序的复杂性可能是不可接受的(至少在SwiftUI演变的这一阶段)。
毋庸置疑,SwiftUI仅支持iOS 13及更高版本。因此,使用该框架集成到您的应用程序中的任何内容都无法在使用旧版iOS的用户上运行。
截至2020年6月,Apple报告称,过去4年内推出的设备中有92%使用iOS 13,而所有设备(包括最旧的设备)中有81%使用iOS 13。 19%的用户。
尽管这从根本上来说是一项商业决策,但作为开发人员,我们的职责是告知利益相关者和产品经理选择一个或多个选项所花费的时间和成本。虽然8%-19%的用户绝对重要,但您需要将其与支持这些用户的成本和时间影响进行比较。在许多情况下,增加的费用超过收益。例如,如果您是团队规模较小且预算有限的早期采用者,则以较少的用户获得MVP可能远远超过了尽早吸引每个可能的用户的重要性,尤其是在技术选择使开发速度大大提高的情况下。
最后,尽管以上考虑因素都提到了这一点,但值得直接解决。
您可以控制在给定应用程序中使用多少SwiftUI,并且始终可以在需要时使用UIKit。您的选择权实际上分为两类,每类都有自己的风险特征。
这是在生产应用中使用SwiftUI的最低风险方法。 UIHostingController允许您在SwiftUI中布局各个视图和视图控制器,并像在任何其他视图或视图控制器中一样将它们呈现回UIKit。这是一种充分利用SwiftUI好处的好方法,而无需像以前那样受到许多怪癖的困扰。
如果您是SwiftUI的新手,这里唯一需要注意的是时间限制。在内部化声明式结构之前,某些项目需要花费很长时间才能实施,并且可能导致看似莫名其妙的错误或崩溃,从而需要更多时间才能弄清。在期限有限的项目上,您可以自己按时完成类似的工作。在您达到一定程度的熟悉度之前,即使将其引入一项功能也可能会导致意外的挫折。
例如,假设您有一个UITableView,您只想为其单元格布局使用SwiftUI。从技术上讲这是可行的,并且SwiftUI使布局代码比处理自动布局约束更简单。因此,您继续实现它,并将SwiftUI视图包装在UIHostingController中,以从UIKit代码中实例化它。问题是重用。您只能以编程方式访问SwiftUI初始化程序以及UIHostingController自动生成的视图。那么,如何在每次单元出队时不实例化基础视图而更改基础视图?不知道您是否可以以非骇客的方式使用它,因此您最终可能会废弃SwiftUI视图并仍然使用自动布局。
您想在空闲时间遇到这样的问题,而不是在最后期限悬而未决的时候!
任何将要投入生产的应用都可能在某个时候需要引用UIKit。如前所述,SwiftUI的基本实现(例如加载指示器和共享表)需要参考UIKit,并且有时可能需要性能优化的组件才能实现可接受的滚动和内存使用。除非您的应用程序非常简单,否则无论如何它都不可能纯粹是SwiftUI。
而且由于有了UIViewControllerRepresentable和UIViewRepresentable,您可以根据需要获得UIKit的好处。但是同样,您应该等到对SwiftUI有了一定的经验,然后再将其作为应用程序的基础,并确保您的设计要求没有那么细微的差别,以致您不值得花时间来处理完全是SwiftUI的怪癖。在这一点上,具有较小团队的较小应用程序更可能使用SwiftUI,而更复杂,具有丰富设计要求的较大应用程序可能更安全地基于UIKit。
我个人喜欢使用SwiftUI。声明式的样式,易于组合的视图,跨平台访问以及各种其他功能,使许多类型的视图的开发变得极为简单,优雅和快速。我非常喜欢它,以至于随着框架的成熟和不断减少的麻烦,即使在某些生产应用中,我也愿意忍受很多怪癖。
在当前的生产应用程序即将发布的版本中,几乎整个应用程序都是用SwiftUI编写的。我遇到了上面提到的许多问题,但是对于我来说,作为一个已经经历了思维模式转变的人,生产率的提高值得一小笔挫折,而这实际上并不难解决。也就是说,这是针对寻求早期产品市场适应性的相对较小的应用程序。如果您已经拥有数百万的用户,精致的功能和庞大的团队,那么将SwiftUI添加到其中可能会比值得的事更加头痛。您需要权衡自己的细微差别和企业可以承受的风险。