Git Product home page Git Product logo

swiftui-cardstackview's Introduction

πŸƒ CardStack

Swift Version License Platform

A easy-to-use SwiftUI view for Tinder like cards on iOS, macOS & watchOS.

Alt text

Installation

Xcode 11 & Swift Package Manager

Use the package repository URL in Xcode or SPM package.swift file: https://github.com/dadalar/SwiftUI-CardStackView.git

CocoaPods

CardStack is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod "SwiftUICardStack"

Usage

The usage of this component is similar to SwiftUI's List. A basic implementation would be like this:

@State var cards: [Card] // This is the data to be shown in CardStack

CardStack(
  direction: LeftRight.direction, // See below for directions
  data: cards,
  onSwipe: { card, direction in // Closure to be called when a card is swiped.
    print("Swiped \(card) to \(direction)")
  },
  content: { card, direction, isOnTop in // View builder function
    CardView(card)
  }
)

Direction

CardStack needs to know which directions are available and how a swipe angle can be transformed into that direction. This is a conscious decision to make the component easily extendable while keeping type safety. The argument that needs to be passed to CardStack Initializer is a simple (Double) -> Direction? function. The Double input here is the angle in degrees where 0 points to up and 180 points to down. Direction is a generic type, that means users of this library can use their own types. Return nil from this function to indicate that that angle is not a valid direction (users won't be able to swipe to that direction).

There are the following predefined directions (LeftRight, FourDirections, EightDirections) and each of them define a direction(double:) function which can used in the CardStack Initializer. You can check the example project for a custom direction implementation.

Configuration

CardStack can be configured with SwiftUI's standard environment values. It can be directly set on the CardStack or an encapsulating view of it.

CardStack(
  // Initialize
)
.environment(\.cardStackConfiguration, CardStackConfiguration(
  maxVisibleCards: 3,
  swipeThreshold: 0.1,
  cardOffset: 40,
  cardScale: 0.2,
  animation: .linear
))

Use case: Appending items

It's really easy to load new data and append to the stack. Just make sure the data property is marked as @State and then you can append to the array. Please check the example project for a real case scenario.

struct AddingCards: View {
  @State var data: [Person] // Some initial data

  var body: some View {
    CardStack(
      direction: LeftRight.direction,
      data: data,
      onSwipe: { _, _ in },
      content: { person, _, _ in
        CardView(person: person)
      }
    )
    .navigationBarItems(trailing:
      Button(action: {
        self.data.append(contentsOf: [ /* some new data */ ])
      }) {
        Text("Append")
      }
    )
  }
}

Use case: Reload items

Since the component keeps an internal index of the current card, changing the order of the data or appending/removing items before the current item will break the component. If you want to replace the whole data, you need to force SwiftUI to reconstruct the component by changing the id of the component. Please check the example project for a real case scenario.

struct ReloadCards: View {
  @State var reloadToken = UUID()
  @State var data: [Person] = Person.mock.shuffled()

  var body: some View {
    CardStack(
      direction: LeftRight.direction,
      data: data,
      onSwipe: { _, _ in },
      content: { person, _, _ in
        CardView(person: person)
      }
    )
    .id(reloadToken)
    .navigationBarItems(trailing:
      Button(action: {
        self.reloadToken = UUID()
        self.data = Person.mock.shuffled()
      }) {
        Text("Reload")
      }
    )
  }
}

Author

Deniz Adalar, [email protected]

License

CardStack is available under the MIT license. See the LICENSE file for more info.

swiftui-cardstackview's People

Contributors

dadalar avatar nomadplanet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

swiftui-cardstackview's Issues

Matched geometry effect

Hi there, seems like card views don`t like matchedgeometryeffect modifier, because when I add it, all cards inside of stack loose their offset and swiping animation just breaks

Card stuck when swiping

When swiping in speed my card stucks in between. After some debugging, the issue I find is with the DragGesture(). The drag gesture's .onEnded function is not working when swiping in speed.

Animations and CardStack

Your library does a great job with cards that have images, and everything is super and perfect. But unfortunately I ran into a small problem. I use Lottie animations in my card. The problem is that if 50 cards arrive with Lottie animation, I get freezes in the application, especially on the latest cards. As soon as my view changes state (the CardStack disappears from view), my memory returns to normal (50 MB approximately). As soon as I do an unswipe, my memory goes back to 500MB of memory (maybe more). Maybe you can tell me, as the author of this library, what can be done in my case?

Cards going to the left

Hey my cards are sticking to the left for some reason. How can I fix this?
Example of issue

(Looking at the Restaurant Stuff, ignore the media references)

struct RestCardView: View {
    let restaurants: Restaurants
    @Environment(\.colorScheme) var colorScheme
    var body: some View {
        GeometryReader { geo in
            let card = (
                VStack {
                    if #available(iOS 15, *) {
                        AsyncImage(url: restaurants.imageUrl) { image in
                            imag
![Simulator Screen Shot - iPhone 13 - 2022-03-09 at 11 48 24](https://user-images.githubusercontent.com/81453549/157522210-0892cbd6-6cc7-4d9f-9889-9a3ffdfe2ffe.png)
e
                                .resizable()
                                .frame(idealWidth: geo.size.width, idealHeight: geo.size.height)//, maxHeight: 10)
                                .aspectRatio(contentMode: .fit)
                        } placeholder: {
                            Image(systemName: "photo")
                                .res
![Simulator Screen Shot - iPhone 13 - 2022-03-09 at 11 48 24](https://user-images.githubusercontent.com/81453549/157522269-6dee8139-720b-4384-9424-9105e3c5accb.png)
izable()
//                                .frame(height: geo.size.width)
                                .aspectRatio(contentMode: .fit)
                                .imageScale(.large)
                                .foregroundColor(.gray)
                        }
                        .ignoresSafeArea()
//                            switch image {
//                            case .empty:
//                                    Image(named: "No Image")
//                                    .resizable()
//                                    .aspectRatio(contentMode: .fit)
//                                    .frame(height: geo.size.width)
//                                    .clipped()
//                            case .success(let image):
//                                image
//                                    .resizable()
//                                    .aspectRatio(contentMode: .fit)
//                                    .frame(height: geo.size.width)
//                                    .clipped()
//                            case .failure:
//                                Image(named: "No Image")
//                                    .resizable()
//                                    .aspectRatio(contentMode: .fit)
//                                    .frame(height: geo.size.width)
//                                    .clipped()
//                            @unknown default:
//                                // Since the AsyncImagePhase enum isn't frozen,
//                                // we need to add this currently unused fallback
//                                // to handle any new cases that might be added
//                                // in the future:
//                                Image(named: "No Image")
//                                    .resizable()
//                                    .aspectRatio(contentMode: .fit)
//                                    .frame(height: geo.size.width)
//                                    .clipped()
//                            }
//                        }
                    }else {
                        URLImage(restaurants.imageUrl) { image in
                            image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(idealHeight: geo.size.width)//, maxHeight: 50)
                            .clipped()
                        }
                    }
                    HStack{
                        Text(restaurants.name)
                        if restaurants.price != "Unknown" {
                            Text(restaurants.price)
                        }
                    }
                        .padding(.bottom)
                    if restaurants.category != [] {
                        Text("\(restaurants.category.joined(separator: ", "))")
                            .padding(.horizontal)
                    }
//                    if !restaurants.isClosed {
//                        Text("Open")
//                            .foregroundColor(Color.green)
//                    }else {
//                        Text("Closed")
//                            .foregroundColor(Color.red)
//                    }
                        Button(action: {
                            print("opening site pressed")
                            if let url = URL(string: restaurants.url) {
                                UIApplication.shared.open(url)
                            }
                        }) {
                            HStack {
                                RestaurantReviews(reviews: restaurants.rating, forgroundColor: .yelpDefault, yelpIconSelectedSize: .large)
                            Text("\(restaurants.reviewCount)")
                                .foregroundColor(.primary)
                                .padding(.trailing)
                            Text("More Info")
                            Image("yelp logo")
                                .resizable()
                                .aspectRatio(contentMode: .fit)
                                .frame(maxWidth: 30)
                            }
                    }
                        .padding(.horizontal)
                    Button(action: {
                        print("opening location pressed")
                        Utilities.encodeForGoogleMaps(url: restaurants.name) { loc in
                            print("loc = \(loc)")
                            if let url = URL(string: "https://www.google.com/maps/search/?api=1&query=\(loc)") {
                                UIApplication.shared.open(url)
                            }
                        }
                    }, label: {
                        Text(restaurants.location)
                    })
                    .padding(.bottom)
                })
            if colorScheme == .dark {
                card
//                .opacity(0.2)
                .background(Color(red: 0.15, green: 0.15, blue: 0.15))
                .cornerRadius(12)
                .shadow(radius: 4)
            }else {//if colorScheme == .light {
                card
                .background(Color.white)
                .cornerRadius(12)
                .shadow(radius: 4)
            }
        }
    }
}
struct MediaCardView: View {
    let media: Media
    @Environment(\.colorScheme) var colorScheme
    var body: some View {
        GeometryReader { geo in
            let card = (
                VStack {
                    if #available(iOS 15, *) {
                        AsyncImage(url: media.imageUrl) { image in
                            image
                                .resizable()
                                .frame(idealWidth: geo.size.width, idealHeight: geo.size.height)//, maxHeight: 10)
                                .aspectRatio(contentMode: .fit)
                        } placeholder: {
                            Image(systemName: "photo")
                                .resizable()
//                                .frame(height: geo.size.width)
                                .aspectRatio(contentMode: .fit)
                                .imageScale(.large)
                                .foregroundColor(.gray)
                        }
                        .ignoresSafeArea()
//                            switch image {
//                            case .empty:
//                                    Image(named: "No Image")
//                                    .resizable()
//                                    .aspectRatio(contentMode: .fit)
//                                    .frame(height: geo.size.width)
//                                    .clipped()
//                            case .success(let image):
//                                image
//                                    .resizable()
//                                    .aspectRatio(contentMode: .fit)
//                                    .frame(height: geo.size.width)
//                                    .clipped()
//                            case .failure:
//                                Image(named: "No Image")
//                                    .resizable()
//                                    .aspectRatio(contentMode: .fit)
//                                    .frame(height: geo.size.width)
//                                    .clipped()
//                            @unknown default:
//                                // Since the AsyncImagePhase enum isn't frozen,
//                                // we need to add this currently unused fallback
//                                // to handle any new cases that might be added
//                                // in the future:
//                                Image(named: "No Image")
//                                    .resizable()
//                                    .aspectRatio(contentMode: .fit)
//                                    .frame(height: geo.size.width)
//                                    .clipped()
//                            }
//                        }
                    }else {
                        URLImage(media.imageUrl) { image in
                            image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(idealHeight: geo.size.width)//, maxHeight: 50)
                            .clipped()
                        }
                    }
                    HStack{
                        Text(media.name)
                    }
//                        .padding(.bottom)
                    let rating = "Avg. Rating: \(media.rating.rounded())/10 from \(media.ratingCount) reviews"
                    Text(rating)
                        .padding(.bottom)
                Text(media.description)
                        .padding(.horizontal)
                }
                    .padding(.bottom))
                .onAppear {
                    print("Avg. Rating: \(media.rating)/10 from \(media.ratingCount) reviews")
                }
            if colorScheme == .dark {
                card
//                .opacity(0.2)
                .background(Color(red: 0.15, green: 0.15, blue: 0.15))
                .cornerRadius(12)
                .shadow(radius: 4)
            }else {//if colorScheme == .light {
                card
                .background(Color.white)
                .cornerRadius(12)
                .shadow(radius: 4)
            }
        }
    }
}
struct CardViewWithThumbs: View {
    let restaurants: Restaurants
    let media: Media
    let direction: LeftRight?
    static var yes = Array<Any>()
    
    var body: some View {
        ZStack(alignment: .topTrailing) {
            ZStack(alignment: .topLeading) {
                if Utilities.picktType == .media {
                    MediaCardView(media: media)
                        .accessibilityIdentifier("Card")
                }else {
                    RestCardView(restaurants: restaurants)
                        .accessibilityIdentifier("Card")
                }
                Image(systemName: "hand.thumbsup.fill")
                    .resizable()
                    .foregroundColor(Color.green)
                    .opacity(direction == .right ? 1 : 0)
                    .frame(width: 100, height: 100)
                    .padding()
            }
            Image(systemName: "hand.thumbsdown.fill")
                .resizable()
                .foregroundColor(Color.red)
                .opacity(direction == .left ? 1 : 0)
                .frame(width: 100, height: 100)
                .padding()
        }
        .animation(.default)
    }
}

struct Thumbs: View {
...
    func showFinalRestView() {
        db.collection("parties").document(Utilities.code).addSnapshotListener { doc, error in
            print("Start data fetch...")
            if error == nil {
                print("error = nil")
                if doc != nil && doc!.exists {
                    print("Doc Exists")
                    if let num = doc!.get("devicesNum") as? Int {
                        devicesNum = num
                        if let dev = doc!.get("devices") as? Array<String> {
                            devices = dev
                            print("devices = \(dev)")
                            for name in dev {
                                print("DeviceList = \(Devices.list)")
                                if !Devices.list.contains(Devices(id: name, name: name)) {
                                    print("DeviceList Contains = \(name)")
                                    Devices.list.append(Devices(id: name, name: name))
                                }
                            }
                            if let done = doc!.get("devicesDone") as? Array<String> {
                                print("devicesDone = \(done), devices = \(devices)")
                                devicesDone = done
                                if done.sorted() == dev.sorted() && done.count == num {
                                    if var topController = UIApplication.shared.windows.first!.rootViewController {
                                        while let presentedViewController = topController.presentedViewController {
                                            topController = presentedViewController
                                        }
                                        self.fullScreenAd.showAd(root: topController, then: {
                                            //                                print("fetch data")
                                            //                                viewModels.fetchData {
                                            //                                        viewModels.fetchData { //yesRestaurant, AllRestaurants, yesMedia, allMedia, devices, devicesDone in
                                            //                                            vmRestAll = AllRestaurants
                                            //                                            vmRestYes = yesRestaurant
                                            //                                            vmDevices = devices
                                            //                                            vmMediaAll = allMedia
                                            //                                            vmMediaYes = yesMedia
                                            print("show final view")
                                            //                                    fullScreenAd.showAd {
                                            Utilities.showFinal = true
                                            DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
                                                self.presentation.wrappedValue.dismiss()
                                                results = false
                                                topPick = false
                                            }
                                            print("yesRestaurants from restPicker = \(viewModels.yesRestaurants), allRestaurants = \(viewModels.allRestaurants)")
                                            print("done from completion handler")
                                            //                                    }
                                            //                                        }
                                        })
                                    }
                                }
                            }else {
                                print("error: \(error)")
                            }
                        }else {
                            print("error: \(error)")
                        }
                    }else {
                        print("error: \(error)")
                    }
                }
            }
        }
    }
    func showfinalview() {
            print("show final view, yes.restlist = \(yes.restList), yes.medialist = \(yes.mediaList)")
            results = true
            topPick = true
    }
    func addRestData(card: Restaurants, direction: LeftRight) {
        if NetworkMonitor.shared.isConnected {
            if direction == .right {
                itemCount += 1
                print("Code = \(Utilities.code)")
                print("Name = \(Utilities.name)")
                    yes.restList.append(Restaurants(name: card.name, imageUrl: card.imageUrl, id: card.id, rating: card.rating, url: card.url, location: card.location, category: card.category, tag: [], isClosed: card.isClosed, price: card.price, reviewCount: card.reviewCount))
                print("yes, list = \(yes.restList)")
            }else {
                print("no")
                itemNo += 1
                no.restList.append(Restaurants(name: card.name, imageUrl: card.imageUrl, id: card.id, rating: card.rating, url: card.url, location: card.location, category: card.category, tag: [], isClosed: card.isClosed, price: card.price, reviewCount: card.reviewCount))
            }
            if card.name == restData.last?.name {
                showfinalview()
//                            showFinal = true
            }
        }else {
            disabledAlert = true
        }
    }
...
    var body: some View {
        GeometryReader { geo in
//            NavigationView {
            VStack(alignment: .center) {
                    Text(" ")
                        .padding(.bottom)
                    HStack {
                        Text("Party Code: \(Utilities.code)")
                            .padding(.trailing)
                        if Utilities.joinDisabled == false {
                            Button(action: {
                                copy = true
                                switch Utilities.shareMode {
                                case .sheet:
//                                    shareSheet.toggle()
                                    Utilities.presentShareSheet(activityItems: ["pickt://join/\(Utilities.code)"])
//                                    let activityVC = UIActivityViewController(activityItems: ["pickt://join/\(Utilities.code)"], applicationActivities: nil)
//                                    UIApplication.shared.windows.first?.rootViewController?.present(activityVC, animated: true, completion: nil)
                                case .clipboard:
                                    let pasteboard = UIPasteboard.general
                                    pasteboard.string = "pickt://join/\(Utilities.code)"
                                }
                                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                                    copy = false
                                }
                            }, label: {
                                if !copy {
                                    Group {
                                        switch Utilities.shareMode {
                                        case .sheet:
                                            Image(systemName: "square.and.arrow.up")
                                        case .clipboard:
                                            Image(systemName: "doc.on.clipboard")
                                        }
                                    }
                                }else {
                                    Image(systemName: "checkmark")
                                }
                            })
                                .foregroundColor(.primary)
                                .frame(width: 35, height: 35)
                                .overlay(
                                    RoundedRectangle(cornerRadius: 10)
                                    .stroke(Color.primary, lineWidth: 1)
                                )
                        }
                    }
                    .padding(.top)
    //                Button("Stop Searching") {
    //                    if yes.list.count > 0 {
    //                       showfinalview()
    //                    }
    //                }
                if restData == [] && Utilities.picktType == .restaurants {
                        Text("No Results Found For Query Terms:\n\nLocation: \(Utilities.location),\nRestaurant Filter: \(Utilities.restFilter),\nCuisine: \(Utilities.cuisine)")
                            .multilineTextAlignment(.center)
                            .padding(.top)
                    }else {
                        Group {
                            if Utilities.picktType == .media {
                                if Utilities.mediaType == .TVShows {
                                    Text("Searching for TV Shows")
//                                    Text("Everyone is seeing the same tv cards, please swipe right for yes and left for no.")
                                }else if Utilities.mediaType == .Movies {
                                    Text("Searching for Movies")
//                                    Text("Everyone is seeing the same movie cards, please swipe right for yes and left for no.")
                                }
                            }else {
                                Text("Searching \(Utilities.location) with \(Utilities.restFilter), \(Utilities.cuisine)")
//                                    .padding(.horizontal)
//                                Text("Everyone is seeing the same restaurant cards, please swipe right for yes and left for no.")
                            }
                        }
                        .padding()
                        .accessibility(identifier: "Searching")
                        .onTapGesture {
                            if UserDefaults.standard.bool(forKey: "FASTLANE_SNAPSHOT") {
                                for item in Restaurants.viewModels {
                                    yes.restList.append(item)
                                }
                                self.showfinalview()
                            }
                        }
                        HStack {
                            Button(action: {
                                var mediaList = Array<Media>()
                                var restList = Array<Restaurants>()
                                for item in no.mediaList {
                                    if !mediaList.contains(item) {
                                        mediaList.append(item)
                                    }
                                }
                                for item in no.restList {
                                    if !restList.contains(item) {
                                        restList.append(item)
                                    }
                                }
                                no.mediaList = mediaList
                                no.restList = restList
                                noList = true
                            }, label: {
                                Image(systemName: "hand.thumbsdown")
                                Text("\(itemNo)")
                            })
                                .accessibility(identifier: "No List")
                                .foregroundColor(.red)
                            Spacer()
                                .frame(width: 40)
                            Button(action: {
                                var mediaList = Array<Media>()
                                var restList = Array<Restaurants>()
                                for item in yes.mediaList {
                                    if !mediaList.contains(item) {
                                        mediaList.append(item)
                                    }
                                }
                                for item in yes.restList {
                                    if !restList.contains(item) {
                                        restList.append(item)
                                    }
                                }
                                yes.mediaList = mediaList
                                yes.restList = restList
                                yesList = true
                            }, label: {
                                Image(systemName: "hand.thumbsup")
                                Text("\(itemCount)")
                            })
                                .accessibility(identifier: "Yes List")
                            .foregroundColor(.green)
                        }
                        .padding()
                    }
                    if results {
                        Group {
                            Text("\(waitingPhrases.randomElement()!)")
    //                        VStack {
    //                            Text("People Who Have Finished:\n")
    //                            ForEach(devicesDone.indices) {
    //                                Text(self.devicesDone[$0])
    //                            }
    //                        }
    //                        .padding()
                            Button("Override, Show Results") {
                                overrideResults = true
                            }
                            Button {
                                topPick = true
                            } label: {
                                HStack {
                                    Image(systemName: "filemenu.and.selection")
                                    if pick != "None" {
                                        Text("Change your top pick")
                                    }else {
                                        Text("Choose your top pick")
                                    }
                                }
                            }

                        }
                        .padding()
                    }
                if !results && restData != [] && restData != [Restaurants(name: "nil", imageUrl: URL(string: Utilities.noImage)!, id: "abcdef", rating: 0, url: "https://google.com", location: "nil", category: ["nil"], tag: [], isClosed: false, price: "Unknown", reviewCount: 0)] && Utilities.picktType == .restaurants {
                            ZStack(alignment: Alignment(horizontal: .center, vertical: .center), content: {
                                CardStack(
                                    direction: LeftRight.direction,
                                    data: restData,
                                    onSwipe: { card, direction in
                                        print("Swiped \(card.name) to \(direction)")
                                        addRestData(card: card, direction: direction)
                                    },
                                    content: { restaurants, direction, _ in
                                        CardViewWithThumbs(restaurants: restaurants, media: Media(name: "nil", imageUrl: URL(string: Utilities.noImage)!, id: "abcde", description: "abcde", ratingCount: 1, rating: 8, tag: []), direction: direction)
                                    }
                                )
                                    .padding()
                                    .scaledToFit()
                                    .frame(alignment: .center)
//                                .frame(maxWidth: screen.width)
                                .environment(\.cardStackConfiguration, CardStackConfiguration(
                                  maxVisibleCards: 3
        //                          swipeThreshold: 0.1,
        //                            cardOffset: 10,
        //                            cardScale: 0.2,
        //                            animation: .linear
                                ))
                            })
                }else if Utilities.picktType == .media && mediaData != [] {
                    ZStack(alignment: Alignment(horizontal: .center, vertical: .center), content: {
                        CardStack(
                            direction: LeftRight.direction,
                            data: mediaData,
                            onSwipe: { card, direction in
                                print("Swiped \(card.name) to \(direction)")
                                addMediaData(card: card, direction: direction)
                                if card.name == mediaData.last?.name {
                                    showfinalview()
        //                            showFinal = true
                                }
                            },
                            content: { media, direction, _ in
                                CardViewWithThumbs(restaurants: Restaurants(name: "nil", imageUrl: URL(string: Utilities.noImage)!, id: "abcdef", rating: 0, url: "https://google.com", location: "nil", category: ["nil"], tag: [], isClosed: false, price: "Unknown", reviewCount: 0), media: media, direction: direction)
                            }
                        )
                        .padding(.all)
//                                .frame(height: screen.height)
//                                .frame(minHeight: 0, idealHeight: screen.height, maxHeight: 450, alignment: .center)
                        .scaledToFit()
                        .frame(maxWidth: 700, maxHeight: 800)
//                                .frame(maxWidth: screen.width)
                        .environment(\.cardStackConfiguration, CardStackConfiguration(
                          maxVisibleCards: 3
//                          swipeThreshold: 0.1,
//                            cardOffset: 10,
//                            cardScale: 0.2,
//                            animation: .linear
                        ))
                    })
                }
            }
            ...
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
    }
}

Thanks in advanced!!!

Swipe left and right on button click

Hi,
Thanks for you help,
How can I Swipe left and right on button click
as when I click Like and Dislike button card should auto Swipe

Your response will be helpful to me

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.