SwiftUI案例:3D旋转图片播放器

2022-08-05 19:23:35 浏览数 (1)

SwiftUI案例:3D旋转图片播放器

效果

目标

  • 实现多张图片的3D切换查看功能

外观配置

任选 7 张任意尺寸的图片按 p1p7 进行命名并拖拽进 Assets.xcassets 文件中(如图所示)

创建View视图

在工作区的项目文件夹下创建名为 ViewGroup 并在其中依次创建 Home.swift CarouseBodyView.swift ScrollViewOffsetModifier.swift 视图文件,其功能如下:

  1. Home.swift: 主视图,用来控制文字布局与图片布局;
  2. CarouseBodyView.swift: 控件视图,用来具体实现文字部分与图片部分;
  3. ScrollViewOffsetModifier.swift: 滚动偏量视图,用来设置3D滚动效果;

视图的实现

主视图 Home.swift

大致定义整个屏幕视图的布局与容器接口。

代码语言:javascript复制
import SwiftUI

struct Home: View {
	//定义当前的图片
    @State var currentTab = "p1"
    var body: some View {
        //ZStack视图容器
        ZStack {
            GeometryReader {
            	//背景阅读器
                proxy in
                let size = proxy.size
                //图片样式
                Image(currentTab)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: size.width, height: size.height)
                    .cornerRadius(1)
            }
            //约束ZStack容器样式
            .ignoresSafeArea()
            .overlay(.ultraThinMaterial)
            .colorScheme(.dark)
            //调用CarouseBodyView控件将图片信息返回给currentTab变量
            TabView(selection: $currentTab) {
                ForEach(1...7, id: .self) { index in
                    CarouseBodyView(index: index)
                }
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
        }
    }
}

struct Home_Previews: PreviewProvider {
    static var previews: some View {
        Home()
    }
}

控件视图 CarouseBodyView.swift

通过视图容器的嵌套布局,实现 Home.swift 中组件的文本与图片的具体内容。

代码语言:javascript复制
import SwiftUI

struct CarouseBodyView: View {
	//整型index标注图片序号
    var index: Int
    //定义浮点型offset偏量大小
    @State var offset:CGFloat = 0
    var body: some View {
        GeometryReader {proxy in
            let size = proxy.size
            ZStack {
            	//通过index来显示指定图片
                Image("p(index)")
                	//设置图片样式
                    .resizable()
                    .aspectRatio( contentMode: .fill)
                    .frame(width: size.width - 8, height: size.height / 1.2, alignment: .center)
                    .cornerRadius(12) //圆角
                //嵌套VStack(垂直排列子元素的视图)容器
                VStack(spacing: 25) {
                	//嵌套VStack容器
                    VStack(alignment: .leading, spacing: 10) {
                        Text("标题")
                            .font(.title2.bold())
                            .kerning(1.5)
                        Text("副标题")
                            .foregroundStyle(.primary)
                    }
                    .foregroundStyle(.white)
                    .padding(.top)
                    Spacer()
                    VStack (alignment:.leading){
                    	//嵌套HStack容器
                        HStack( spacing: 25) {
                            Image("justine")
                                .resizable()
                                .aspectRatio(contentMode: .fill)
                                .frame(width: 55, height: 55)
                                .clipShape(Circle())
                            VStack(alignment: .leading, spacing: 6) {
                                Text("Dioxide.CN")
                                    .font(.title2.bold())
                                Text("个人开发者")
                                    .foregroundStyle(.secondary)
                            }
                            .foregroundStyle(.black)
                        }
                        //嵌套HStack容器
                        HStack {
                            VStack {
                                Text("2022")
                                    .font(.title2.bold())
                                Text("文章")
                                    .foregroundStyle(.secondary)
                            }
                            .frame(maxWidth: .infinity)
                            VStack {
                                Text("1000")
                                    .font(.title2.bold())
                                Text("粉丝")
                                    .foregroundStyle(.secondary)
                            }
                            .frame(maxWidth: .infinity)
                            //嵌套VStack容器
                            VStack {
                                Text("50")
                                    .font(.title2.bold())
                                Text("关注者")
                                    .foregroundStyle(.secondary)
                            }
                            .frame(maxWidth: .infinity)
                        }
                        .padding(.top)
                    }
                    .padding(20)
                    .padding(.horizontal)
                    .background(.white)
                    .cornerRadius(12)
                }
                .padding(20)
            }
            .frame(width: size.width - 8, height: size.height / 1.2)
            .frame(width: size.width, height: size.height)
        }
        .tag("p(index)")
        
        //调用getProgress()函数设置整体容器偏量
        .rotation3DEffect(
        	.init(degrees: getProgress() * 90), //旋转度数
            axis:(x:0, y: 1, z: 0), 
            anchor: offset > 0 ?.leading : .trailing, //锚点
            anchorZ: 0, 
            perspective: 0.5
        )
        
        //调用ScrollViewOffsetModifier控件定义3D旋转动画
        .modifier(
        	ScrollViewOffsetModifier(
            	anchorPoint: .leading, 
                offset: $offset
            )
        )
    }
    
    func getProgress()->CGFloat {
    	//progress = (-)偏移 ÷ 弹性screen宽度
        let progress = -offset / UIScreen.main.bounds.width
        return progress
    }
}

struct CarouseBodyView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

滚动偏量视图 ScrollViewOffsetModifier.swift

这类似于 css 中的 transform: rotateX() transform: rotateY() 属性,通过屏幕反馈的滑动位置来控制每个图片组件的 X Y 轴偏量。

代码语言:javascript复制
import SwiftUI

struct ScrollViewOffsetModifier: ViewModifier {
	//定义顶部锚点
    var anchorPoint: Anchor = .top
    @Binding var offset: CGFloat
    func body(content: Content) -> some View {
        content
            .overlay(
                GeometryReader { proxy -> Color in
                let frame = proxy.frame(in: .global)
                DispatchQueue.main.async {
                    //基于锚点设置偏移量
                    switch anchorPoint {
                      case .top:
                          offset = frame.minY
                      case .bottom:
                          offset = frame.maxY
                      case .leading:
                          offset = frame.minX
                      case .trailing:
                          offset = frame.maxX
                    }
                }
                return Color.clear
            }
        )
    }
}
//为ScrollView和tab视图定义修饰器
//枚举锚点位置
enum Anchor {
    case top
    case bottom
    case leading
    case trailing
}

源码

3d-carousel-slider.zip

来源:百度网盘 | 提取码:up0f

3d-carousel-slider.zip

来源:蓝奏云网盘 | 提取码:1z5b

0 人点赞