您想知道的有关SwiftUI的所有信息,但又害怕问


你好 我叫Renat,我正在iOS中开发订阅分析服务-Apphud。


如您所知,Apple在WWDC 2019上引入了其新的SwiftUI框架,该框架在将来被设计用来替代(或不可以替代)熟悉的UIKit。 SwiftUI允许您以声明式描述应用程序接口,并大大减少了代码量。


苹果已经介绍了一些有趣的英语教程 ,其中包括许多示例。 我将尝试以问答的形式谈论新框架。 所以走吧


开始之前


要使用SwiftUI,您需要下载Xcode 11 Beta 。 您还必须是注册的Apple开发人员。 拥有最新的macOS Catalina是可取的,但不是必需的。 没有它,Canvas将不可用。


因此,在Xcode 11 Beta中,创建一个新项目,并确保选中“ Use SwiftUI”。


问与答


Interface Builder到哪里去了?


SwiftUI不再需要Interface Builder-它已被Canvas取代, Canvas是与代码密切相关的交互式界面编辑器。 编写代码时,将自动生成其在画布中的可视部分,反之亦然。 非常方便,最重要的是安全。 现在,由于您忘记使用变量更新@IBOutlet ,因此您的应用程序不会@IBOutlet 。 在本文中,我们不会涉及canvas ,只会考虑代码。


应用程序启动是否更改?


是的,现在应用程序界面中的初始对象不是UIWindow ,而是新的UIScene类(或其后代UIWindowScene )。 然后将一个窗口添加到场景中。 这些更改不仅会影响SwiftUI,还会影响整个iOS 13。


创建项目时,您将看到AppDelegateSceneDelegateContentView文件SceneDelegate - UIWindowScene类的委托,用于在应用程序中控制场景。 强烈让人联想到AppDelegate


SceneDelegate类在Info.plist中指定
SceneDelegate类在Info.plist中指定


在委托方法scene: willConnectTo: options:创建一个窗口和一个根UIHostingController ,其中包含一个ContentViewContentView是我们的“主页”页面。 所有开发都将在此类中进行。


View与UIView有何不同?


打开ContentView.swift ,将看到一个ContentView容器声明。 如您viewDidAppear viewDidLoad viewDidAppear中没有熟悉的viewDidLoadviewDidAppear方法。 屏幕的基础不是UIViewController ,而是View 。 首先要注意的是ContentView是一个接受View协议的struct 。 是的, View现在已经成为一种协议,并且非常简单。 您需要在ContentView实现的唯一方法是描述变量body 。 您的所有子视图和自定义视图都必须接受View协议,即必须具有body变量。


什么是身体?


Body是直接添加所有其他子视图的容器。 这有点类似于html页面中的正文 ,其中html页面是ContentViewBody应该始终只有一个后代,以及任何接受View协议的类。


 struct ContentView: View { var body: some View { Text("Hello, world!") } } 

不透明的返回类型是什么?


some TypeName的构造是Swift 5.1的一项创新,称为不透明返回类型 。 它用于以下情况:对于我们来说不重要的是返回哪个对象,主要是它支持指定的类型,在这种情况下为View协议。


如果我们只是简单地写var body: View ,那么这意味着我们应该返回ViewAny类也不起作用,因为我们必须执行类型转换操作( 使用 as! 运算符 )。 因此,他们在协议名称前面提出了some特殊字词来表示不透明的返回类型 。 除了View我们还可以返回TextImageVStack ,因为它们都支持View协议。 但是必须有一个确切的元素:当尝试返回多个View编译器将引发错误。



尝试将多个元素返回到主体时发生编译错误


方括号内的语法是什么,addSubview在哪里?


Swift 5.1引入了以声明式样式将对象分组为一个整体的功能。 这类似于包块中的数组,但是元素从新行枚举而没有逗号和return 。 该机制称为函数构建器


这在SwiftUI中被广泛使用。 他们基于Function Builder,使ViewBuilder一个声明性的界面设计器。 使用ViewBuilder我们不再需要为每个元素编写addSubview只需在闭合块内的新行中列出所有View 。 SwiftUI将元素添加并分组到一个更复杂的父容器中。


 @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) @_functionBuilder public struct ViewBuilder { /// Builds an empty view from an block containing no statements, `{ }`. public static func buildBlock() -> EmptyView /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through /// unmodified. public static func buildBlock<Content>(_ content: Content) -> Content where Content : View } 

SwiftUI框架中的ViewBuilder广告


如何添加UILabel,UIImageView和其他元素?


元素的创建非常简单:每个View需要从新行编写,并使用修饰符功能( view修饰符 )更改外观。 我们熟悉的修饰符和函数之间的区别在于,它们始终返回容器对象而不是void 。 因此,我们可以通过该点创建整个修饰符链。


 var body: some View { VStack{ Text("World Time").font(.system(size: 30)) Text("Yet another subtitle").font(.system(size: 20)) } } 

但是,并非所有控件和View在SwiftUI中都有它们的类似物。 以下是UIKit及其类似物的部分类列表:


  • UITableView > List


  • UICollectionView没有类似物


  • UILabel > Text


  • UITextField > TextField


  • UIImageView > Image


  • UINavigationController > NavigationView


  • UIButton > Button


  • UIStackView > HStack / VStack


  • UISwitch > Toggle


  • UISlider > Slider


  • UITextView没有类似物


  • UIAlertController > Alert /操作ActionSheet


  • UISegmentedControl > SegmentedControl


  • UIStepper > Stepper


  • UIDatePicker > DatePicker



屏幕之间如何导航?


导航控制器承担特殊的NavigationView的角色。 只需将代码包装在NavigationView{} 。 并且过渡动作本身可以添加到特殊的NavigationLink按钮中,该按钮可以按条件的DetailView屏幕。


 var body: some View { NavigationView { Text("World Time").font(.system(size: 30)) NavigationLink(destination: DetailView() { Text("Go Detail") } } } 

如何模态表达新观点? 例如,这是使用工作构造完成的:


 Button(action: { print("Button Pushed") self.show_modal = true }) { Text("Present Modal") }.sheet(isPresented: self.$show_modal) { ModalView() } 

如上所述, body不仅可以返回View的实例,还可以返回接受此协议的任何其他类。 这使我们有机会不DetailView ,甚至DetailView TextImage


如何在屏幕上排列元素?


元素彼此独立排列,可以在VStack内部垂直放置,在VStack水平HStack ,在另一个ZStack上方放置。 ScrollViewListView也可供我们使用。 您可以交替并共享这些容器以获得任何网格元素。


通过将容器相互组合,可以得到带有大量附件的相当大的树。 但是,SwiftUI专门为此进行了优化,因此深度容器嵌套不会影响性能。 这在wwdc视频中有说明从15:32开始 )。


 var body: some View { NavigationView { VStack { NavigationLink(destination: LargeView(timeString: subtitle)) { Text("See Fullscreen") } Text("World Time").font(.system(size: 30)) } } } 

如何显示导航栏?


仅仅声明NavigationView不够的;您必须为导航栏指定导航标题和样式。


 NavigationView { VStack{}.navigationBarTitle(Text("World Time"), displayMode: .inline) } 

注意, navigationBarTitle函数不是在NavigationView上调用的,而是在其内部View上调用的。 DisplayMode是一个参数,指示导航栏的样式:大或标准。


有viewDidLoad方法的类似物吗?


如果要在初始化View时执行代码,可以通过添加onAppear {}函数来实现。 可以将OnAppear添加到任何View ,例如,添加到VStack 。 在此示例中,当容器出现在屏幕上时,将向服务器发出http请求。


 struct ContentView : View { @State var statusString : String = "World Time" var body: some View { NavigationView { VStack { NavigationLink(destination:DetailView()) { Text("Go Detail") } Text(statusString).font(.system(size: 30)) }.onAppear { self.loadTime() } } } func loadTime(){ NetworkService().getTime { (time) in if let aTime = time { self.statusString = "\(aTime.date())" } } } } 

我们调用loadTime函数,该函数从服务器请求当前时间并返回WorldTime模型。 我们不会在NetworkService类中循环学习,您可以查看已下载源代码的所有代码。 文章末尾的链接。

呈现变量var statusString以便稍后分配当前时间。 该变量具有特殊的@State属性。 他是什么意思


属性包装器或@State@State


Swift 5.1引入了所谓的属性包装器 (或属性委托 )。 在SwiftUI中, 属性包装器用于使用我们自己的变量(例如, Toggle开关的值)更新或绑定视图参数之一。


@State属性是放置在变量声明之前的特殊属性。 这使我们无需附加代码即可自动跟踪属性更改。 在上面的示例中,一旦我们更新statusString值,文本“世界时间”将更改为当前日期。


要绑定值( Properties Binding ),我们可以在代码本身的变量名称之前指定一个特殊字符$


 struct DetailsView: View { @State var changeToggle: Bool var body: some View { Toggle(isOn: $changeToggle) { Text("Change Toggle") } } } 

通过更改开关的位置,变量的值也将更改。


属性包装器是SwiftUI的一个非常重要的组成部分,我只是顺便提到了它们。 要更详细地了解属性包装器,请在此处 (第37分钟), 此处 (第12分钟)和此处 (第19分钟)观看wwdc的视频。


如何向运行时添加视图?


立即值得注意的是,您无法随时按字面意义添加视图 。 SwiftUI是一个声明式框架,可呈现整个视图 。 但是,您可以在体内设置各种条件,并在它们改变时更新视图的状态。 在此示例中,我们使用最简单的@State – if变量isTimeLoaded


 struct ContentView : View { @State var statusString : String = "World Time" @State var isTimeLoaded : Bool = false var body: some View { NavigationView { VStack { if isTimeLoaded { addNavigationLink() } Text(statusString).font(.system(size: 30)).lineLimit(nil) }.navigationBarTitle(Text("World Time"), displayMode: .inline) }.onAppear { self.loadTime() } } func addNavigationLink() -> some View { NavigationLink(destination: Text("124!!!")) { Text("Go Detail") } } func loadTime(){ NetworkService().getTime { (time) in if let aTime = time { self.statusString = "\(aTime.date().description(with: Locale.current))" self.isTimeLoaded = true } } } } struct DetailView : View { var timeString : String var body : some View { Text(timeString).font(.system(size: 40)).lineLimit(nil) } } 

顺便说一句,您是否注意到addNavigationLink()函数addNavigationLink()单词return ? 这是Swift 5.1的另一项创新-适用于具有单个表达式的函数,现在可以选择编写return 。 但是你可以写。


结论


这只是SwiftUI问答的一部分。 我研究了一般性问题,希望本文能帮助初学者了解该框架的要点。 SwiftUI仍然是原始的,但无疑会得到改进。


逻辑问题是: 值得学习UIKit吗? 当然可以 。 UIKit是在iOS上进行编程的基础,并将进一步发展。 而且,许多SwiftUI组件都是UIKit的包装。 好吧,到目前为止,还没有用于SwiftUI的库,框架和Pod。 一切都必须由你自己编写。 因此,最好研究两种开发方法,这样您才能成为更有价值的开发人员。


您可以在此处下载项目源。


感谢您阅读本文的结尾。 希望您觉得它有用。


读什么?


Source: https://habr.com/ru/post/zh-CN455970/


All Articles