skip to Main Content

I am rendering multiple cards that are swipeable. I have a variable ‘percentage’ which increases a progress bar at the top of the screen. When a card is swiped to the right I want the bar to increase but the variable is out of scope. XCode doesn’t support global scoped states so I’m a bit confused as to what to do here. I have a very simplified version of the code below.

import Photos
import SwiftUI

struct ContentView: View {
    
    // WANT TO ACCESS THIS STATE
    @State var percent: CGFloat = 0

    var body: some View {
            ZStack{
                ForEach(imageObjectGroup){ card in
                    CardView(card: card).padding(8)
                }
            }.zIndex(1.0)
        }
    }
}

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

//Card Structure
struct CardView: View{
    @State var card: ImageObject
            DragGesture().onChanged { value in
                card.x = value.translation.width
                card.y = value.translation.height
                card.degree = 7 * (value.translation.width > 0 ? 1 : -1)
            }
            //When user stops dragging
            .onEnded { value in
                withAnimation(.interpolatingSpring(mass: 1.0, stiffness: 50, damping: 8, initialVelocity: 0)){
                        switch value.translation.width {
                            case 0...100:
                                card.x = 0; card.degree = 0; card.y = 0
                            // Keep
                            case let x where x > 100:
                                card.x = 800;
                            case (-100)...(-1):
                                card.x = 0; card.degree = 0; card.y = 0;
                            // Delete
                            case let x where x < -100:
                                card.x = -200; card.degree = -12
                                
                                //WANT TO INCREMENT HERE
                                percentage++
                            
                                default: card.x = 0; card.y = 0
                        }
                }
    }
}

2

Answers


  1. Because you’re going to have multiple cards, it doesn’t make sense to have a single @State var percent: CGFloat = 0 property inside ContentView that’s tied to all the cards. Instead, you want ImageObject to have a percent property, so that you can set each Card‘s percent independently.

    Then, use a @Binding inside CardView – this syncs all changes in percent to the original imageObjectGroup array.

    struct ImageObject {
        var x = CGFloat(0)
        var y = CGFloat(0)
        var degree = CGFloat(0)
        var percent = CGFloat(0) /// inside `ImageObject`, not `ContentView`
    }
    
    struct ContentView: View {
        @State var imageObjectGroup = [ImageObject(), ImageObject(), ImageObject()]
        
        var body: some View {
    
            /// loop over `indices` in order to access `Binding` version of each `ImageObject`
            ForEach(imageObjectGroup.indices, id: .self) { index in
                CardView(
                    card: $imageObjectGroup[index] /// pass in `Binding`
                )
            }
        }
    }
    
    struct CardView: View {
        @Binding var card: ImageObject 
        
        var body: some View {
            ...
                
            card.percentage += 1 /// increment here
            
            ...
        }
    }
    
    Login or Signup to reply.
  2. You can pass variable as a @Binding to your card, but in terms of architecture it’s not a good solution: card shouldn’t know about progress, it’s not here field of interest.

    Your card should notify parent that it were swiped. So I suggest you adding a block, which card will call, and then your container view you’ll increase the progress.

    Also it looks like you’re storing information about card position and rotation inside ImageObject. This is not very good too: you’re mixing model with view state. Most probably you don’t need this values ImageObject out of this view, so I suggest moving them directly to the view.

    struct ContentView: View {
        
        @State var percent: Double = 0
        
        @State var imageObjectGroup = (0...100).map {
            ImageObject(title: "Title ($0)")
        }
    
        var body: some View {
            VStack {
                ProgressView(value: percent, total: Double(imageObjectGroup.count))
                    .progressViewStyle(LinearProgressViewStyle())
                ZStack{
                    ForEach(imageObjectGroup){ card in
                        CardView(card: card, onAccept: {
                            percent += 1
                        }).padding(8)
                    }
                }.zIndex(1.0)
            }
        }
    }
    
    //Card Structure
    struct CardView: View{
        let card: ImageObject
        let onAccept: () -> Void
        
        @State private var x: CGFloat = 0
        @State private var y: CGFloat = 0
        @State private var degree: Double = 0
        
        var body: some View {
            Rectangle().foregroundColor(Color.red).frame(width: 100, height: 100)
                .overlay(Text(card.title))
                .offset(x: x, y: y)
                .rotationEffect(.degrees(degree))
                .gesture(
                    
                    DragGesture().onChanged { value in
                        x = value.translation.width
                        y = value.translation.height
                        degree = 7 * (value.translation.width > 0 ? 1 : -1)
                    }
                    //When user stops dragging
                    .onEnded { value in
                        withAnimation(.interpolatingSpring(mass: 1.0, stiffness: 50, damping: 8, initialVelocity: 0)){
                            switch value.translation.width {
                            case 0...100:
                                x = 0; degree = 0; y = 0
                            // Keep
                            case let x where x > 100:
                                self.x = 800;
                            case (-100)...(-1):
                                x = 0; degree = 0; y = 0;
                            // Delete
                            case let x where x < -100:
                                self.x = -200; degree = -12
                                
                                onAccept()
                                
                            default: x = 0; y = 0
                            }
                        }
                    }
                )
        }
    }
    
    struct ImageObject: Identifiable {
        let id = UUID()
        let title: String
    }
    

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search