SwiftUI小功能模块系列
0001、SwiftUI自定义Tabbar动画效果
0002、SwiftUI自定义3D动画导航抽屉效果
0003、SwiftUI搭建瀑布流-交错网格-效果
0004、SwiftUI-<探探App>喜欢手势卡片
0005、SwiftUI-粘性动画指示器引导页
技术:SwiftUI3.0、动画指示器、粘性动画指示器、引导页
运行环境:
SwiftUI3.0 + Xcode13.4.1 + MacOS12.5 + iPhone Simulator iPhone 13 Pro Max
使用SwiftUI做一个
粘性动画指示器引导页
的案例
思路:
1.创建主页Home
2.搭建主页进行偏移的逻辑处理OffsetTabView
3.添加主页介绍信息的模型Intro
4.处理滚动的时候 通过 主页进行绑定OffsetPageTabView
的偏移量offset
进行监听 是否要改变当前页面
AnimatedIndicator
颜色
Blue#5EC7F9
Pink#EB627A
Yellow#F7CE46
引导页介绍图片3张
New Group
命名为 View
New File
选择SwiftUI View
类型 命名为Home
New File
选择SwiftUI View
类型 命名为OffsetTabView
New Group
命名为 Model
New File
选择SwiftUI View
类型 命名为Intro
、并且删除预览视图、改造成模型 继承Identifiable
主要是展示主窗口
Home
并且获取屏幕总高度 - 传递给 Home页面
import SwiftUI
struct ContentView: View {
var body: some View {
// Home().preferredColorScheme(.dark)
// Getting screenSzie Globally....
GeometryReader{proxy in
let size = proxy.size
Home(screentSize:size).preferredColorScheme(.dark)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
思路
- 顶部模块 - 吃豆人图像
- 中间部分核心模块 - 滚动页面 - UI创建
包含图片、两个文本
- 底部模块 - 指示器 和 下一页的按钮
指示器使用Capsule创建
- 创建介绍页面数据、和滚动核心UI
OffsetPageTabView
//
// Home.swift
// AnimatedIndicator (iOS)
//
// Created by 李宇鸿 on 2022/8/16.
//
import SwiftUI
struct Home: View {
var screentSize : CGSize
@State var offset : CGFloat = 0
var body: some View {
VStack{
Button {
} label: {
Image("pacman")
.resizable()
.renderingMode(.template)
.foregroundColor(.white)
.frame(width: 30, height: 30)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
OffsetPageTabView(offset: $offset) {
HStack(spacing:0){
ForEach(intros){ intro in
VStack{
Image(intro.image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: screentSize.height/3)
VStack(alignment: .leading, spacing: 22) {
Text(intro.title)
.font(.largeTitle.bold())
Text(intro.description)
.fontWeight(.semibold)
.foregroundColor(.secondary)
}
.foregroundStyle(.white)
.padding(.top,50)
.frame(maxWidth:.infinity,alignment:.leading)
}
.padding()
// 设置最大宽度
.frame(width: screentSize.width)
}
}
}
// 动画指标……
HStack(alignment:.bottom){
// 指示器
HStack(spacing:12){
ForEach(intros.indices,id:\.self){index in
Capsule()
.fill(.white)
// 仅为当前索引增加宽度…
// 当前指示器宽度为20 其他为7
.frame(width:getIndex() == index ? 20 : 7, height: 7)
}
}
.overlay(
Capsule()
.fill(.white)
.frame(width:20, height: 7)
.offset(x:getIndicatorOffset())
,alignment: .leading
)
.offset(x:10,y:-15)
Spacer()
Button {
// 更新偏移量
let index = min(getIndex() + 1, intros.count - 1)
offset = CGFloat(index) * screentSize.width
} label: {
Image(systemName: "chevron.right")
.font(.title2.bold())
.foregroundColor(.white)
.padding(20)
.background(
intros[getIndex()].color,
in: Circle()
)
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
// 测试滚动监听
// .onChange(of: offset) { _ in
// print(offset)
// }
// 当索引改变时动画…
.animation(.easeOut,value: getIndex())
}
// 偏移量为指标……
func getIndicatorOffset()->CGFloat{
let progress = offset / screentSize.width
// 12 =空格
// 7 =圆的大小。
let maxWidth : CGFloat = 12 + 7
return progress * maxWidth
}
// 据offset扩展索引…
func getIndex()-> Int{
let progress = round(offset / screentSize.width)
// 安全操作
let index = min(Int(progress),intros.count - 1)
return index
// return Int(progress)
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
主要是做 滚动页面的逻辑处理
思路
- 基于ScrollView进行处理
- 提供初始化构造器 - 方便上层通过偏移量进行 调用初始化
init(offset: Binding
, @ViewBuilder content: @escaping()->Content) - 监听ScrollView滚动的代理、更新当前的偏移量
class Coordinator
- 提供滚动的代理 滚动到下一个页面 进行是否更新当前的偏移量
updateUIView
import SwiftUI
// 自定义视图泰式将返回填充控件的偏移量…
struct OffsetPageTabView<Content: View>: UIViewRepresentable {
var content: Content
@Binding var offset : CGFloat
func makeCoordinator() -> Coordinator {
return OffsetPageTabView.Coordinator(parent: self)
}
init(offset: Binding<CGFloat> , @ViewBuilder content: @escaping()->Content){
self.content = content()
self._offset = offset
}
func makeUIView(context: Context) -> UIScrollView {
let scrollview = UIScrollView()
// 提取SwiftUI View并嵌入到UIKit ScrollView…
let hostview = UIHostingController(rootView: content)
hostview.view.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
hostview.view.topAnchor.constraint(equalTo: scrollview.topAnchor),
hostview.view.leadingAnchor.constraint(equalTo: scrollview.leadingAnchor),
hostview.view.trailingAnchor.constraint(equalTo: scrollview.trailingAnchor),
hostview.view.bottomAnchor.constraint(equalTo: scrollview.bottomAnchor),
//如果你使用的是垂直填充…
//然后不要声明高度限制…
hostview.view.heightAnchor.constraint(equalTo: scrollview.heightAnchor)
]
scrollview.addSubview(hostview.view)
scrollview.addConstraints(constraints)
// 启用分页
scrollview.isPagingEnabled = true
scrollview.showsVerticalScrollIndicator = false
scrollview.showsHorizontalScrollIndicator = false
// 设置代理
scrollview.delegate = context.coordinator
return scrollview
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
//只有当offset被手动更改时才需要更新…
//检查当前和滚动视图的偏移量…
let currentOffset = uiView.contentOffset.x
if currentOffset != offset {
print("updating");
uiView.setContentOffset(CGPoint(x: offset, y: 0),animated:true)
}
}
// 页面抵消……
class Coordinator : NSObject,UIScrollViewDelegate {
var parent : OffsetPageTabView
init(parent: OffsetPageTabView){
self.parent = parent
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.x
parent.offset = offset
}
}
}
struct OffsetPageTabView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
介绍模型
import SwiftUI
// 介绍模型和样本介绍的…
struct Intro: Identifiable{
var id = UUID().uuidString
var image: String
var title: String
var description: String
var color: Color
}
var intros : [Intro] = [
Intro(image: "food2", title: "Choose your favourite menu", description: "But they are not the inconvenience that our pleasure.",color: Color("Blue")),
Intro(image: "food1", title: "Find the best price", description: "There is no provision to smooth the consequences are.",color: Color("Yellow")),
Intro(image: "food3", title: "Your food is ready to be delivered", description: "ter than the pain of the soul to the task.",color: Color("Pink")),
]