前言
今天没有前言,直接进入主题吧...😁
1、Form
Form表单,新建一个表单,发现和List并无太大差别,只是界面背景置灰
了,显示内容会自动有个margin
。
Form{
Section(header: Text("第一组"), footer: Text("Footer")) {
Text("form")
Text("form")
Text("form")
}
Section() {
Text("form")
Text("form")
Text("form")
}
}
样式看起来和List差不多。但Form真正比较实用的地方类似于设置功能界面。
@State var pick = 0
@State var toggle1 = false
@State var toggle2 = true
NavigationView {
Form {
Section(header: Text("自动下载"), footer: Text("自动下载在其他的设备上新购买的项目(含免费项目)。")) {
Toggle("App", isOn: $toggle1)
Toggle("App更新", isOn: $toggle1)
}
Section(header: Text("蜂窝数据"), footer: Text("仅允许 200 MB以下的 App 使用蜂窝数据自动下载。")) {
Toggle("自动下载", isOn: $toggle1)
Picker("App 下载", selection: $pick) {
Text("始终允许").tag(0)
Text("超过 200 MB时询问").tag(1)
Text("始终询问").tag(2)
}
}
Section(footer: Text("在App Store中自动播放 App 预览视频。")) {
Picker("视频自动播放", selection: $pick) {
Text("开").tag(0)
Text("仅限 Wi-Fi").tag(1)
Text("关").tag(2)
}
}
Section(footer: Text("允许 App 询问产品使用反馈,以帮助开发者和其他用户了解您的想法。")) {
Toggle("App 内评分及评论", isOn: $toggle2)
}
Section(header: Text("沙盒账户"), footer: Text("此账户将仅用于本地开发时,测试 App 内购买项目。您当前的 App Store 账户将用于 TestFlight App。")) {
Button("Apple ID:LogicEducation@qq.com") {}
}
Section(header: Text("隐私")) {
Button("App Store 与 Arcade 隐私") {}
Button("个性化推荐") {}
}
}.navigationTitle("App Store").navigationBarTitleDisplayMode(.inline)
}
50行代码就能实现设置内功能界面,代码量少,但功能一个不落。
查看Form的视图层级,发现它内部也是一个UITableViewWrapperView
,也是对UITableView
的包装,难怪和List那么相似。你可以试着将Form改成List,就会发现毫无变化。
2、ScrollView
ScrollView在使用上和List没什么差别,而且在底层cell的复用机制
上也和UITableView一样。
struct customView: View {
var text: String
var body: some View {
Text(text)
}
init(_ text: String) {
print("create", text)
self.text = text
}
}
ScrollView{
VStack {
ForEach(0..<100){
customView("Cell \($0)").font(.title)
}
}
}
如上代码,运行后打印了100条,说明没有用到复用机制,当我们去掉当前容器VStack层后只打印18条,说明是VStack强制让子视图一次性加载。
ScrollViewReader
SwiftUI提供了这么一个代理来帮助我们实现比如点击按钮滑动到列表底部这样的功能:
ScrollViewReader { proxy in
Button("gotoBottom"){
// 99 底部cell的ID
proxy.scrollTo(99)
}
ScrollView{
VStack {
//id 添加维一标识
ForEach(0..<100){
customView("Cell \($0)").font(.title).id($0)
}
}
}
}
3、Grid
表格,在使用类似于之前的UICollectionView
的效果。
LazyVGrid(columns: [GridItem(),GridItem()],spacing: 10){
ForEach(0..<100,id: \.self) {
Text("\($0)").frame(height: 100).frame(maxWidth: .infinity).background(.orange)
}.padding(10).font(.title2)
}
即可实现两列列表,但是此时不能上下滑动,我们需要将Grid放入ScrollView
中才能滑动,这两者在使用上基本形影不离。
如果是宽度不规则的瀑布流样式,可以改变宽度范围来控制列数:
ScrollView{
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150,maximum: 200))], alignment: .leading,spacing: 10){
ForEach(0..<100,id: \.self) {
Text("\($0)").frame(height: 100).frame(width:CGFloat(arc4random() % 10)*10 + 100).background(.orange)
}.padding(10).font(.title2)
}
}
4、VideoPlayer
视频播放器需要引入AVKit
框架,可直接使用VideoPlayer
结构体构造简单播放器。
import AVKit
let url = "https://video.yinyuetai.com/e65a400cf89941439cf1175f69737800.mp4"
var body: some View {
VStack{
VideoPlayer(player: AVPlayer(url: URL.init(string: url)!),videoOverlay: {
Text("play").font(.largeTitle).bold().foregroundColor(.white)
}).ignoresSafeArea().frame(width: .infinity,height: 260)
Spacer()
}
}
设置一定的尺寸就能满足简单的播放器需求。如果想实现另外的一些复杂功能,用法基本上和UIKit里是一样的。
5、Alert / AlertController
Alert提示弹框,样式还是和之前一样的,写法有所不同。
@State var show = false
Button ("show Alert") {
show.toggle()
}.alert(isPresented: $show) {
Alert.init(title: Text("title"), message: Text("message"), primaryButton: .default(Text("cancle"),action: {
print("cancle")
}), secondaryButton: .destructive(Text("sure"),action: {
print("sure")
}))
}
即可实现两个按钮的提示框,并监听按钮点击事件用以处理下一步操作。
当需要添加多个按钮,类似于UIAlertController
的alert样式的弹出框,就需要像下面这样添加:
Button ("show Alert") {
show.toggle()
}.alert("title" ,isPresented: $show) {
Button("btn1"){}
Button("btn2"){}
Button("btn3"){}
}
此种写法,当不给添加按钮时候,默认样式是一个带ok按钮
的提示弹窗。
那如果想实现底部弹出类似Sheet
的样式,就需要用到.confirmationDialog
,ActionSheet
已经废弃不用。
Button ("show Alert") {
show.toggle()
}.confirmationDialog("sheet", isPresented: $show) {
Button("btn1"){}
Button("btn2"){}
Button("btn3"){}
}
底部弹出会自带一个cancle按钮。
6、显式动画
Button("animation"){
withAnimation {
scaleAmount += 44
}
}.font(.title).padding().background(.orange)
.cornerRadius(20)
.rotation3DEffect(.degrees(scaleAmount), axis: (x: 1, y: 0, z: 0))
创建一个按钮,在3D视角上,点击时就沿x轴旋转
一定的角度。
Button("animation"){
withAnimation(.easeInOut) {
scaleAmount.toggle()
}
}
.font(.title).padding().background(scaleAmount ? .white : .orange)
//背景变换
.animation(.easeInOut.repeatForever(autoreverses:true), value: scaleAmount)
.cornerRadius(20)
//缩放
.scaleEffect(scaleAmount ? 1.1 : 1)
.animation(.easeInOut.repeatForever(), value: scaleAmount)
//边框发散
.overlay{
RoundedRectangle(cornerRadius: 20)
.stroke(.red)
.scaleEffect(scaleAmount ? 2 : 1)
.opacity(scaleAmount ? 0 : 1)
.animation(.easeInOut(duration: 1).repeatForever(autoreverses: false), value: scaleAmount)
}
复杂动画,就是后面的动画不会影响前面的动画。
下面再写个关于改变位置的动画效果:
@State var isVstack:Bool = false
@Namespace var nameSpace
Group {
if isVstack{
VStack{
Image(uiImage: UIImage.init(named: "2222")!).resizable().frame(width: 150, height: 150).matchedGeometryEffect(id: "image", in: nameSpace)
Text("Hello Lcr").font(.largeTitle).matchedGeometryEffect(id: "text", in: nameSpace)
}
}else{
HStack{
Text("Hello Lcr").font(.largeTitle).matchedGeometryEffect(id: "text", in: nameSpace)
Image(uiImage: UIImage.init(named: "2222")!).resizable().frame(width: 150, height: 150).matchedGeometryEffect(id: "image", in: nameSpace)
}
}
}.onTapGesture {
withAnimation {
isVstack.toggle()
}
}
如果不加上.matchedGeometryEffect
,效果就只是更改位置而没有动画效果。
7、手势
一些基本的手势如点击(.onTapGesture)
、长按(.onLongPressGesture)
等使用比较简单,大家可以自己试试,另外的旋转
、缩放
、拖动
稍微复杂点,需要搭配.gesture()
来使用,下面写个例子来看看缩放效果:
@State var currentAmount: CGFloat = 0
@State var finalAmount: CGFloat = 1
Text("Hello Lcr").bold().font(.title).padding().background(.green).scaleEffect(currentAmount + finalAmount)
.gesture(
MagnificationGesture()
.onChanged({ amount in
print(amount)
currentAmount = amount - 1
}).onEnded({ amount in
finalAmount += currentAmount
currentAmount = 0
})
)
SwiftUI里点击事件的响应也是和UIKit中一样的,子视图比父视图响应的优先级更高
,我们可以利用.highPriorityGesture
来将响应者更改,可以理解为高优先级的手势:
.highPriorityGesture(
TapGesture().onEnded({ _ in
print("VStack tap")
})
)
这样就能使响应优先级反转。
需求一
:父子视图同时响应点击事件,那么可将highPriorityGesture
替换成simultaneousGesture
即可。
需求二
:长按3秒之后可拖动,我们就需要借助于.sequenced
序列来对两种手势进行组合:
let combined = pressGesture.sequenced{before: dragGesture}
SwiftUI手势使用中,我们需要利用.onChanged
、.onEnded
等来获取手势的状态来实现一些功能上的操作。以及利用withAnimation{}
来结合手势去完成一些动画效果。
最后我们来实现一个比较有意思的例子:
let words = Array("Lcr is a gentleman")
@State var enabled = false
@State var dragAmount = CGSize.zero
var body: some View {
HStack(spacing: 0) {
ForEach(0..<words.count) {num in
Text(String(words[num])).bold().font(.title).foregroundColor(.white)
.padding(2)
.background(enabled ? .orange : .red)
.offset(dragAmount)
.animation(Animation.default.delay(Double(num) / 20), value: dragAmount)
}
}.gesture(
DragGesture()
.onChanged {
dragAmount = $0.translation
}
.onEnded {_ in
dragAmount = .zero
enabled.toggle()
}
)
}
8、ToolBar
给界面顶部导航条添加按钮,之前用的是.navigationBarItems()
,现在推荐使用.toolbar{}
:
NavigationView {
Text("Hello Lcr").font(.title)
.navigationTitle("navi")
//方式一 已废弃
//.navigationBarItems(trailing: EditButton())
//方式二
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Edit"){}
}
}
}
.toolbar
方式可以很轻松自定义一些自己想要的按钮效果,文字图片都行。然后可以切换placement
不同样式,查看按钮位置的不一样,以及多个ToolbarItem按钮之间的优先级
(布局上的)。
9、fullScreenCover
.sheet()展示效果类似于representedViewController
,不能铺满全屏,如果需要铺满,可以使用.fullScreenCover()
即可:
@State var show = false
VStack {
Text("Hello Lcr").fullScreenCover(isPresented: $show) {
Text("Detail")
}
Button("show"){
show.toggle()
}
}
也可用sheet替代fullScreenCover看看不一样的效果。
我们通过三篇文章简单介绍SwiftUI中一些常用的Views和Modifies,暂时也有个印象,项目中用一用,敲一敲就更熟悉了。