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的旋转动画
⭐️新手指示效果:

  1. 获取根控制器
  2. 从根控制器获取view
  3. 从根控制器的view 里面添加一个view 用来当做聚光灯的背景
  4. 从聚光灯背景中反向提取指定区域 设置成白色 也就是高光效果

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 - 引导页

思路

  1. 主要部分核心模块 - 滚动页面 - UI创建包含图片、两个文本
  2. 叠加层 - 最顶层 做了指示器 和 跳过、下一页的按钮 指示器使用Circle创建 和 做了登录和注册按钮
  3. 创建引导页面数据、和滚动核心UIOffsetPageTabView
  4. 添加滚动的时候 做一个圆形矩形背景 进行360度自身旋转
  5. ⭐️给需要新手指示的 添加聚光灯效果 - 通过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视图逻辑处理

主要是做 滚动页面的逻辑处理
思路

  1. 基于ScrollView进行处理
  2. 提供初始化构造器 - 方便上层通过偏移量进行 调用初始化 init(offset: Binding<CGFloat> , @ViewBuilder content: @escaping()->Content)
  3. 监听ScrollView滚动的代理、更新当前的偏移量 class Coordinator
  4. 提供滚动的代理 滚动到下一个页面 进行是否更新当前的偏移量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 - 扩展 用来处理 新手指引

思路

  1. 获取根控制器
  2. 从根控制器获取view
  3. 从根控制器的view 里面添加一个view 用来当做聚光灯的背景
  4. 从聚光灯背景中反向提取指定区域 设置成白色 也就是高光效果
//
//  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新手指引-新手指示-聚光灯介绍说明相关推荐

  1. 口袋进化服务器维护,《口袋进化》新手指引.新手指导

    第一只精灵,你做好成为一名训练家的准备了么? 作为一枚纯纯的萌新,在刚接触时,建议根据游戏提供的引导,来逐步熟悉基本玩法和养成体系. 除此之外,别忘了阅读新手FAQ!在这里,为大家总结了一些新手常见的 ...

  2. 【SwiftUI模块】0008、SwiftUI-自定义启动闪屏动画-App启动闪屏曲线路径动画

    SwiftUI小功能模块系列 0001.SwiftUI自定义Tabbar动画效果 0002.SwiftUI自定义3D动画导航抽屉效果 0003.SwiftUI搭建瀑布流-交错网格-效果 0004.Sw ...

  3. 从王者荣耀里我学会的前端新手指引

    在王者的世界里,不仅仅只有快乐,还能搞学习,让你成为快乐而又富有知识的人.这其中的功臣,这不得不说的就是它的新手指引. 为什么这么说呢?我们先来看几张图. 没错,上面的就是王者荣耀的新手引导,手把手教 ...

  4. 【SwiftUI模块】0012、SwiftUI-搭建一个类似微博、网易云、抖音个人页面的头部下拉放大图片效果

    SwiftUI模块系列 - 已更新11篇 SwiftUI项目 - 已更新1个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI3.0.下拉放大.tableview粘性头部.头部下拉放大图 ...

  5. 【SwiftUI模块】0033、SwiftUI创建用户双击帖子时的心形动画

    SwiftUI模块系列 - 已更新33篇 SwiftUI项目 - 已更新3个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI4.0.双击动画.心形动画.动画 运行环境: SwiftUI ...

  6. 【SwiftUI模块】0018、SwiftUI搭建一个类似支付宝中的余额宝余额数字动画效果

    SwiftUI模块系列 - 已更新18篇 SwiftUI项目 - 已更新1个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI3.0.支付宝.余额宝.数字动画 运行环境: SwiftUI ...

  7. 【SwiftUI模块】0032、SwiftUI搭建一个类似抖音评论模块的半页模式 - 底部抽屉

    SwiftUI模块系列 - 已更新32篇 SwiftUI项目 - 已更新3个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI4.0.抖音评论.半页模式.底部抽屉 运行环境: Swift ...

  8. 【SwiftUI模块】0013、SwiftUI搭建-类似蚂蚁财富的基金累计盈亏的走势图

    SwiftUI模块系列 - 已更新13篇 SwiftUI项目 - 已更新1个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI3.0.基金.走势图.蚂蚁财富 运行环境: SwiftUI3 ...

  9. 【SwiftUI模块】0052、使用SwiftUI设计时尚旅行应用程序UI

    SwiftUI模块系列 - 已更新52篇 SwiftUI项目 - 已更新5个项目 往期Demo源码下载 技术:SwiftUI.SwiftUI4.0.旅行.旅行App.旅行应用程序.时尚旅行 运行环境: ...

最新文章

  1. openssl在64位的机器上编译32位的库
  2. SQLITE中原子提交的实现
  3. 关于ASP.NET中fileupload控件的缺点
  4. 《mysql必知必会》学习_第11章_20180801_欢
  5. Python列表以及列表的处理方法
  6. 高等数学基础 - 高等数学主要内容
  7. 备忘:VC++ 中的异常处理
  8. 洛谷P1852 奇怪的字符串
  9. mysql导入dat文件_mysql dat 导入数据库
  10. $_FILES[file]['error']
  11. tensorflow中的global_step参数(转)
  12. 祝愿父亲节里的父亲们快乐!
  13. 手机APP应用怎样从公网访问局域网WEB应用
  14. 烽火路由路虚拟服务器,烽火路由器怎么设置?烽火路由器设置详解
  15. 机器学习算法工程师面试经历
  16. CSDN公式插入——关于对数
  17. 第十一周助教工作总结——NWNU李泓毅
  18. 将自家的位置标注到地图上(51ditu.com)
  19. 2dpca matlab程序,[转载]人脸识别-2dpca之Matlab程序
  20. vlan的几种划分方式

热门文章

  1. 靶场环境的搭建(2020年6月15日学习笔记)
  2. 《算竞(紫书)》笔记1 STL入门
  3. java 内存模型JMM解析
  4. 宝洁网测管道题技巧_宝洁笔试网测小技巧分享
  5. 【FPGA】Chipscope的基本使用
  6. PDFJS跨域显示PDF文件的两种方法
  7. maven打包maven-shade-plugin报错Non-parseable POM解决方法
  8. 【第25题】一球从 100 米高度自由落下,每次落地后反跳回原高度的一半
  9. 解决hbase RegionServer频繁宕机的一些办法
  10. 项目管理案例分析:如何通过黄金圈法则建立共识?