【SwiftUI模块】0007、SwiftUI新手指引-新手指示-聚光灯介绍说明
SwiftUI小功能模块系列
0001、SwiftUI自定义Tabbar动画效果
0002、SwiftUI自定义3D动画导航抽屉效果
0003、SwiftUI搭建瀑布流-交错网格-效果
0004、SwiftUI-<探探App>喜欢手势卡片
0005、SwiftUI-粘性动画指示器引导页
0006、SwiftUI自定义引导页动画
0007、SwiftUI新手指引
技术:SwiftUI3.0、新手指引、聚光灯、新手指示、聚光灯介绍说明
运行环境:
SwiftUI3.0 + Xcode13.4.1 + MacOS12.5 + iPhone Simulator iPhone 13 Pro Max
⚠️基于上一个 SwiftUI自定义引导页动画 案例 进行扩展添加一个新手引导效果
SwiftUI新手指引-新手指示-聚光灯介绍说明
- 概述
- 详细
- 一、运行效果
- 二、项目结构图
- ⭐️ 额外给引导页的背景添加一个颜色值
- 三、程序实现 - 过程
- 1.创建一个项目命名为 `OnBoardingAnimation`
- 1.1.引入资源文件和颜色
- 2. 创建一个虚拟文件`New Group` 命名为 `View`
- 2. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`OnBoarding`
- 2. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`OffsetPageTabView`
- 3. 创建一个虚拟文件`New Group` 命名为 `Model`
- 3. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`BoardingScreen` 、并且删除预览视图、改造成模型 继承`Identifiable`
- ⭐️4. 创建一个虚拟文件`New Group` 命名为 `Helpers`
- ⭐️5. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`Extensions` 、并且删除预览视图、改造成一个扩展文件`extension`
- Code
- ContentView - 主窗口
- OnBoarding - 引导页
- OffsetPageTabView.swift -偏移Tab视图逻辑处理
- BoardingScreen - 模型
- ⭐️Extensions - 扩展 `用来处理 新手指引`
- demo源码
概述
使用SwiftUI做一个
SwiftUI新手指引
的效果
如果你有跟着我操作上篇 SwiftUI自定义引导页动画 案例
这次只需要注重 ⭐️的部分即可 。 代码会出现部分的改变
详细
一、运行效果
二、项目结构图
⭐️ 额外给引导页的背景添加一个颜色值
默认颜色
#000000
暗黑模式#212121
三、程序实现 - 过程
思路:
1.创建主页OnBoarding
2.搭建主页进行偏移的逻辑处理OffsetTabView
3.添加主页介绍信息的模型BoardingScreen
4.处理滚动的时候 通过 主页进行绑定OffsetPageTabView
的偏移量offset
进行监听 是否要改变当前页面
5.并且通过一个圆形矩形背景
做一个自身360
的旋转动画
⭐️新手指示效果:
- 获取根控制器
- 从根控制器获取view
- 从根控制器的view 里面添加一个view 用来当做聚光灯的背景
- 从聚光灯背景中反向提取指定区域 设置成白色 也就是高光效果
1.创建一个项目命名为 OnBoardingAnimation
1.1.引入资源文件和颜色
颜色
screen1#D2BA64
screen2#5050CF
screen3#7EBA64
screen4#504F5F
引导页介绍图片4张
图片名称 和 颜色名称一样 。方便统一根据名字设置对应的页面
2. 创建一个虚拟文件New Group
命名为 View
2. 创建一个文件New File
选择SwiftUI View
类型 命名为OnBoarding
2. 创建一个文件New File
选择SwiftUI View
类型 命名为OffsetPageTabView
具体实现和 0005、SwiftUI-粘性动画指示器引导页 案例一样
如果上一个案例 你有跟我实现 - 那么可以直接拖拽过来即可
3. 创建一个虚拟文件New Group
命名为 Model
3. 创建一个文件New File
选择SwiftUI View
类型 命名为BoardingScreen
、并且删除预览视图、改造成模型 继承Identifiable
⭐️4. 创建一个虚拟文件New Group
命名为 Helpers
⭐️5. 创建一个文件New File
选择SwiftUI View
类型 命名为Extensions
、并且删除预览视图、改造成一个扩展文件extension
Code
ContentView - 主窗口
主要是展示主窗口
OnBoarding
//
// ContentView.swift
// Shared
//
// Created by 李宇鸿 on 2022/8/17.
//import SwiftUIstruct ContentView: View {var body: some View {OnBoarding()}
}struct ContentView_Previews: PreviewProvider {static var previews: some View {ContentView()}
}
OnBoarding - 引导页
思路
- 主要部分核心模块 - 滚动页面 - UI创建
包含图片、两个文本
- 叠加层 - 最顶层 做了指示器 和 跳过、下一页的按钮
指示器使用Circle创建
和 做了登录和注册按钮- 创建引导页面数据、和滚动核心UI
OffsetPageTabView
- 添加滚动的时候 做一个
圆形矩形背景
进行360度
自身旋转- ⭐️给需要新手指示的 添加聚光灯效果 - 通过View的扩展 - 使得每个控件都可以拥有聚光效果 - 然后设置当前的聚光位置
//
// OnBoarding.swift
// OnBoardingAnimation (iOS)
//
// Created by 李宇鸿 on 2022/8/17.
//import SwiftUIstruct OnBoarding: View {@State var offset : CGFloat = 0@State var showLight : Bool = true
// @State var currentHighlight : Int = 0@State var currentHighlight : Int = 1 // 聚光灯从1开始 var body: some View {// 自定义页面视图…OffsetPageTabView(offset:$offset){HStack(spacing:0){ForEach(boardingScreens) { screen inVStack(spacing:15){Image(screen.image).resizable().aspectRatio(contentMode: .fit).frame(width:getScrrenBounds().width - 100,height: getScrrenBounds().width - 100)// 小屏幕采用…….scaleEffect(getScrrenBounds().height < 750 ? 0.9 : 1).offset(y:getScrrenBounds().height < 750 ? -100 : -120)VStack(alignment:.leading,spacing: 12){Text(screen.title).font(.largeTitle.bold()).foregroundColor(.white).padding(.top,20)Text(screen.description).fontWeight(.semibold).foregroundColor(.white)}.frame(maxWidth:.infinity,alignment:.leading).offset(y:-70)}.padding().frame(width:getScrrenBounds().width).frame(maxHeight: .infinity)
// .background(Color(screen.image))}}}// 动画// 使用一个圆形矩形做一个背景动画 基于自身白色圆形矩形进行一个360动画效果.background(RoundedRectangle(cornerRadius: 50).fill(.white)// 大小为图像大小….frame(width:getScrrenBounds().width - 100,height: getScrrenBounds().width - 100).scaleEffect(2).rotationEffect(.init(degrees: 25)).rotationEffect(.init(degrees: getRotation())).offset(y: -getScrrenBounds().width + 20),alignment: .leading).background(Color("screen\(getIndex() + 1)")).animation(.easeInOut,value: getIndex())// 适配刘海屏.ignoresSafeArea(.container,edges: .all)// 叠加层 放在最前面.overlay(VStack{HStack(spacing:25){Button {} label: {Text("Login").fontWeight(.semibold).foregroundColor(.black).padding(.vertical,20).frame(maxWidth: .infinity).background(Color.white,in:RoundedRectangle(cornerRadius: 12))}// 增加聚光灯视野.spotlight(enabled: currentHighlight == 1, title: "Login into Account")Button {} label: {Text("SignUp").fontWeight(.semibold).foregroundColor(.black).offset(x:-5).padding(.vertical,20).frame(maxWidth: .infinity).background(Color.white,in:RoundedRectangle(cornerRadius: 12))}.spotlight(enabled: currentHighlight == 2, title: "SignUp New Account")}HStack{Button{} label: {Text("Skip").fontWeight(.semibold).foregroundColor(.white)}.spotlight(enabled: currentHighlight == 3, title: "Skip Intro's")// 指示器HStack(spacing:8){ForEach(boardingScreens.indices,id:\.self){index inCircle().fill(.white).opacity(index == getIndex() ? 1 : 0.4).frame(width: 8, height: 8).scaleEffect(index == (getIndex()) ? 1.3 : 0.85).animation(.easeInOut,value:getIndex())}}.frame(maxWidth:.infinity).spotlight(enabled: currentHighlight == 4, title: "Indicator's")// 用于测试圆形形状
// Circle()
// .fill(.red)
// .frame(width: 45, height: 45)
// .spotlight(enabled: currentHighlight == 0, title: "")Button{//设置Mac Offset…// Max 4个屏幕,所以Max将是3*宽offset = min(offset + getScrrenBounds().width,getScrrenBounds().width * 3)} label: {Text("Next").fontWeight(.semibold).foregroundColor(.white)}.spotlight(enabled: currentHighlight == 5, title: "Indicator's")}.padding(.top,30).padding(.horizontal,8)}.padding(),alignment: .bottom).onTapGesture{currentHighlight += 1}}// 得到旋转func getRotation()-> Double{let progress = offset / (getScrrenBounds().width * 4 )// 做一个完整的旋转…let rotation = Double(progress) * 360return rotation}// 基于偏移改变背景颜色…func getIndex() -> Int {let progress = (offset / getScrrenBounds().width).rounded()return Int(progress)}
}struct OnBoarding_Previews: PreviewProvider {static var previews: some View {OnBoarding()}
}// 扩展视图获得屏幕边界…
extension View {func getScrrenBounds()-> CGRect{return UIScreen.main.bounds}
}
OffsetPageTabView.swift -偏移Tab视图逻辑处理
主要是做 滚动页面的逻辑处理
思路
- 基于ScrollView进行处理
- 提供初始化构造器 - 方便上层通过偏移量进行 调用初始化
init(offset: Binding<CGFloat> , @ViewBuilder content: @escaping()->Content)
- 监听ScrollView滚动的代理、更新当前的偏移量
class Coordinator
- 提供滚动的代理 滚动到下一个页面 进行是否更新当前的偏移量
updateUIView
比 0005、SwiftUI-粘性动画指示器引导页多做了一个清除背景操作
import SwiftUI// 自定义视图泰式将返回填充控件的偏移量…
struct OffsetPageTabView<Content: View>: UIViewRepresentable {var content: Content@Binding var offset : CGFloatfunc 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// 清除背景hostview.view.backgroundColor = .clearlet 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 = truescrollview.showsVerticalScrollIndicator = falsescrollview.showsHorizontalScrollIndicator = false// 设置代理scrollview.delegate = context.coordinatorreturn scrollview}func updateUIView(_ uiView: UIScrollView, context: Context) {//只有当offset被手动更改时才需要更新…//检查当前和滚动视图的偏移量…let currentOffset = uiView.contentOffset.xif currentOffset != offset {print("updating");uiView.setContentOffset(CGPoint(x: offset, y: 0),animated:true)}}// 页面抵消……class Coordinator : NSObject,UIScrollViewDelegate {var parent : OffsetPageTabViewinit(parent: OffsetPageTabView){self.parent = parent}func scrollViewDidScroll(_ scrollView: UIScrollView) {let offset = scrollView.contentOffset.xparent.offset = offset}}}struct OffsetPageTabView_Previews: PreviewProvider {static var previews: some View {ContentView()}
}
BoardingScreen - 模型
介绍模型
import SwiftUIstruct BoardingScreen: Identifiable {var id = UUID().uuidStringvar image : Stringvar title : Stringvar description : String
}// 相同的标题和描述…
let title = "Easy Payments with \n Walletoy"
let description = "Samll business can receive device \npayment super fast and super easy"// 因为图片名称和BG颜色名称相同…// 样本模型屏幕…
var boardingScreens : [BoardingScreen] = [BoardingScreen(image: "screen1", title: title, description: description),BoardingScreen(image: "screen2", title: title, description: description),BoardingScreen(image: "screen3", title: title, description: description),BoardingScreen(image: "screen4", title: title, description: description)]
⭐️Extensions - 扩展 用来处理 新手指引
思路
- 获取根控制器
- 从根控制器获取view
- 从根控制器的view 里面添加一个view 用来当做聚光灯的背景
- 从聚光灯背景中反向提取指定区域 设置成白色 也就是高光效果
//
// Extensions.swift
// OnBoardingAnimation (iOS)
//
// Created by 李宇鸿 on 2022/8/18.
//import SwiftUIextension View{// MARK:自定义Spolitght编辑func spotlight(enabled: Bool,title: String = "")->some View{return self.overlay{if enabled{//获取当前内容大小GeometryReader{proxy inlet rect = proxy.frame(in:.global)SpotlightView(rect:rect, title: title) {self}}}}}//屏幕边界func screenBounds()-> CGRect{return UIScreen.main.bounds}// 获取根控制器func rootController()-> UIViewController {guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else{return .init()}guard let root = screen.windows.first?.rootViewController else{return .init()}return root}}// 给当前类文件做一个预览窗口
struct OnBoarding_Spotlight_Previews: PreviewProvider {static var previews: some View {OnBoarding()}
}//聚光灯视野
struct SpotlightView<Content: View> : View{var content : Contentvar rect : CGRectvar title : Stringinit(rect:CGRect, title: String,@ViewBuilder content: @escaping ()-> Content){self.content = content()self.title = titleself.rect = rect}@State var tag: Int = 1009var body : some View{Rectangle()// 如果你想避免用户互动 就不要使用clear.fill(.clear)
// .fill(.white.opacity(0.02)).onAppear{addOverlayView()}.onDisappear{removeOverlay()}}// 当视图消失时移除覆盖层func removeOverlay(){rootController().view.subviews.forEach { view inif view.tag == self.tag{view.removeFromSuperview()}}}//在当前视图上添加一个额外视图//从根控制器中提取UIViewfunc addOverlayView(){// 转换SwiftUI View到UIKitlet hostingView = UIHostingController(rootView: OverlaySwiftUIView())hostingView.view.frame = screenBounds()hostingView.view.backgroundColor = .clear//有时候SiwftUI On Appear会被调用两次//添加到当前视图// 要标识添加的是哪个视图,可以向视图添加一个标记if self.tag == 1009{self.tag = generateRandom()}hostingView.view.tag = self.tagrootController().view.subviews.forEach { view inif view.tag == self.tag{return}}// 添加到当前视图rootController().view.addSubview(hostingView.view)}@ViewBuilder// 叠加的viewfunc OverlaySwiftUIView()-> some View {ZStack{Rectangle().fill(Color("Spotlight").opacity(0.8))// 反向屏蔽当前高光点.mask({// 如果高度和宽度几乎相同,那么使它圆形esle,圆形let radius = (rect.height / rect.width) > 0.7 ? rect.width : 6Rectangle().overlay(content.frame(width:rect.width,height:rect.height)// 特别亮的小区域.padding(10).background(.white,in: RoundedRectangle(cornerRadius: radius))// 放置在正确的位置.position()//位置将把内容放在左上角//在MidXY的帮助下,我们将它设置在正确的位置.offset(x:rect.midX,y:rect.midY)// The Exact coent Size// 确切的股份大小.blendMode(.destinationOut))})// 显示文本if title != "" {Text(title).font(.title.bold()).foregroundColor(.white).position()// 如果是底部,则在上面或下面显示文本.offset(x:screenBounds().midX,y:rect.maxY > (screenBounds().height - 150) ? (rect.minY - 150) : (rect.maxY + 150) )}}.frame(width: screenBounds().width, height: screenBounds().height).ignoresSafeArea()}// 标签的随机数func generateRandom()-> Int{let random = Int(UUID().uuid.0)// 检查是否有一个视图已经拥有这个标签let subViews = rootController().view.subviewsfor index in subViews.indices {// 相同的话 就一直递归 直到找到不同Viewif subViews[index].tag == random {return generateRandom()}}return random}
}
demo源码
如需看源码,请点击下载!
【SwiftUI模块】0007、SwiftUI新手指引-新手指示-聚光灯介绍说明相关推荐
- 口袋进化服务器维护,《口袋进化》新手指引.新手指导
第一只精灵,你做好成为一名训练家的准备了么? 作为一枚纯纯的萌新,在刚接触时,建议根据游戏提供的引导,来逐步熟悉基本玩法和养成体系. 除此之外,别忘了阅读新手FAQ!在这里,为大家总结了一些新手常见的 ...
- 【SwiftUI模块】0008、SwiftUI-自定义启动闪屏动画-App启动闪屏曲线路径动画
SwiftUI小功能模块系列 0001.SwiftUI自定义Tabbar动画效果 0002.SwiftUI自定义3D动画导航抽屉效果 0003.SwiftUI搭建瀑布流-交错网格-效果 0004.Sw ...
- 从王者荣耀里我学会的前端新手指引
在王者的世界里,不仅仅只有快乐,还能搞学习,让你成为快乐而又富有知识的人.这其中的功臣,这不得不说的就是它的新手指引. 为什么这么说呢?我们先来看几张图. 没错,上面的就是王者荣耀的新手引导,手把手教 ...
- 【SwiftUI模块】0012、SwiftUI-搭建一个类似微博、网易云、抖音个人页面的头部下拉放大图片效果
SwiftUI模块系列 - 已更新11篇 SwiftUI项目 - 已更新1个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI3.0.下拉放大.tableview粘性头部.头部下拉放大图 ...
- 【SwiftUI模块】0033、SwiftUI创建用户双击帖子时的心形动画
SwiftUI模块系列 - 已更新33篇 SwiftUI项目 - 已更新3个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI4.0.双击动画.心形动画.动画 运行环境: SwiftUI ...
- 【SwiftUI模块】0018、SwiftUI搭建一个类似支付宝中的余额宝余额数字动画效果
SwiftUI模块系列 - 已更新18篇 SwiftUI项目 - 已更新1个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI3.0.支付宝.余额宝.数字动画 运行环境: SwiftUI ...
- 【SwiftUI模块】0032、SwiftUI搭建一个类似抖音评论模块的半页模式 - 底部抽屉
SwiftUI模块系列 - 已更新32篇 SwiftUI项目 - 已更新3个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI4.0.抖音评论.半页模式.底部抽屉 运行环境: Swift ...
- 【SwiftUI模块】0013、SwiftUI搭建-类似蚂蚁财富的基金累计盈亏的走势图
SwiftUI模块系列 - 已更新13篇 SwiftUI项目 - 已更新1个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI3.0.基金.走势图.蚂蚁财富 运行环境: SwiftUI3 ...
- 【SwiftUI模块】0052、使用SwiftUI设计时尚旅行应用程序UI
SwiftUI模块系列 - 已更新52篇 SwiftUI项目 - 已更新5个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI4.0.旅行.旅行App.旅行应用程序.时尚旅行 运行环境: ...
最新文章
- openssl在64位的机器上编译32位的库
- SQLITE中原子提交的实现
- 关于ASP.NET中fileupload控件的缺点
- 《mysql必知必会》学习_第11章_20180801_欢
- Python列表以及列表的处理方法
- 高等数学基础 - 高等数学主要内容
- 备忘:VC++ 中的异常处理
- 洛谷P1852 奇怪的字符串
- mysql导入dat文件_mysql dat 导入数据库
- $_FILES[file]['error']
- tensorflow中的global_step参数(转)
- 祝愿父亲节里的父亲们快乐!
- 手机APP应用怎样从公网访问局域网WEB应用
- 烽火路由路虚拟服务器,烽火路由器怎么设置?烽火路由器设置详解
- 机器学习算法工程师面试经历
- CSDN公式插入——关于对数
- 第十一周助教工作总结——NWNU李泓毅
- 将自家的位置标注到地图上(51ditu.com)
- 2dpca matlab程序,[转载]人脸识别-2dpca之Matlab程序
- vlan的几种划分方式