Tinder like ui using SwiftUI

Overview
//
//  User.swift
//  CardUI
//
//  Created by paige on 2021/12/09.
//

import SwiftUI

struct User: Identifiable {

    var id = UUID().uuidString
    var name: String
    var place: String
    var profilePic: String

}
//
//  HomeViewModel.swift
//  CardUI
//
//  Created by paige on 2021/12/09.
//

import SwiftUI

class HomeViewModel: ObservableObject {

    // Store All the fetched Users here...
    // Since we're building UI so using sample Users here....
    @Published var fetchedUsers: [User] = []

    @Published var displayingUsers: [User]?

    init() {

        // fetching users...
        fetchedUsers = [
            User(name: "Natalia", place: "Vadalia NYC", profilePic: "img1"),
            User(name: "Natalia", place: "Vadalia NYC", profilePic: "img2"),
            User(name: "Natalia", place: "Vadalia NYC", profilePic: "img3"),
            User(name: "Natalia", place: "Vadalia NYC", profilePic: "img1"),
            User(name: "Natalia", place: "Vadalia NYC", profilePic: "img2"),
        ]

        // storing it in displaying users...
        // what is displaying users?
        // it will be updated/removed based on user interaction to reduce memory usage....
        // and the same time we need all the fetched uesrs data....
        displayingUsers = fetchedUsers

    }

    // retreiving index...
    func getIndex(user: User) -> Int {
        let index = displayingUsers?.firstIndex(where: {
            return $0.id == user.id
        }) ?? 0
        return index
    }

}
//
//  Home.swift
//  CardUI
//
//  Created by paige on 2021/12/09.
//

import SwiftUI

struct Home: View {

    @StateObject private var viewModel = 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("Discover")
                    .font(.title.bold())
            )
            .foregroundColor(.black)
            .padding()

            // Users Stack...
            ZStack {

                if let users = viewModel.displayingUsers {

                    if users.isEmpty {
                        Text("Come back later we can find more matches for you!")
                            .font(.caption)
                            .foregroundColor(.gray)
                    } else {

                        // Displaying Cards
                        // Cards are reversed since its ZStack...
                        // You can use reverse here...
                        // or you can use while fetching users...
                        ForEach(users.reversed()) { user in

                            // Card View...
                            StackCardView(user: user)
                                .environmentObject(viewModel)

                        }

                    }

                } else {
                    ProgressView()
                }

            }
            .padding(.top, 30)
            .padding()
            .padding(.vertical)
            .frame(maxWidth: .infinity, maxHeight: .infinity)

            // Action Buttons
            HStack(spacing: 15) {
                Button {

                } label: {
                    Image(systemName: "arrow.uturn.backward")
                        .font(.system(size: 15, weight: .bold))
                        .foregroundColor(.white)
                        .shadow(radius: 5)
                        .padding(13)
                        .background(.gray)
                        .clipShape(Circle())
                }
                Button {
                    doSwipe(rightSwipe: false)
                } label: {
                    Image(systemName: "xmark")
                        .font(.system(size: 15, weight: .black))
                        .foregroundColor(.white)
                        .shadow(radius: 5)
                        .padding(13)
                        .background(.blue)
                        .clipShape(Circle())
                }
                Button {

                } label: {
                    Image(systemName: "star.fill")
                        .font(.system(size: 15, weight: .bold))
                        .foregroundColor(.white)
                        .shadow(radius: 5)
                        .padding(13)
                        .background(.yellow)
                        .clipShape(Circle())
                }
                Button {
                    doSwipe(rightSwipe: true)
                } label: {
                    Image(systemName: "suit.heart.fill")
                        .font(.system(size: 15, weight: .black))
                        .foregroundColor(.white)
                        .shadow(radius: 5)
                        .padding(13)
                        .background(.pink)
                        .clipShape(Circle())
                }

            } //: HSTACK
            .padding(.bottom)
            .disabled(viewModel.displayingUsers?.isEmpty ?? false)
            .opacity(viewModel.displayingUsers?.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 = viewModel.displayingUsers?.first else {
            return
        }

        // Using Notification to post and receiveing in Stack Cards...
        NotificationCenter.default.post(name: NSNotification.Name("ACTIONFROMBUTTON"), object: nil, userInfo: [
            "id": first.id,
            "rightSwipe": rightSwipe
        ])
    }

}
//
//  StackCardView.swift
//  CardUI
//
//  Created by paige on 2021/12/09.
//

import SwiftUI

struct StackCardView: View {

    @EnvironmentObject private var viewModel: HomeViewModel
    let user: User

    // Gesture Properties...
    @State var offset: CGFloat = 0
    @GestureState var isDragging = false

    @State var endSwipe: Bool = false

    var body: some View {

        GeometryReader { proxy in
            let size = proxy.size
            let index = CGFloat(viewModel.getIndex(user: user))
            // Showing Next two cards at top like a Stack...
            let topOffset = (index <= 2 ? index : 2) * 15

            ZStack {

                Image(user.profilePic)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                // Reducing width too...
                    .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)))
        // about trim.. https://seons-dev.tistory.com/142
        .contentShape(Rectangle().trim(from: 0, to: endSwipe ? 0 : 1)) // 뒤에 있는 카드를 못 선택하게 막는다.
        //        .contentShape(Rectangle())
        .gesture(
            DragGesture()
                .updating($isDragging, body: { value, out, _ in
                    out = true
                })
                .onChanged({ value in
                    let translation = value.translation.width
                    offset = (isDragging ? translation : .zero)
                })
                .onEnded({ value in
                    let width = UIScreen.main.bounds.width - 50
                    let translation = value.translation.width
                    let checkingStatus = (translation > 0 ? translation : -translation)
                    withAnimation {
                        if checkingStatus > (width / 2) {
                            // remove card....
                            offset = (translation > 0 ? width : -width) * 2
                            endSwipeActions()

                            if translation > 0 {
                                rightSwipe()
                            } else {
                                leftSwipe()
                            }
                        } else {
                            // reset
                            offset = .zero
                        }
                    }
                })
        )
        .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ACTIONFROMBUTTON"))) { data in
            guard let info = data.userInfo else { return }
            let id = info["id"] as? String ?? ""
            let rightSwipe = info["rightSwipe"] as? Bool ?? false
            let width = UIScreen.main.bounds.width - 50

            if user.id == id {

                // removing card...
                withAnimation {
                    offset = (rightSwipe ? width : -width) * 2
                    endSwipeActions()

                    if rightSwipe {
                        self.rightSwipe()
                    } else {
                        leftSwipe()
                    }
                }
            }

        }

    }

    // Rotation
    private func getRotation(angle: Double) -> Double {
        let rotation = (offset / (UIScreen.main.bounds.width - 50)) * angle
        return rotation
    }

    private func endSwipeActions() {
        withAnimation(.none) {
            endSwipe = true
            // after the card is moved away removing the card from array to preserve the memory...

            // The delay time based on your animation duration...
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                if let _ = viewModel.displayingUsers?.first {
                    _ = withAnimation {
                        viewModel.displayingUsers?.removeFirst()
                    }
                }
            }
        }
    }

    private func leftSwipe() {
        // DO ACTIONS HERE
        print("Left Swiped")
    }

    private func rightSwipe() {
        // DO ACTIONS HERE
        print("Right Swiped")
    }

}
You might also like...
A fluent Collapsing header view like iOS Contacts detail and Weather App

FluentCollapsingHeaderView A Collapse Header View like iOS Weather App Credits FluentCollapsingHeaderView is owned and maintained by the Seyed Samad G

ProgrammingCalculator - a simple programmer's calculator with operators like AND, OR
ProgrammingCalculator - a simple programmer's calculator with operators like AND, OR

Programmer's Calculator This is a simple calculator program which implements operators commonly used in discrete logic such as AND, OR, Bit Shifting,

Fast Multi-store Redux-like architecture for iOS/OSX applications

Highway Highway is implementation of Redux-like architecture pattern using Swift. If you were looking for a something like this: TEA (The Elm Architec

Github repo search with using mvvm-c and clean architecture and using combine swift

GitSearchWithMVVM-C-CleanArchitecture Github repo search with using mvvm-c and clean architecture and using combine swift. Content Overview How To Run

An example to-do list app using SwiftUI which is introduced in WWDC19
An example to-do list app using SwiftUI which is introduced in WWDC19

SwiftUITodo SwiftUITodo is an example to-do list application using SwiftUI which is first introduced in WWDC19 keynote. Requirements Xcode 11 Beta Swi

SwiftUI & Combine app using MovieDB API. With a custom Flux (Redux) implementation.
SwiftUI & Combine app using MovieDB API. With a custom Flux (Redux) implementation.

MovieSwiftUI MovieSwiftUI is an application that uses the MovieDB API and is built with SwiftUI. It demos some SwiftUI (& Combine) concepts. The goal

content for Using Combine - notes on learning Combine with UIKit and SwiftUI
content for Using Combine - notes on learning Combine with UIKit and SwiftUI

SwiftUI-Notes A collection of notes, project pieces, playgrounds and ideas on learning and using SwiftUI and Combine. Changes, corrections, and feedba

An example APOD app with SwiftUI and Combine using NASA API
An example APOD app with SwiftUI and Combine using NASA API

SwiftUI-APOD An example Astronomy Picture of the Day(APOD) application using SwiftUI and Combine under iOS 13 Requirement Xcode 11 macOS 10.15 Catalin

Beers is a simple experimental app implemented using the new amazing SwiftUI.
Beers is a simple experimental app implemented using the new amazing SwiftUI.

Beers is a simple experimental app implemented using the new amazing SwiftUI. The app shows a list of beers fetched from Punk API

Owner
paigeshin
paigeshin
In this mini app covered the concepts like basics of SwiftUI and Navigations and Animations and List with CRUD functions and MVVM and App Launch and App icons adding and also applied persistence using UserDefaults Concept.

TodoList In this application used the concepts from the beginner level project of SwiftUI_Evolve_1 The following concepts covered in this mini app Swi

Sivaram Yadav 2 Dec 4, 2021
The ToDo-like app using swift

ToDo-List The ToDo-like app. The data model uses classes. The table view is created depending on the structure of the model classes. The user has the

NIKOLAY NIKITIN 2 Aug 8, 2022
StackUI just like SwiftUI

StackUI 中文文档 Use UIStackView like SwiftUI. Use @propertyWrapper, @resultBuilder, chain syntax and other features used by SwiftUI, making UIStackView e

暴走的鑫鑫 103 Jan 3, 2023
GroceryMartApp-iOS-practice - To Practice fundamental SwiftUI feature like navigation, state mamagement, customazing etc

?? GroceryMartApp-iOS-practice 아래의 내용은 스윗한 SwiftUI 책의 실전 앱 구현하기 을 바탕으로 정리한 내용입니다

Jacob Ko 0 Jan 7, 2022
A cross-platform SwiftUI-like framework built on SwiftGtk.

SwiftGtkUI A SwiftUI-like framework for creating cross-platform apps in Swift. It uses SwiftGtk as its backend. NOTE: SwiftGtkUI does not attempt to r

null 99 Jan 5, 2023
A simple SwiftUI Application to demonstrate creation of UI using SwiftUI.

WatchShop_UI A simple SwiftUI Application to demonstrate creation of UI using SwiftUI. How to run the project ? Fork the project. Run the project usin

Shubham Kr. Singh 12 Apr 15, 2022
Weather-swiftui - An example of using SwiftUI

weather-swiftui An example of using SwiftUI Installation Get openweather api key

null 0 Jan 1, 2022
SwiftUI-MSALSample - Sample project to login with MSAL using SwiftUI

SwiftUI-MSALSample I could not find a good walkthrough on how to implement MSAL

Rob Evans 10 Nov 7, 2022
SwiftUI-Card - Simple card ui designed using SwiftUI

SwiftUI - Card Simple card ui designed using SwiftUI Preview

bahri hırfanoğlu 0 Feb 5, 2022
Visualize your dividend growth. DivRise tracks dividend prices of your stocks, gives you in-depth information about dividend paying stocks like the next dividend date and allows you to log your monthly dividend income.

DivRise DivRise is an iOS app written in Pure SwiftUI that tracks dividend prices of your stocks, gives you in-depth information about dividend paying

Kevin Li 78 Oct 17, 2022