In this tutorial When one is removed, another is added to the bottom of the stack.
ContentView.swift
//
// ContentView.swift
// swiftuidev
//
// Created by Cairocoders
//
import SwiftUI
struct User: Hashable, CustomStringConvertible {
var id: Int
let firstName: String
let lastName: String
let age: Int
let mutualFriends: Int
let imageName: String
let occupation: String
var description: String {
return "\(firstName), id: \(id)"
}
}
struct ContentView: View {
/// List of users
@State var users: [User] = [
User(id: 0, firstName: "Airi", lastName: "Satou", age: 33, mutualFriends: 43, imageName: "photo1", occupation: "Accountant"),
User(id: 1, firstName: "Angeleca", lastName: "Ramos", age: 47, mutualFriends: 12, imageName: "photo2", occupation: "Junior Techinical Author"),
User(id: 2, firstName: "Aston", lastName: "Cox", age: 20, mutualFriends: 10, imageName: "photo3", occupation: "Scientist"),
User(id: 3, firstName: "Bradley", lastName: "Greer", age: 45, mutualFriends: 46, imageName: "photo4", occupation: "Sales Assistant"),
User(id: 4, firstName: "Bruno", lastName: "Nash", age: 23, mutualFriends:48, imageName: "photo5", occupation: "Sales Assistant"),
User(id: 5, firstName: "Cara", lastName: "Stevens", age: 24, mutualFriends: 37, imageName: "photo6", occupation: "Marketing Manager")
]
/// Return the CardViews width for the given offset in the array
/// - parameters: - geometry: The geometry proxy of the parent, - id: The ID of the current user
private func getCardWidth(_ geometry: GeometryProxy, id: Int) -> CGFloat {
let offset: CGFloat = CGFloat(users.count - 1 - id) * 10
return geometry.size.width - offset
}
/// Return the CardViews frame offset for the given offset in the array
/// - Parameters: - geometry: The geometry proxy of the parent, - id: The ID of the current user
private func getCardOffset(_ geometry: GeometryProxy, id: Int) -> CGFloat {
return CGFloat(users.count - 1 - id) * 10
}
private var maxID: Int {
return self.users.map { $0.id }.max() ?? 0
}
var body: some View {
VStack {
GeometryReader { geometry in
LinearGradient(gradient: Gradient(colors: [Color.init(#colorLiteral(red: 0.8509803922, green: 0.6549019608, blue: 0.7803921569, alpha: 1)), Color.init(#colorLiteral(red: 1, green: 0.9882352941, blue: 0.862745098, alpha: 1))]), startPoint: .bottom, endPoint: .top)
.frame(width: geometry.size.width * 1.5, height: geometry.size.height)
.background(Color.blue)
.clipShape(Circle())
.offset(x: -geometry.size.width / 4, y: -geometry.size.height / 2)
VStack(spacing: 24) {
DateView()
ZStack {
ForEach(self.users, id: \.self) { user in
// Range Operator
if (self.maxID - 3)...self.maxID ~= user.id {
CardView(user: user, onRemove: { removedUser in
// Remove that user from our array
self.users.removeAll { $0.id == removedUser.id }
})
.frame(width: self.getCardWidth(geometry, id: user.id), height: 400)
.offset(x: 0, y: self.getCardOffset(geometry, id: user.id))
}
}
}
Spacer()
}
}
}.padding()
}
}
struct DateView: View {
var body: some View {
VStack {
HStack {
VStack(alignment: .leading) {
Text(Date(), style: .date)
.font(.title)
.bold()
Text("Today")
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
}.padding()
}
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
CardView.swift
//
// CardView.swift
// swiftuidev
//
// Created by Cairocoders
//
import SwiftUI
struct CardView: View {
@State private var translation: CGSize = .zero
@State private var swipeStatus: LikeDislike = .none
private var user: User
private var onRemove: (_ user: User) -> Void
private var thresholdPercentage: CGFloat = 0.5 // when the user has draged 50% the width of the screen in either direction
private enum LikeDislike: Int {
case like, dislike, none
}
init(user: User, onRemove: @escaping (_ user: User) -> Void) {
self.user = user
self.onRemove = onRemove
}
/// What percentage of our own width have we swipped - Parameters: - geometry: The geometry - gesture: The current gesture translation value
private func getGesturePercentage(_ geometry: GeometryProxy, from gesture: DragGesture.Value) -> CGFloat {
gesture.translation.width / geometry.size.width
}
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
ZStack(alignment: self.swipeStatus == .like ? .topLeading : .topTrailing) {
Image(self.user.imageName)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geometry.size.width, height: geometry.size.height * 0.75)
.clipped()
if self.swipeStatus == .like {
Text("LIKE")
.font(.headline)
.padding()
.cornerRadius(10)
.foregroundColor(Color.green)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.green, lineWidth: 3.0)
).padding(24)
.rotationEffect(Angle.degrees(-45))
} else if self.swipeStatus == .dislike {
Text("DISLIKE")
.font(.headline)
.padding()
.cornerRadius(10)
.foregroundColor(Color.red)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.red, lineWidth: 3.0)
).padding(.top, 45)
.rotationEffect(Angle.degrees(45))
}
}
HStack {
VStack(alignment: .leading, spacing: 6) {
Text("\(self.user.firstName) \(self.user.lastName), \(self.user.age)")
.font(.title)
.bold()
Text(self.user.occupation)
.font(.subheadline)
.bold()
Text("\(self.user.mutualFriends) Mutual Friends")
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
Image(systemName: "info.circle")
.foregroundColor(.gray)
}
.padding(.horizontal)
}
.padding(.bottom)
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
.animation(.interactiveSpring())
.offset(x: self.translation.width, y: 0)
.rotationEffect(.degrees(Double(self.translation.width / geometry.size.width) * 25), anchor: .bottom)
.gesture(
DragGesture()
.onChanged { value in
self.translation = value.translation
if (self.getGesturePercentage(geometry, from: value)) >= self.thresholdPercentage {
self.swipeStatus = .like
} else if self.getGesturePercentage(geometry, from: value) <= -self.thresholdPercentage {
self.swipeStatus = .dislike
} else {
self.swipeStatus = .none
}
}.onEnded { value in
// determine snap distance > 0.5 aka half the width of the screen
if abs(self.getGesturePercentage(geometry, from: value)) > self.thresholdPercentage {
self.onRemove(self.user)
} else {
self.translation = .zero
}
}
)
}
}
}
struct CardView_Previews: PreviewProvider {
static var previews: some View {
CardView(user: User(id: 1, firstName: "Cairocoders", lastName: "Tutorial101", age: 27, mutualFriends: 0, imageName: "photo1", occupation: "Coder"),
onRemove: { _ in
// do nothing
})
.frame(height: 400)
.padding()
}
}
