gadirom / TransformGesture

Handle two finger transformation gesture in SwiftUI and Metal views
30 stars 1 forks source link

Views inside ForEach not transforming #4

Closed gdetari closed 1 year ago

gdetari commented 1 year ago

Thank you so much for making this package. It works well, for most cases, however I found that if the view is inside a ForEach, it does not. Any ideas for a workaround / fix?

ForEach(viewDescriptors) { desc in
    customView(desc)
       .transformEffect(transform)
       .transformGesture(transform: transform, draggingDisabled: false, active: true) { _ in
       }
} 

This is also observed when there is a single element in viewDescriptors.

gadirom commented 1 year ago

Hi! I'm glad you fund it useful! I guess your use case is not perfect for the package. E.g. this code works as expected:

struct ContentView: View {
    @StateObject var transform = TouchTransform()
    @State var array = [1,2,3,4,5]
    var body: some View {
        VStack {
            ForEach(array, id:\.self){ element in
                Text("\(element)")
                    .transformEffect(transform)
                    .transformGesture(transform: transform, draggingDisabled: true)
            }
        }
    }
}

But what happens here is you have a lot of views with separately added transformGesture, in which case the screen is being "cut" into separate areas, so that if two fingers are too far apart they will not be registered as a transform gesture and your gesture will be ignored. (By the way, I was not getting any issues with just one element, so, please, post some more code that will demonstrate this problem)

If you change the scope of the gesture like this:

VStack {
            ForEach(array, id:\.self){ element in
                Text("\(element)")
                    .transformEffect(transform)
            }
        }
        .transformGesture(transform: transform, draggingDisabled: false)

You will be getting correct gestures but the transforms will mess up because now you need to recalculate all the rotational offsets for each view. And this will not be a pleasant thing to do :)

I guess you should probably reconsider your scenario. What exact behaviour you wanted to achieve with ForEach and a single TouchTransform object?

If you wanted to transform each view separately you may try this approach (a little bit ugly but should work in some cases):

@MainActor
struct ContentView: View {

    let transforms = [TouchTransform(), TouchTransform(), TouchTransform()]

    @State var selected = 1

    var body: some View {
        VStack{
            ZStack {
                ForEach(0..<transforms.count){ element in
                    Text("\(element)")
                        .transformEffect( transforms[element])
                }
                ForEach(0..<transforms.count){ element in
                    Rectangle()
                        .fill(Color.clear)
                        .transformGesture(transform: transforms[element], draggingDisabled: false)
                        .disabled(element != selected)
                }
            }
            Picker(selection: $selected, label: Text("select the view to transform")){
                ForEach(0..<transforms.count){ element in
                    Text("\(element)").tag(element)
                }
            }
        }
    }
}
gdetari commented 1 year ago

Thank you for your detailed response. I used something similar to your proposed solution, and it works. 👍