技术:SwiftUI、SwiftUI3.0、轮播、轮播器、轮播列表、Banner、广告轮播器
运行环境:
SwiftUI3.0 + Xcode13.4.1 + MacOS12.5 + iPhone Simulator iPhone 13 Pro Max
第三方库Swift、Object-C推荐
Swift
Object
SwiftUI搭建一个轮播列表
-
- 概述
- 详细
-
- 一、运行效果
- 二、项目结构图
- 三、程序实现 - 过程
-
- 1.创建一个项目命名为 `Carousel_Custom`
- 1.1.引入资源文件和颜色
- 2. 创建一个虚拟文件`New Group` 命名为 `View`
- 3. 创建一个虚拟文件`New Group` 命名为 `Model`
- 4. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`Home`
- 5. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`SnapCarousel`
- 6. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`Post` 并且继承于`Identifiable` 删除预览视图、修改成模型
- Code
概述
使用SwiftUI搭建一个轮播列表
详细
一、运行效果
二、项目结构图
三、程序实现 - 过程
思路:
- 搭建顶部的返回 和标题
- 分段控制器
- 主体部分: 电影模块的-轮播列表
- 指示器 跟踪电影模块
- 监听手势拖拽的时候 处理更新同步 轮播图片和指示器
1.创建一个项目命名为 Carousel_Custom
1.1.引入资源文件和颜色
随机电影封面图片7张
2. 创建一个虚拟文件New Group
命名为 View
3. 创建一个虚拟文件New Group
命名为 Model
4. 创建一个文件New File
选择SwiftUI View
类型 命名为Home
5. 创建一个文件New File
选择SwiftUI View
类型 命名为SnapCarousel
主要是:处理每一张轮播图片滚动的操作和初始化属性设置
6. 创建一个文件New File
选择SwiftUI View
类型 命名为Post
并且继承于Identifiable
删除预览视图、修改成模型
Code
ContentView - 主窗口
主要是展示主窗口
Home
//
// ContentView.swift
// Shared
//
// Created by 李宇鸿 on 2022/9/6.
//
import SwiftUI
struct ContentView: View {
var body: some View {
Home()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Home - 主页
思路
- 搭建顶部的返回 和标题
- 分段控制器
- 主体部分: 电影模块的-轮播列表
- 指示器 跟踪电影模块
//
// Home.swift
// Carousel_Custom (iOS)
//
// Created by 李宇鸿 on 2022/9/6.
//
import SwiftUI
struct Home: View {
@State var currentIndex : Int = 0
@State var posts : [Post] = []
@State var currentTab = "Slide Show"
@Namespace var animation
var body: some View {
VStack(spacing:15){
VStack(alignment: .leading, spacing: 12) {
Button{
}label: {
Label{
Text("Back")
.fontWeight(.semibold)
}icon: {
Image(systemName: "chevron.left")
.font(.title2.bold())
}
.foregroundColor(.primary)
}
Text("My Wishes")
.font(.title)
.fontWeight(.black)
}
.frame(maxWidth:.infinity,alignment: .leading)
.padding()
// 分段控制器
HStack(spacing: 0){
TabButton(title: "Slide Show", animation: animation, currentTab: $currentTab)
TabButton(title: "List", animation: animation, currentTab: $currentTab)
}
.background(Color.black.opacity(0.04),in: RoundedRectangle(cornerRadius: 10))
.padding(.horizontal)
// 快速旋转木马……
// 如果设置是traillingSpace = 100 spacing = 50 ,那么整个页面只会占据1张图片 和 左右间距50 如果traillingSpace = 100 ,spacing = 30 。可以看到下一张图片的40宽度的范围
// SnapCarousel(spacing:50, traillingSpace:100,
SnapCarousel(index: $currentIndex, items: posts) {
post in
GeometryReader{
proxy in
let size = proxy.size
Image(post.postImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: size.width)
.cornerRadius(12)
}
}
.padding(.vertical,40)
// 指示器
HStack(spacing:10){
ForEach(posts.indices,id:\.self){
index in
Circle()
.fill(Color.black.opacity(currentIndex == index ? 1 : 0.1))
.frame(width: 10, height: 10)
.scaleEffect(currentIndex == index ? 1.4 : 1)
.animation(.spring(), value: currentIndex == index)
}
}
.padding(.bottom,40)
}
.frame(maxHeight:.infinity,alignment: .top)
.onAppear{
for index in 1...5{
posts.append(Post(postImage: "post\(index)"))
}
}
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
}
}
// 选项卡按钮
struct TabButton : View{
var title : String
var animation : Namespace.ID
@Binding var currentTab : String
var body: some View {
Button {
withAnimation(.spring()){
currentTab = title
}
} label: {
Text(title)
.fontWeight(.bold)
.foregroundColor(currentTab == title ? .white : .black)
.frame(maxWidth: .infinity)
.padding(.vertical, 9)
.background(
ZStack{
if currentTab == title {
RoundedRectangle(cornerRadius: 10)
.fill(.black)
.matchedGeometryEffect(id: "TAB", in: animation)
}
}
)
}
}
}
SnapCarousel - 主要处理每一张轮播图片滚动的操作和初始化属性设置
用来设置图片的间距、总间距大小、总视图、当前展示的视图、当前索引值
//
// SnapCarousel.swift
// Carousel_Custom (iOS)
//
// Created by 李宇鸿 on 2022/9/6.
//
import SwiftUI
// 接收名单…
struct SnapCarousel<Content:View, T: Identifiable> : View {
var content : (T) -> Content
var list : [T]
// 属性……
var spacing : CGFloat
var traillingSpace: CGFloat
@Binding var index : Int
init(spacing: CGFloat = 15,traillingSpace : CGFloat = 100,index : Binding<Int>,items:[T],@ViewBuilder conent : @escaping (T)-> Content){
self.list = items
self.spacing = spacing
self.traillingSpace = traillingSpace
self._index = index
self.content = conent
}
// 偏移量
@GestureState var offset : CGFloat = 0
@State var currentIndex : Int = 0
var body: some View{
GeometryReader{
proxy in
// 设置正确的旋转木马宽度…
// 单侧的快速旋转木马
let width = proxy.size.width - (traillingSpace - spacing)
let adjustMentWidth = (traillingSpace / 2) - spacing
HStack(spacing:spacing){
ForEach(list){
item in
content(item)
.frame(width:proxy.size.width - traillingSpace)
}
}
// 间距将是水平填充…
.padding(.horizontal,spacing)
// 仅设置在第0个索引之后。
.offset(x: (CGFloat(currentIndex) * -width) + (currentIndex != 0 ? adjustMentWidth : 0) + offset)
.gesture(
DragGesture()
.updating($offset, body: {
value, out, _ in
out = value.translation.width
})
.onEnded({
value in
// 更新当前指数……
let offsetX = value.translation.width
//我们将转换成进度(0 - 1)
//,取整值....
//根据当前索引增加或减少的进度…
let progress = -offsetX / width
let roundIndex = progress.rounded()
// 设置最小值
currentIndex = max(min(currentIndex + Int(roundIndex) , list.count - 1),0)
// 更新索引
currentIndex = index
})
.onChanged({
value in
// 更新当前指数……
let offsetX = value.translation.width
//我们将转换成进度(0 - 1)
//,取整值....
//根据当前索引增加或减少的进度…
let progress = -offsetX / width
let roundIndex = progress.rounded()
// setting min ...
// 设置最小值
index = max(min(currentIndex + Int(roundIndex) , list.count - 1),0)
})
)
}
// 当offset = 0时的动画
.animation(.easeInOut, value: offset == 0)
}
}
struct SnapCarousel_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Post - 模型
//
// Post.swift
// Carousel_Custom (iOS)
//
// Created by 李宇鸿 on 2022/9/6.
//
import SwiftUI
// Post Model And Sample Data...
struct Post : Identifiable {
var id = UUID().uuidString
var postImage : String
}