I am creating a Cardview stored with user data for each user in the usersList. Now I want to offset each card for itself by pressing on either button. If I press the button now I will offset every card created by the foreach. How can I make the button only affect the first card? It is probably a better Idea to offset the card in the CardView itself, like I did with the Draggesture and not in the ContentView
struct ContentView: View {
@State var usersList: [User] = []
@State var fetchingComplete: Bool = false
@State var offset = CGSize.zero
@State var buttonPushed: Bool = false
var body: some View {
VStack {
NavBar().onAppear() {
fetchUsers()
}
if fetchingComplete {
ZStack {
ForEach(usersList, id: \.id) { user in
CardView(user: user).offset(x: offset.width, y: offset.height * 0.4)
.rotationEffect(.degrees(Double(offset.width / 40)))
.animation(.spring(), value: buttonPushed)
}
}
}
ButtonsBar(offset: $offset, buttonPushed: $buttonPushed)
}
}
}
public func fetchUsers() -> [User]{
FirebaseManager.shared.firestore.collection("users").getDocuments { snapshot, error in
if let error = error {
print("Failed to fetch users: ", error)
return
}
for document in snapshot!.documents {
let user = User(dictionary: document.data())
usersList.append(user)
}
fetchingComplete.toggle()
}
return usersList
}
}
struct ButtonsBar:View {
@Binding var offset: CGSize
@Binding var buttonPushed: Bool
var body: some View {
HStack {
Button {
print("perform dislike")
offset = CGSize(width: -500, height: 0)
buttonPushed.toggle()
} label: {
Image("dismiss_circle")
}.frame(width: 65)
Button {
print("perform like")
offset = CGSize(width: 500, height: 0)
buttonPushed.toggle()
} label: {
Image("like_circle")
}.frame(width: 65)
}
}
}
struct CardView: View {
@State private var offset = CGSize.zero
var user: User
var body: some View {
VStack{
Image("lady")
.resizable()
}.cornerRadius(12)
.frame(width: 310, height: 392)
.scaledToFit()
.overlay(ImageOverlay(name: self.user.name ?? "", age: self.user.age ?? 0, profession: self.user.profession ?? ""), alignment: .bottomLeading)
.offset(x: offset.width, y: offset.height * 0.4)
.rotationEffect(.degrees(Double(offset.width / 40)))
.gesture(
DragGesture()
.onChanged { gesture in
offset = gesture.translation
} .onEnded { _ in
withAnimation {
swipeCard(width: offset.width)
}
}
)
}
func swipeCard(width: CGFloat) {
switch width {
case -500...(-150):
offset = CGSize(width: -500, height: 0)
case 150...500:
offset = CGSize(width: 500, height: 0)
default:
offset = .zero
}
}
}
CodePudding user response:
Right now, your current design will be unable to achieve what you're saying. How you have designed offset makes it so there's only the single source of truth being a single var offset
. Thus, there's no way to give an offset to each card uniquely. Likewise, your design of a single ButtonBar will also be unable to achieve the goal of modifying each unique entry. How would this ButtonBar as it is choose any particular CardView? I know how to implement this, but I think you can figure it out once I explain more on how to uniquely associate values to each CardView.
I know you asked for how to just indent the first card only, but I don't think the easiest answer will be your desired outcome.
What you should do is that you have to track an offset
and/or a buttonPressed
for EACH user in your UserList. The easiest way is to create a CardViewModel so at the top of your CardView add
@StateObject var cardVM = CardViewModel()
and for the CardViewModel,
class CardViewModel : ObservableObject {
@Published var offset = CGSize.zero
@Published var buttonPushed: Bool = false
}
Now we have a unique offset and buttonPushed for each CardView. From here I would remove the modifiers
.offset(x: offset.width, y: offset.height * 0.4)
.rotationEffect(.degrees(Double(offset.width / 40)))
.animation(.spring(), value: buttonPushed)
and inside the CardView place your ButtonBar like so
struct CardView: View {
@StateObject var cardVM = CardViewModel()
var user: User
var body: some View {
VStack{
ButtonBar(offset: $cardVM.offset, buttonPushed: $cardVM.buttomPushed)
Image("lady")
.resizable()
}
.cornerRadius(12)
.frame(width: 310, height: 392)
.scaledToFit()
.overlay(ImageOverlay(name: self.user.name ?? "", age: self.user.age ?? 0, profession: self.user.profession ?? ""), alignment: .bottomLeading)
.offset(x: offset.width, y: offset.height * 0.4)
.rotationEffect(.degrees(Double(offset.width / 40)))
.gesture(
DragGesture()
.onChanged { gesture in
offset = gesture.translation
}
.onEnded { _ in
withAnimation {
swipeCard(width: offset.width)
}
}
)
// the offset and effects based on button push
.offset(x: cardVM.offset.width, y: cardVM.offset.height * 0.4)
.rotationEffect(.degrees(Double(cardVM.offset.width / 40)))
.animation(.spring(), value: cardVM.buttonPushed)
}
func swipeCard(width: CGFloat) {
switch width {
case -500...(-150):
offset = CGSize(width: -500, height: 0)
case 150...500:
offset = CGSize(width: 500, height: 0)
default:
offset = .zero
}
}
}
Because this isn't an MRE, I'm not able to actually test this, but in general, this should follow the design of creating a CardViewModel for each CardView which contains unique to each view variables offset
and buttonPressed
and placing the ButtonBar in each CardView allows us to modify the CardViewModel uniquely too.