前言
后续这个SwiftUI分类的文章全部都是针对SwiftUI的日常学习和理解写的,自己利用Swift写的第二个项目也顺利上线后续的需求也不是特着急,最近正好有空就利用这段时间补一下自己对SwiftUI的理解,这个过程当中正好把整个学习过程记录下来,方便自己查阅,也希望能给需要的同学一点点的帮助。由于自己还欠着RxSwift的帐,这次也是想着先放弃别的账务(欠的的确挺多的)先全心全意的把这两块的帐给补补,希望补上这笔账之后自己对Swift的理解也能上一个台阶,对Siwft的理解自认为还是感觉欠缺的,不算是真的深入的掌握,我对SwiftUI也是在学习当中,现在能查阅的关于SwiftUI的资料很多是需要收费的,遇到问题只能想办法努力解决,有写的不钟意的地方,希望多加指正!
这两张图相信看过苹果官方SwiftUI介绍文档并且跟着写了一遍代码的同学应该不陌生,当然我们的目的不是说这两篇的代码,这个具体的可以到下面连接去查看,我自己跟着写了一遍之后对SwiftUI也是有了一个基本的认识。我们在后面遇到的一些问题也会回到这个官方文档进行一些验证。
SwiftUI
在进入项目搭建先说说我自己对SwiftUI的一个基本的认知:
SwiftUI我觉得对iOSer来说最大的是开发UI模式的优化,针对一个需求或者是一个新的项目我们基本上都是从写UI开始的,根据设计图再编造一些假数据来做,只是在写的过程中它的及时效果也都是脑补!SwiftUI我觉得能改变的痛点就是这点,能让我们实时预览自己写的UI效果,保持我们代码和界面的同步性!
声明式UI:关于它的理解往细了说,的确能专门写一篇文章出来,下面这篇文章能很好的帮助理解我们现在使用的命令式和SwiftUI采用的声明式UI之间的区别。
跨平台: 在最新的swiftUI 5.1中,我们创建一个MultilPlatform App有了下面这些区别:
·Before
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). // Create the SwiftUI view that provides the window contents. let contentView = BaseTabbarView() // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: contentView) self.window = window window.makeKeyAndVisible() } }
·After
import SwiftUI @main struct MultiPlatformApp: App { var body: some Scene { WindowGroup { ContentView() } } }
SwiftUI 将整个原有的苹果平台差异部分抽象为 App 和 Scene 部分,可以看到Swift5.1之后在完全无需引入UIKit 的情况下我们就创建了一个多平台的App工程,代码也从原本的基于 UI/NS HostViewController 变成了基于 App的声明式描述。这意味着我们后续在UI布局系统上可以逐渐摆脱对传统命令式 UI 编程的依赖。达到真正的平台无关!
下面开始我们最常见的项目场景的搭建,一点点的学习一下SwiftUI里面的一些知识。
实时预览:
这个画布的显示控制是在下图标注的地方,当然当你创建一个SwiftUIView的时候它是默认创建展示的,要是不见了就在下面去找:
画布的代码控制是在你每个View 的最后面的遵循了PreviewProvider协议的结构体里面的,就像下面我们要说的基本的Tab的预览:
struct BaseTabbarView_Previews: PreviewProvider { /// 预览视图,你试着在这里多添加两个看看效果呀 static var previews: some View { BaseTabbarView() } }
从最常见的场景搭建开始
在我们的日常开发中,标签(TabBar)+ 导航(Na)形式的模式是随处可见的,我们这次的目的是利用SwiftUI搭建这样一个场景构建一个基本的应用,包括登录和数据处理以及iOS常见控件在SwiftUI中的一些具体的使用,这个项目会随着学习进度慢慢的把所有的内容都基本的补齐,下面是最基本的导航+标签的git效果。
View
我自己觉得,要想从UIKit转换到SwiftUI,需要我们最先转变的概念就是 Controller -> View 的一个改变,在使用SiwftUI写UI的过程中,基本上是不在需要我们向UIkit那样去创建Controller来管理View,在SwiftUI中最常见的就是View。
在UIKit中我们的导航、标签都是通过控制器来管理,但是在SwiftUI中他们分别是通过NavigationView+TabView管理的,我们得在认识上有一个基本的转变,从Controller到View的认识转变。
认识一下NavigationView,先看看下面的代码:
NavigationView{ NavigationLink.init( destination: Text("Destination"), label: { Text("Navigate") }) .navigationTitle(title) /// 留意下这个显示模式 displayMode 分三种,具体的可以点进去看看 /// inline 就是我们常见的模式 /// .navigationBarTitle(title,displayMode: .inline) }
大概解析一下上面代码的 NavigationLink,它是用来控制View之间的跳转的:
destination:是跳转的目标View,我们在做一些数据传递的时候一般都是在这里说明的。
label:对它的理解简单点就是下个View的内容
再认识一下TabView,下面代码是SwiftUI对它的基本定义和描述:
/// A view that switches between multiple child views using interactive user /// interface elements. /// /// To create a user interface with tabs, place views in a `TabView` and apply /// the ``View/tabItem(_:)`` modifier to the contents of each tab. The following /// creates a tab view with three tabs: /// /// TabView { /// Text("The First Tab") /// .tabItem { /// Image(systemName: "1.square.fill") /// Text("First") /// } /// Text("Another Tab") /// .tabItem { /// Image(systemName: "2.square.fill") /// Text("Second") /// } /// Text("The Last Tab") /// .tabItem { /// Image(systemName: "3.square.fill") /// Text("Third") /// } /// } /// .font(.headline) /// /// Tab views only support tab items of type ``Text``, ``Image``, or an image /// followed by text. Passing any other type of view results in a visible but /// empty tab item. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *) public struct TabView<SelectionValue, Content> : View where SelectionValue : Hashable, Content : View { /// Creates an instance that selects from content associated with /// `Selection` values. public init(selection: Binding<SelectionValue>?, @ViewBuilder content: () -> Content) /// The content and behavior of the view. public var body: some View { get } /// The type of view representing the body of this view. /// /// When you create a custom view, Swift infers this type from your /// implementation of the required `body` property. public typealias Body = some View }
关于这个TabView在定义的上面苹果是给出了一个使用的基本的示例的,要和我们项目中经常使用的模式要绑定在一起的的话就是结合他的初始化方法绑定一个@State变量使用的,具体的我们会在后面的代码中说的,关于这个@State我在项目Demo中有具体的解释,包括像@bind类型或者是@EnvironmentObject这些关键字我们肯定是得需要学习的,就像我们从OC转到Swift一样。
简单看看Na+Tb的代码
从SceneDelegate开始, 根控制器就是 UIHostingController,我们需要做的第一步就是设置它的根视图 rootView
// Create the SwiftUI view that provides the window contents. let contentView = BaseTabbarView() // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: contentView) self.window = window window.makeKeyAndVisible() }
接下来是TabView的代码,需要注意的是我们点击item的时候视图切换的绑定状态,基本上在代码注释中我说的比较清楚了,应该能理解的。
import SwiftUI struct BaseTabbarView: View { /// 理解State这个属性 https://www.cnblogs.com/xiaoniuzai/p/11417123.html /* 通过使用 @State 修饰器我们可以关联出 View 的状态. SwiftUI 将会把使用过 @State 修饰器的属性存储到一个特殊的内存区域,并且这个区域和 View struct 是隔离的. 当 @State 装饰过的属性发生了变化,SwiftUI 会根据新的属性值重新创建视图 */ @State private var selectedTab = 0 var body: some View { /// public init(selection: Binding<SelectionValue>?, @ViewBuilder content: () -> Content) TabView(selection: $selectedTab){ HomeView(title: "首页") .tabItem { Image(selectedTab == 0 ? "icon_home_selected" : "icon_home") Text("首页") } .onTapGesture { selectedTab = 0 } .tag(0) ServiceView(title: "周边") .tabItem { Image(selectedTab == 1 ? "icon_around_selected" : "icon_around") Text("周边") } .onTapGesture { selectedTab = 1 } .tag(1) AroundView(title: "服务") .tabItem { Image(selectedTab == 2 ? "icon_service_selected" : "icon_service") Text("服务") } .onTapGesture { selectedTab = 2 } .tag(2) MineView(title: "我的").environmentObject(NavigationAction()) .tabItem { Image(selectedTab == 3 ? "icon_mine_selected" : "icon_mine") Text("我的") } .onTapGesture { selectedTab = 3 } .tag(3) /// 这个着重颜色设置可以设置tabbaritem字体的颜色 }.accentColor(.blue) } } struct BaseTabbarView_Previews: PreviewProvider { /// 预览视图,你试着在这里多添加两个看看效果呀 static var previews: some View { BaseTabbarView() } }
在上面的代码中,点击的切换我们是通过View的onTapGesture方法通过改变selectedTab 来进行控制的,然后item具体是要显示那种风格的图片也是通过selectedTab经过三目运算符控制,具体得我们这里不多解释了废话了,看代码我相信都能理解。
下面的参考文章相信能帮助我们更好的理解一下,SwiftUI!
参考文章: