SwiftUI技术探究之SwiftUI介绍

1,742 阅读14分钟

本文主要内容

一.SwiftUI开胃菜
二.SwiftUI简介
三.常见问题
四.SwiftUI与UIKit的联系
五.Views&Modifies Library 六.LaunchScreen&AccentColor

一.SwiftUI开胃菜

用SwiftUI实现如下界面效果

AppStore截图.PNG

关键代码
ContenView.swift

import SwiftUI

let ScreenWidth = UIScreen.main.bounds.width
let ScreenHeight = UIScreen.main.bounds.height

let img1 = "https://gp-dev.cdn.bcebos.com/gp-dev/upload/file/source/d642fa9e1210194ccc228947af9283c7.png"
let img2 = "https://gameplus-platform.cdn.bcebos.com/gameplus-platform/upload/file/source/e04e9258a65f74c3c045b8a152b56551.jpg"

struct ContentView: View {
    @State var items: [String] = Array(0...2).map { "Item\($0)" }
    var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    // 分割线:上/前/后20
                    Divider().padding(EdgeInsets.init(top: 20, leading: 20, bottom: 0, trailing: 20))
                    
                    // 上部分:可左右滑动
                    TabView {
                        forEach($items, id: \.self) { _ in
                            // 重磅更新、地下城堡3:魂之诗、新地图“废械阵”上线、大图、大图底部的上方覆盖内容
                            VStack {
                                HStack {
                                    Text("重磅更新").foregroundColor(.blue).fontWeight(.bold)
                                    Spacer()
                                }
                                
                                HStack {
                                    Text("地下城堡3:魂之诗").font(.title)
                                    Spacer()
                                }
                                
                                HStack {
                                    Text("新地图“废械阵”上线").foregroundColor(.gray).font(.title2)
                                    Spacer()
                                }
                                
                                AsyncImage(url: URL.init(string: img1)) { img in
                                    img.resizable()
                                } placeholder: {
                                    ProgressView()
                                }
                                .frame(height: 220)
                                .cornerRadius(10)
                                
                                // 覆盖内容:小图、地下城堡3:魂之诗、暗黑文字地牢探险、获取、App内购买
                                .overlay {
                                    VStack {
                                        Spacer()
                                        HStack {
                                            AsyncImage(url: URL.init(string: img2)) { img in
                                                img.resizable()
                                            } placeholder: {
                                                ProgressView()
                                            }
                                            .frame(width: 50, height: 50).cornerRadius(12)
                                            VStack {
                                                HStack {
                                                    Text("地下城堡3:魂之诗")
                                                    .foregroundColor(.white)
                                                    .font(.title3)
                                                    Spacer()
                                                }
                                                HStack {
                                                    Text("暗黑文字地牢探险")
                                                    .foregroundColor(.gray)
                                                    Spacer()
                                                }
                                            }
                                            Spacer()
                                            
                                            VStack {
                                                Button("获取") {
                                                    print("get")
                                                }
                                                .foregroundColor(.white)
                                                .font(Font.system(.title2).bold()).frame(width: 94, height: 40).background(.gray.opacity(0.8))
                                                .cornerRadius(20)
                                                
                                                Text("App内购买")
                                                .foregroundColor(.gray)
                                                .font(.footnote)
                                            }.padding(.top, 15)
                                        }
                                        .padding(EdgeInsets.init(top: 0, leading: 16, bottom: 6, trailing: 16))
                                        .background(LinearGradient(colors: [.black.opacity(0.5), .clear], startPoint: .bottom, endPoint: .top)).cornerRadius(12)
                                    }
                                }
                            }.padding(20)
                        }
                    }
                    .frame(width: ScreenWidth, height: 320)
                    .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
                    Divider().padding(EdgeInsets.init(top: 25, leading: 20, bottom: 0, trailing: 20))
                    
                    VStack {
                        HStack {
                            Text("我们都在玩").font(.title).bold()
                            Spacer()
                            Button("查看全部") {
                                print("all")
                            }.font(.title2)
                        }
                        
                        HStack {
                            Text("探索本周游戏热点").foregroundColor(.gray).font(.title2)
                            Spacer()
                        }
                    }.padding(EdgeInsets.init(top: 0, leading: 20, bottom: 0, trailing: 20))
                    
                    TabView {
                        ForEach($items, id: \.self) { _ in
                            List(0..<3) { _ in //禁止滑动
                                HStack {
                                    AsyncImage(url: URL.init(string: img1)) { img in
                                        img.resizable()
                                    } placeholder: {
                                        ProgressView()
                                    }
                                    .frame(width: 68, height:68).cornerRadius(16)
                                
                                    VStack {
                                        HStack {
                                            Text("底下城堡3:魂之诗").font(.title3)
                                            Spacer()
                                        }
                                        HStack {
                                            Text("暗黑文字地牢探险").foregroundColor(.gray)
                                            Spacer()
                                        }
                                    }
                                    Spacer()
                                
                                    VStack {
                                        Button("获取") {
                                            print("get") }.foregroundColor(.blue).font(Font.system(.title2).bold())
                                        .frame(width: 94, height: 40).background(.gray.opacity(0.2))
                                        .cornerRadius(20)
                                    
                                        Text("App内购买").foregroundColor(.gray).font(.footnote)
                                    }
                                }
                            }.listStyle(InsetListStyle()) //.listRowSeparator(.hidden)
                        }
                    }
                    .frame(width: ScreenWidth, height: 270)
                    .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
                }
            }.navigationTitle("游戏")
        }
    }
}

实际效果图

截屏2022-08-25 09.16.21.png

二.SwiftUI简介

2.1、关于SwiftUI(了解)

2019年WWDC(苹果全球开发者大会“Worldwide Developers Conference”)大会上,苹果在压轴环节向大众宣布了基于Swift语言构建的全新UI框架——SwiftUI,开发者可通过它快速为所有的Apple平台创建美观、动态的应用程序
SwiftUI在iOS13、macOS10.15、tvOS13和watchOS6上运行,而且SwiftUI的运行速度优于UIKit,它减少了界面的层次结构,因此减少了绘制步骤,并且完全绕过了C oreAnimation,直接进入Metal

2.2、什么是SwiftUI(了解)

SwiftUi is a user interface toolkit that lets us design apps in a declarative way。可以理解为SwiftUI就是Swift+UI,一种描述式的构建UI的方式。相对于UIKit是一种使用指令式编程方式的UI框架,SwiftUI是一种描述式、使用声明式编程方式的UI框架。
SwiftUI是一个用户界面开发工具包,它让我们使用声明式(declarative)编程的方式设计应用。也就是说:我们只需要告诉SwiftUI,我们的UI应该长什么样子,当用户和它互动(i interact)时,我们的UI应该如何反应。例如:通过一个按钮,添加一行文字显示。如果使用指令式的UIKit,需要写一个函数,点击按钮时调用此函数,在函数内部读取值并且创建一个UILabel添加当前subView,可能导致状态相关的问题;而声明式的SwiftUI,只需要说明按钮,显示什么文字,需要做什么反应,设置好规则即可。 SwiftUI作为Apple在自家平台使用Swift语言打造的首个重量级系统框架,将为这个平台上用户界面的构建方式带来革命性的转变,它摒弃了从上世纪八十年代开始就一直被使用的指令式(imperative)编程的方式,转而向声明式编程的阵营,这提高了我们解决问题时所需要着手的层级,从而让我们可以将更多的注意力集中到更重要的创意方面。

三.常见问题

3.1、为什么苹果要推出SwiftUI(了解)

在SwiftUI出现之前,苹果不同的设备之间的开发框架并不互通,移动端的工程师和桌面端的工程师需要掌握的知识,有很大一部分是差异化的。比如iOS、macOS、watchOS,这种碎片化的开发体验无疑会大大增加开发者所需消耗的时间精力,也不利于构建跨平台的软件体验。苹果希望直接优化语言本身,并统一所有设备的开发体验,让开发者更容易上手,也更容易将心里的想法转化为运行的程序。
除了统一终端以外,苹果也在想方设法增加开发者的数量,提升单个应用质量。方式也非常符合第一性思维原则——降低开发的难度。所以先有了Swift,紧接着又推出了SwiftUI,因为之前的UIKit是有很大的局限性的,UIKit的基本思想要求ViewController承担绝大部分职责,它需要协调model、view以及用户交互,这带来了巨大的sideeffect以及大量的状态。如果没有妥善安置,它们将在ViewContr oller中混杂在一起,同时作用于view或者逻辑,从而使状态管理愈发复杂,最后甚至不可维护而导致项目崩溃。换句话说,在不断增加新的功能和页面后,同一个ViewController会越来月庞杂,很容易在意想不到的地方产生bug。而且代码纠缠在一起后也会大大降低可读性,影响维护和协作的效率。
SwiftUI使用了大量Swift的语言特性,特别是5.0之后新增的特性。Swift 5.1的很多特性几乎可以说都是为了SwiftUI量身定制的,比如Opaque return types、Property Delegate和Function builder等。
推出SwiftUI,基本就可以看作苹果在推广新语言的过程中一个里程碑式的节点。

image.png

3.2、SwiftUI的特点

  • 声明式语法
    在计算机科学的领域里面,抽象是一个很重要的概念。从底层的二进制逻辑门,到人类可以阅读和理解的编程语言之间,是由很多层的抽象将它们关联起来的。所谓抽象,简单解释就是通过封装组件,将底层细节打包并隐藏起来,从而明确逻辑、降低复杂度。就像把晶体管打包成逻辑门。在软件开发的过程中,工程师只需负责某个具体功能的实现,而其他人则通过开放的API使用该功能
    与以往的布局方式相比,声明式的页面开发无疑又加了一层抽象
    在UIKit框架中,由于坐标系统的存在,界面上的每一个元素都需要开发者进行布置。有时候计算量会非常大也非常繁琐,例如长宽的改变或是屏幕可视面积的变化等,这种线性的方式被称为指令式编程以一行文字为例放置在哪个坐标、宽度多少、在哪里换行、怎么断句、字形字号是多少、最终高度多少、是否需要缩小字号来完全显示等,这些都是i开发者在制作界面时要考虑和计算妥当的问题。到了新设备发布,用户可能换更大屏幕的手机,系统支持动态字体调节等新功能,此时原先的程序不进行适配就可能出现显示问题,开发者就需要回头及性能程序的重新调试换做SwiftUI之后上述的很多变量就被系统接管了,也取消了坐标系统,开发者要做的就是直观的告诉系统放置一个图像,上面加一行文字,右边加一个按钮,系统会根据屏幕大小、方向等自动渲染这个界面,开发者也不再需要像素级的进行计算,这被称为声明式编程

  • 链式调用修改属性 链式调用是Swift语言的一种特性,就是用来使用函数方式的一种方式。可以像链条那样不断地调用函数,中间不需要断开,使用这样方式可以大大减少代码量。
    除了系统提供的属性可以使用之外,开发者也可以进行自定义。例如将不同字体、字号、行间距、颜色等属性综合起来,可以组合成一个叫“标题”的文字属性,之后凡是需要将某一行文字设置成标题,直接添加这个自定义的属性即可,使用这种方式进行开发无疑能够极大的避免无意义的重复工作,更快的搭建应用界面框架。

  • 界面元素组件化
    理论上来讲,每一个复杂的视图,都是由大量简单的单元视图构成。但是函数方法可以包装起来,做到仅在有需要的时候进行调取使用。在UIKit框架下的页面元素解耦却不太容易,一般都是针对某种特定情境,很难进行移植,有时候可能手机横屏就会让页面元素混乱,就更被论页面元素的组件化了。不过SwiftUI在布局上的特点,却可以便捷的拆分复杂的视图组件。单一的组件不仅可以自由组合,而且在苹果的任意平台上都可以使用该组件,达到跨平台的实现。
    一般个人会将视图组件区分为基础组件、局部组件和功能组件,因为SwiftUI的界面不再像UIKit那样,用ViewController承载各种UIView控件,而是一切都是视图。这种视图的拼装方式提高了界面开发的灵活性和复用性。

  • 与UIKit彼此相容 一般开发者学习新技术有一个最大的障碍就是原先的项目怎么办?但SwiftUI在这一点上考虑得非常周到。由于是一个新发布的框架,UI组件并不齐全,当SwiftUI中并没有提供类似的功能时,就可以把UIKit中已有的部分进行封装,提供给SwiftUI使用。开发者需要做的仅仅是遵循UIViewRepresentable协议即可。相反,在已有的项目中,也可以仅用SwiftUI制作一部分的UI界面。当然两种代码的风格是截然不同的,但在使用上却基本没有性能的损失。在最终的运行效果上,用户也无法分辨出两种界面框架的不同。

  • 单一数据源 在WWDC的介绍视频中,Source of truth这个词反复出现,中文可以理解为单一数据源。一直以来复杂的UI结构都会创造更为复杂的数据和逻辑管理需求,每次在用户交互,或是数据来源发生变化的时候,如果不能及时更新相关界面组件就会引起显示问题。
    在SwiftUI中,只要在属性声明时加上@State等关键词,就可以将该属性和界面元素联系起来,在每次数据改动后,都有机会决定是否更新视图,这样就可以将所有的属性都集中到一起进行管理和计算,也不再需要手写刷新的逻辑。因为在swiftUI中,页面渲染前会将开发者描述的界面状态存储为结构体,更新界面就是将之前状态的结构体销毁,然后生成新的状态。而在绘制界面的过程中,会自动比较视图中各个属性是否有变化,如果发生变化,便会更新对应的视图,避免全局绘制和资源浪费。 使用这种方式,读和写都集中在一处,开发者就能够更好地设计数据结构,比较方便的增减类型和排查问题。而不用再考虑线程、原子状态、寻找最新数据等各种细节,再决定通知相关的界面进行刷新。

截屏2022-08-25 11.08.54.png

  • 设计工具&快速预览 Xcode包含直观的设计工具,只需拖放操作就能使用SwiftUI轻松构建界面。当在设计画布中操作时,每一项编辑都会与相邻编辑器中的代码保持完全同步。在键入时代码会立即以预览形式显示,对预览进行的任何更改会立即反映在代码中。Xcode会即时重新编译更改,并将更改插入到app的运行版本中,方便随时查看和编辑。
    SwiftUI可与Xcode11及以上无缝协作,让代码和设计完美同步,同时还提供对动态类型、暗黑模式、本地化和可访问性的自动支持。
    拖放操作,只需在画布上拖放控件,就能调整组件在用户界面中的位置。点击打开检查器,即可选择字体、颜色、对齐方式和其他设计选项,并可通过光标轻松重新排列控件。
    动态替换,Swift编译器和运行时已全面嵌入到Xcode中,可随时构建和运行app,Xcode可以直接在实时运行的app中替换编辑后的代码。
    预览,可以为任何SwiftUI视图构建一个或多个预览来获取样本数据。用户能看见的任何内容(如大字体、本地化或深色模式),都可以配置。预览也可以显示UI在任何设备和方向上的呈现效果。

四.SwiftUI与UIKit的联系

  • SwiftUI是使用声明式编程方式的UI框架,UIKit是使用指令式编程方式的UI框架;

  • SwiftUI的界面不再像UIKit那样,用ViewController承载各种UIView控件,而是一切都是视图;
    SwiftUI 中的视图构造是指完全不同的过程,因为 SwiftUI 中没有视图类的实例。当我们在UIKit中讨论视图或视图控制器时,我们引用UIView或UIViewController类的实例。在 UIKit 中查看构造意味着构建视图控制器树和视图对象,以后可以修改以更新屏幕内容。SwiftUI中的视图构造是指完全不同的过程,因为SwiftUI中没有视图类的实例。当我们讨论视图时,我们谈论的是符合View协议的值。这些值描述屏幕上应该显示的内容,但它们与您在屏幕上看到的内容(如 UIKit 视图)没有一对一的关系:SwiftUI 中的视图值是暂时的,可以随时重新创建。

  • 在UIKit中,计数器应用的视图构造将只是必要代码的一部分;还必须为修改计数器的按钮实现事件处理程序,而该按钮又需要触发对文本标签的更新。查看构造和查看更新是 UIKit 中的两个不同的代码路径。在SwiftUI中,无需编写额外的代码才能在屏幕上更新文本标签。每当状态发生更改时,视图树都会重建,并且SwiftUI将承担确保屏幕反映视图树中的描述的责任。

五.Views&Modifies Library

SwiftUI中的Library除了UIKit中也有的Snippets、Media、Color内容外,增加了ViewsModifies

  • Views包括:Controls、Layout、Paint、Other
  • Modifies包括:Controls、Effects、Layout、Text、Image、List、Navigation Bar、Style、Accessibility、Events、Gestures、Shapes、Other

截屏2022-08-25 11.48.57.png

截屏2022-08-25 11.43.27.png

六.LaunchScreen&AccentColor

  • 设置APP的启动图片:在Info.plist文件中包含“Launch Screen”关键字,此关键字为字典型,可以添加6个内容,包含:Background color、Image Name、Image respects safe area insets、Show Navigation bar、Show Tab bar、Show Toolbar
    • Background color:设置颜色背景作为启动图,需要在Assets中添加颜色;
    • Image Name:设置启动图片
    • Show Navigation bar:设置启动图片的导航栏图片;
    • Show Tab bar:启动图片底部添加Bar;
    • Show Toolbar:设置图片底部Bar的图片内容。

截屏2022-08-25 14.53.14.png