【SwiftUI模块】0004、SwiftUI-<探探App>喜欢手势卡片

SwiftUI小功能模块系列
0001、SwiftUI自定义Tabbar动画效果
0002、SwiftUI自定义3D动画导航抽屉效果
0003、SwiftUI搭建瀑布流-交错网格-效果
0004、SwiftUI-<探探App>喜欢手势卡片

技术:SwiftUI3.0、喜欢卡片、手势、探探App、TinderApp
运行环境:
SwiftUI3.0 + Xcode13.4.1 + MacOS12.5 + iPhone Simulator iPhone 13 Pro Max

概述

使用SwiftUI做一个类似探探App的喜欢手势卡片

详细

一、运行效果

请添加图片描述

二、项目结构图

在这里插入图片描述

三、程序实现 - 过程

思路:
1.创建主窗口
2.搭建导航和底部按钮和中间卡片部分
3.添加卡片用户数据、自定义喜欢卡片UI
4.处理拖拽手势操作、左右滑动进行通知卡片页面删除卡片

1.创建一个项目命名为 TantanUI

在这里插入图片描述
在这里插入图片描述

1.1.引入资源文件和颜色

颜色
Blue #8BBDE6
Gray #9A9D9F
Pink #E76397
Yellow #E7BB71
随机美女6-10张

在这里插入图片描述

2. 创建一个虚拟文件New Group 命名为 View

在这里插入图片描述
在这里插入图片描述

2. 创建一个文件New File 选择SwiftUI View类型 命名为Home

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2. 创建一个文件New File 选择SwiftUI View类型 命名为StackCardView

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. 创建一个虚拟文件New Group 命名为 Model

在这里插入图片描述
在这里插入图片描述

3. 创建一个文件New File 选择SwiftUI View类型 命名为User 、并且删除预览视图、改造成模型 继承Identifiable

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4. 创建一个虚拟文件New Group 命名为 ViewModel

在这里插入图片描述
在这里插入图片描述

4. 创建一个文件New File 选择SwiftUI View类型 命名为HomeViewModel 、并且删除预览视图、改造成模型 继承ObservableObject 类型是class

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Code

ContentView - 主窗口

主要是展示主窗口Home

//
//  ContentView.swift
//  Shared
//
//  Created by 李宇鸿 on 2022/8/13.
//

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. 创建卡片数据、创建卡片UI
  5. 监听卡片没有情况 按钮不可点击
  6. 监听按钮和手势的操作卡片的通知传递
//
//  Home.swift
//  TanTanUI (iOS)
//
//  Created by 李宇鸿 on 2022/8/14.
//

import SwiftUI

struct Home: View {
    
    
    @StateObject var homeData : HomeViewModel = HomeViewModel()
    
    var body: some View {
    
    
        VStack{
    
    
            
            Button{
    
    
                
            }label: {
    
    
                Image("menu")
                    .resizable()
                    .renderingMode(.template)
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 22, height: 22)
            }
            .frame(maxWidth:.infinity,alignment: .leading)
            .overlay(
                Text("Disover")
                    .font(.title.bold())
            )
            .foregroundColor(.black)
            .padding()
            
            
            // 用户布局
            ZStack{
    
    
                
                if let users = homeData.displaying_users{
    
    
                    
                    if users.isEmpty{
    
    
                        Text("Come back later can find more matches for you!")
                            .font(.caption)
                            .foregroundColor(.gray)
                    }
                    else{
    
    
                        // 显示卡片……
                        //从Zstack开始反转牌
                        //你可以在这里使用反向操作…
                        //或者你可以在抓取用户时…
                        ForEach(users.reversed()){
    
     user in
                            
                            // 卡片视图
                            StackCardView(user: user)
                                .environmentObject(homeData)
                            
                        }
                    }
                    
                }
                else{
    
    
                    ProgressView()
                }
            }
            .padding(.top,30)
            .padding()
            .padding(.vertical)
            .frame(maxWidth:.infinity,maxHeight: .infinity)

            
            // 多个按钮
            HStack(spacing:15){
    
    
                Button{
    
    
                    
                }label: {
    
    
                    Image(systemName: "arrow.uturn.backward")
                        .font(.system(size: 15,weight: .bold))
                        .foregroundColor(.white)
                        .shadow(radius: 5)
                        .padding(13)
                        .background(Color("Gray"))
                        .clipShape(Circle())
                    
                }
                
                
                Button{
    
    
                    doSwipe()
                }label: {
    
    
                    Image(systemName: "xmark")
                        .font(.system(size: 20,weight: .black))
                        .foregroundColor(.white)
                        .shadow(radius: 5)
                        .padding(13)
                        .background(Color("Blue"))
                        .clipShape(Circle())
                    
                }
                
                
                Button{
    
    
                    
                }label: {
    
    
                    Image(systemName: "star.fill")
                        .font(.system(size: 15,weight: .bold))
                        .foregroundColor(.white)
                        .shadow(radius: 5)
                        .padding(13)
                        .background(Color.yellow)
                        .clipShape(Circle())
                    
                }
                
                Button{
    
    
                    doSwipe(rightSwipe: true)

                }label: {
    
    
                    Image(systemName: "suit.heart.fill")
                        .font(.system(size: 20,weight: .black))
                        .foregroundColor(.white)
                        .shadow(radius: 5)
                        .padding(13)
                        .background(Color("Pink"))
                        .clipShape(Circle())
                    
                }
                
                
                
            }
            .padding(.bottom)
            // 设置按钮不可点击
            .disabled(homeData.displaying_users?.isEmpty ?? false)
            .opacity((homeData.displaying_users?.isEmpty ?? false ? 0.6 : 1))
        }
        .frame(maxWidth:.infinity,maxHeight: .infinity,alignment: .top)

    }
    
    
    // removing cards when doing Swipe...
    // 在刷牌时移除卡片…
    func doSwipe(rightSwipe: Bool = false){
    
    
        guard let first = homeData.displaying_users?.first else{
    
    
            return
        }
        
        // Using Notification to post and receiving in Stack Cards...
        // 使用通知在堆叠卡片中寄收……
        NotificationCenter.default.post(name: NSNotification.Name("ACTIONROMBUTTON"), object: nil,userInfo: [
            "id": first.id,
            "rightSwipe": rightSwipe
        ])
        
    }
    
}

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

StackCardView.swift - 卡片视图

主要是做 卡片UI展示
思路

  1. 搭建卡片UI
  2. 监听上层传递的通知手势处理拖拽操作 进行监听是否删除卡片
  3. 拖拽卡片动画设计

//
//  StackCardView.swift
//  TanTanUI (iOS)
//
//  Created by 李宇鸿 on 2022/8/15.
//

import SwiftUI

struct StackCardView: View {
    
    
    @EnvironmentObject var homeData: HomeViewModel
    var user: User
    
    // 手势属性……
    @State var offset : CGFloat = 0;
    @GestureState var isDragging : Bool = false
    
    @State var endSwipe : Bool = false
    
    var body: some View {
    
    
        GeometryReader {
    
    proxy in
            let size = proxy.size
            
            let index = CGFloat(homeData.getIndex(user: user))

            // 显示下两个卡在顶部像一个堆栈....
            let topOffset = (index <= 2 ? index : 2 ) * 15

            ZStack{
    
    
                
                Image(user.profilePic)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                // 减少宽度
                    .frame(width: size.width - topOffset, height: size.height)
                    .cornerRadius(15)
                    .offset(y:-topOffset)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
            
        }
        .offset(x:offset)
        .rotationEffect(.init(degrees: getRotation(angle: 8)))
        .contentShape(Rectangle().trim(from: 0, to: endSwipe ? 0 : 1))
        .gesture(
        DragGesture()
            .updating($isDragging, body: {
    
     value, out, _ in
                out = true
            })
            .onChanged({
    
     value in
                let translation = value.translation.width
                offset = (isDragging ? translation : .zero)
            })
            .onEnded({
    
     vlaue in
                let width = getRect().width
                let translation = vlaue.translation.width
                let checkingStatus = (translation > 0 ? translation : -translation)
                
                
                withAnimation {
    
    
                    if checkingStatus > (width / 2){
    
    
                        // 删除卡片
                        offset = (translation > 0 ? width : -width) * 2
                        endSwipeActions()
                        
                        if translation > 0 {
    
    
                            rightSwipe()
                        }
                        else {
    
    
                            leftSwipe()
                        }

                    }
                    else
                    {
    
    
                        // reset...
                        // 重置
                        offset = .zero
                    }
                }
                
            })
        )
        
        // 接收通知发布....
        .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ACTIONROMBUTTON"),object:nil)) {
    
     data in
            guard let info = data.userInfo else{
    
    
                return
            }
            let id = info["id"] as? String ?? ""
            let rightSwipe = info["rightSwipe"] as? Bool ?? false
            let width = getRect().width - 50
            if user.id == id {
    
    
                // 删除卡片
                withAnimation{
    
    
                    offset = (rightSwipe ? width : -width) * 2
                    endSwipeActions()
                    
                    if rightSwipe {
    
    
                        self.rightSwipe()
                    }
                    else {
    
    
                        leftSwipe()
                    }
                }
                
            }

        }
        
    }
    // 旋转
    func getRotation(angle: Double)-> Double{
    
    
        let rotation = (offset / (getRect().width - 50)) * angle
        return rotation
    }
    
    func endSwipeActions(){
    
    
        withAnimation(.none) {
    
    endSwipe = true}
        //移走卡后,将卡从数组中移除以保存内存…
        //根据你的动画持续时间延迟时间…
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    
    
            if let _ =  homeData.displaying_users?.first{
    
    
                let _ = withAnimation {
    
    
                    homeData.displaying_users?.removeFirst()
                }
            }
        }
        
    }
    
    func leftSwipe() {
    
    
        //  在这里做动作
        print("Left Swiped")
    }
    
    func rightSwipe() {
    
    
        //  在这里做动作
        print("right Swiped")
    }
}

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


// 扩展视图获得边界…
extension View{
    
    
    func getRect()->CGRect{
    
    
        return UIScreen.main.bounds
    }
}


User - 模型

用户模型

import SwiftUI

// 用户模型
struct User: Identifiable {
    
    
    var id = UUID().uuidString
    var name : String
    var place : String
    var profilePic : String
}



HomeViewModel - 视图模型

视图模型

  1. 处理创建用户数据
  2. 是否还有用户数据展示
  3. 获取当前图片的索引
import SwiftUI

// 视图模型,它保存所有用户数据…
class HomeViewModel: ObservableObject {
    
    
    //存储所有获取的用户在这里…
    //因为我们正在构建UI,所以在这里使用示例用户…
    @Published var feched_users : [User] = []
    
    @Published var displaying_users : [User]?
    
    init(){
    
    
        // 获取用户……
        feched_users = [
            User(name: "Natalia", place: "Vadlia NYC", profilePic: "User1"),
            User(name: "Elisa", place: "Central NYC", profilePic: "User2"),
            User(name: "Jasmine", place: "Metropolitan Museum NYC", profilePic: "User3"),
            User(name: "Zahra", place: "Liberty NYC", profilePic: "User4"),
            User(name: "Angelina", place: "Empier State NYC", profilePic: "User5"),
            User(name: "Brittany", place: "Time Squart NYC", profilePic: "User6")
        ]
        //存储在显示用户…
        //显示用户的是什么?
        //它将根据用户交互来更新/删除,以减少内存使用…
        //同时我们需要所有获取的用户数据…
        displaying_users = feched_users
        
   
    }
    
    // 检索索引…
    func getIndex(user: User)->Int{
    
    
        
        let index = displaying_users?.firstIndex(where: {
    
     currentUser in
            return user.id == currentUser.id
        }) ?? 0
        
        return index
    }
    
}

demo源码

如需看源码,请点击下载!

猜你喜欢

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