简介
SwiftUI是Apple推出的现代界面框架,旨在简化界面开发和布局,并提供强大的响应式编程和动画支持。与UIKit的一个最明显的区别便是:SwiftUI是一个纯coding式的框架,摒弃了UIkit的结合图形界面设计工具构建视图的功能
概念
下面会从SwiftUI的几个基础并且重要的知识概念出发,从而能够大致理清并了解SwiftUI 框架的大致组成结构,达到在使用SwiftUI这个框架时能够更加理性的目的
- UI 控件
- 常用控件
- 控件样式
- 链式调用
- view modifier
- 动画
- 生命周期
- 视图
- 应用程序
- 适配
UI 控件
在SwiftUI中,有许多常用的UI 控件可用于构建应用程序的用户界面,同时还能对这些UI 控件的样式进行调整,设计出想要的页面效果
常用控件
- Text:用于显示文本内容,支持自定义样式、字体和颜色
- Label:用于显示带有图标的文本标签,通常用于表示标签或标题
- Button:创建一个可点击的按钮,用于执行操作或导航到其他视图
- LinkButton:类似于
Button,但用于打开外部链接或导航到其他应用程序 - TextField:用于接受用户的文本输入
- Image:用于显示图像,支持本地图像和网络图像
- NavigationView:用于创建导航界面的容器
- NavigationLink:创建一个可点击的导航链接
- ScrollView:用于滚动内容,以显示更多的信息
- List:用于显示可滚动的列表,支持分组和行操作
- Form:类似于
List,但用于创建表单布局,自动适应键盘弹出 - Alert:用于显示警报消息和操作
- ActionSheet:用于显示一个带有多个操作选项的弹出菜单
- VStack:垂直堆栈布局,用于按垂直方向排列视图
- HStack:水平堆栈布局,用于按水平方向排列视图
提示
Section也是一个 UI 控件,但是不能单独使用,而是用于构建列表和表格的布局组件。Section通常用于将列表或表格中的内容分组显示VStack和HStack是用于页面布局的,经常频繁使用
控件样式
在SwiftUI中构建用户界面时,可以使用链式调用和封装 View Modifier来进行样式调整。这些方法使得在不同视图上应用相似的样式变得非常便捷,同时还可以增强代码的可读性和可维护性
1. 链式调用
在SwiftUI中,可以通过链式调用在视图上应用多个修饰符,从而逐步构建视图的样式。每个修饰符调用都会返回一个新的视图,可以在其上继续应用其他修饰符
struct ProfileHeaderView: View {
@State var lineWidth = CGFloat(4)
@State var radius = CGFloat(7)
var body: some View {
Image(avatarUrl)
.resizable()
.clipShape(Circle())
.overlay(Circle().stroke(Color.orange, lineWidth: lineWidth))
.shadow(radius: radius)
.animation(.default.repeatForever(), value: lineWidth)
.animation(.default.repeatForever(), value: radius)
.onAppear {
lineWidth = 2
radius = 4
}
.frame(width: 60, height: 60)
}
}
上面这个例子是绘制一个包含外框且有动画的头像框,可以看到主要是使用Image这个控件来完成,外框、动画则都是对这个UI 控件的样式进行调整,采用链式调用内置的修饰符来完成
2. 封装 View Modifier
除了使用内置的修饰符外,您还可以创建自定义的View Modifier,将常用的样式封装起来,以便在多个视图中重复使用,这使得你可以在代码中引入更高层次的抽象,使得代码更具可重用性和可维护性
struct ProfileHeaderView: View {
var body: some View {
Image(avatarUrl)
.resizable()
.circle()
.frame(width: 60, height: 60)
}
}
struct CircleModifier: ViewModifier {
@State var lineWidth = CGFloat(4)
@State var radius = CGFloat(7)
func body(content: Content) -> some View {
content
.clipShape(Circle())
.overlay(Circle().stroke(Color.orange, lineWidth: lineWidth))
.shadow(radius: radius)
.animation(.default.repeatForever(), value: lineWidth)
.animation(.default.repeatForever(), value: radius)
.onAppear {
lineWidth = 2
radius = 4
}
}
}
extension Image {
func circle() -> some View {
modifier(CircleModifier())
}
}
上面这个例子则是在链式调用的基础上进行了封装,把多个样式封装成一个样式行为,属于更高层次的样式使用。虽然实现的效果是一样的,但是当多处需要使用到上面的样式时,使用View Modifier封装的样式能让你少写更多相同样式代码
动画
在SwiftUI中,可以使用内置的动画功能为UI 控件添加动画效果,以增强用户体验。动画可以应用于属性的更改,从而使控件的状态转换更加平滑和引人注目
在上面的例子中有应用到动画修饰符,可以看到需要给动画定义一个触发时机
使用动画有两种方式
- 动画修饰符
.animation - 动画API
withAnimation
下面就使用之前的例子对比演示一下两者使用上的一些区别:
@State var lineWidth = CGFloat(4)
@State var radius = CGFloat(7)
// .animation
Image(avatarUrl)
.animation(.default.repeatForever(), value: lineWidth)
.animation(.default.repeatForever(), value: radius)
.onAppear {
lineWidth = 2
radius = 4
}
// withAnimation
Image(avatarUrl)
.onAppear {
withAnimation(.default.repeatForever()) {
lineWidth = 2
radius = 4
}
}
生命周期
SwiftUI的生命周期与UIkit差别挺大的,首先就是没有UIkit那么详细的生命周期函数,然后就是SwiftUI的生命周期分为视图和应用程序两种
- 视图:生命周期函数被缩减为只剩
onAppear和onDisappear两个 - 应用程序:生命周期与
ScenePhase密切相关- .active:应用程序处于活动状态,即在前台运行,用户可以与应用程序进行交互
- .inactive:应用程序处于非活动状态,即在前台运行,但用户不能与应用程序交互
- .background:应用程序处于后台运行状态
SwiftUI中应用程序生命周期的使用案例:
struct MySwiftUIApp: App {
@Environment(\.scenePhase) private var scenePhase: ScenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { newScenePhase in
layoutWithScenePhase(newScenePhase: newScenePhase)
}
private func layoutWithScenePhase(newScenePhase: ScenePhase) {
switch newScenePhase{
case .active:
// code
case .inactive:
// code
case .background:
// code
@unknown default: break
}
}
}
}
适配
在SwiftUI中,适配涉及确保应用程序在不同设备之间(如iPhone和iPad)以及不同屏幕尺寸和分辨率之间都能正常显示
下面列举两个SwiftUI针对不同设备类型的适配方案:
- Size Classes(尺寸类别)
- UIDevice.current.userInterfaceIdiom
Size Classes
SwiftUI使用尺寸类别来表示设备的大小和方向,如:compact和regular。在视图中使用.horizontalSizeClass和.verticalSizeClass来检测设备的尺寸类别,并根据需要应用不同的布局和样式
struct ContentView: View {
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@Environment(\.verticalSizeClass) var verticalSizeClass
var body: some View {
VStack(alignment: .leading) {
if horizontalSizeClass == .compact && verticalSizeClass == .regular {
layoutWithUserInterfaceIdiom()
} else {
HStack {
layoutWithUserInterfaceIdiom()
}
}
}
}
}
UIDevice.current.userInterfaceIdiom
UIDevice.current.userInterfaceIdiom 是UIKit中用于判断设备类型的属性,可以在iPhone和iPad之间进行适配,但如果需要在SwiftUI中使用UIKit属性来判断设备类型,也是可以的
struct ProfileHeaderView: View {
@State var userInterfaceIdiom = UIDevice.current.userInterfaceIdiom
var body: some View {
VStack(alignment: .leading) {
if userInterfaceIdiom == .phone {
HStack {
layoutWithUserInterfaceIdiom()
}
} else {
layoutWithUserInterfaceIdiom()
}
}
}
}
小结
对于SwiftUI的学习,与UIkit的学习路线大致上是相似的,但由于SwiftUI是一种全新的声明式框架,使用方式和一些概念可能会有所不同
对于SwiftUI 框架的使用,本文主要讲述了其通用 UI控件、样式、动画、生命周期以及适配问题等,在后续的文章中会陆续对SwiftUI中的属性包装器、缓存以及网络请求进行谈论及总结