【SwiftUI模块】0025、 SwiftUI搭建一个轮播列表

SwiftUI模块系列 - 已更新25篇
SwiftUI项目 - 已更新2个项目
往期Demo源码下载

技术:SwiftUI、SwiftUI3.0、轮播、轮播器、轮播列表、Banner、广告轮播器
运行环境:
SwiftUI3.0 + Xcode13.4.1 + MacOS12.5 + iPhone Simulator iPhone 13 Pro Max
第三方库Swift、Object-C推荐
Swift
Object

概述

使用SwiftUI搭建一个轮播列表

详细

一、运行效果

请添加图片描述

二、项目结构图

在这里插入图片描述

三、程序实现 - 过程

思路:

  1. 搭建顶部的返回 和标题
  2. 分段控制器
  3. 主体部分: 电影模块的-轮播列表
  4. 指示器 跟踪电影模块
  5. 监听手势拖拽的时候 处理更新同步 轮播图片和指示器

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 - 主页

思路

  1. 搭建顶部的返回 和标题
  2. 分段控制器
  3. 主体部分: 电影模块的-轮播列表
  4. 指示器 跟踪电影模块
//
//  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
}


猜你喜欢

转载自blog.csdn.net/qq_42816425/article/details/126715937