Search code examples
iosswiftswiftuigestureswiftui-ontapgesture

Tap anywhere to add multiple items on canvas


import SwiftUI

struct Level1: View {
    @State var tapScore = 0
    @State var showingMinedHammer = false
    @State var points:[CGPoint] = [CGPoint(x:0,y:0), CGPoint(x:50,y:50)]
    @State private var location = CGPoint.zero      // < here !!
    
    
    func mine() {
        tapScore += 1
        showMinedHammer()
    }
    
    func showMinedHammer() {
        self.showingMinedHammer = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 99) {
            self.showingMinedHammer = false
        }
    }
    
    var body: some View {
        GeometryReader { geometryProxy in
            ZStack {
                Image("hammer.fill").resizable().frame(width: UIScreen.main.bounds.height * 1.4, height: UIScreen.main.bounds.height)
                    .gesture(DragGesture(minimumDistance: 0).onEnded { value in
                        self.location = value.location // < here !!
                        self.mine()
                    })
                if self.showingMinedHammer {
                    Image(systemName: "hammer.fill")
                        .resizable()
                        .frame(width: 30, height: 30)
                        .position(self.location)    // < here !!
                }
            }
        }.edgesIgnoringSafeArea(.all)
    }
    
}

struct Level1_Previews: PreviewProvider {
    static var previews: some View {
        Level1()
    }
}

struct GetTapLocation:UIViewRepresentable {
    var tappedCallback: ((CGPoint) -> Void)
    
    func makeUIView(context: UIViewRepresentableContext<GetTapLocation>) -> UIView {
        let v = UIView(frame: .zero)
        let gesture = UITapGestureRecognizer(target: context.coordinator,
                                             action: #selector(Coordinator.tapped))
        v.addGestureRecognizer(gesture)
        return v
    }
    
    class Coordinator: NSObject {
        var tappedCallback: ((CGPoint) -> Void)
        init(tappedCallback: @escaping ((CGPoint) -> Void)) {
            self.tappedCallback = tappedCallback
        }
        @objc func tapped(gesture:UITapGestureRecognizer) {
            let point = gesture.location(in: gesture.view)
            self.tappedCallback(point)
        }
    }
    
    func makeCoordinator() -> GetTapLocation.Coordinator {
        return Coordinator(tappedCallback:self.tappedCallback)
    }
    
    func updateUIView(_ uiView: UIView,
                      context: UIViewRepresentableContext<GetTapLocation>) {
    }
    
}

New to SwiftUI and I am trying to combine gestures that allow me to tap anywhere on the screen to add an infinite amount of "Images", but currently the image only stays on screen for a short while. Where am I going wrong? Am I supposed to combine another gesture to get the item to stay on screen whilst adding?


Solution

  • You only have a singular location, so only one item will ever appear in your example. To have multiple items, you'll need some sort of collection, like an Array.

    The following is a pared-down example showing using an array:

    struct Hammer: Identifiable {
        var id = UUID()
        var location: CGPoint
    }
    
    struct Level1: View {
        @State var hammers: [Hammer] = [] //<-- Start with `none`
        
        var body: some View {
            ZStack {
                ForEach(hammers) { hammer in // Display all of the hammers
                    Image(systemName: "hammer.fill")
                        .resizable()
                        .frame(width: 30, height: 30)
                        .position(hammer.location)
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .contentShape(Rectangle())
            .gesture(DragGesture(minimumDistance: 0).onEnded { value in
                self.hammers.append(Hammer(location: value.location)) // Add a Hammer
            })
            .edgesIgnoringSafeArea(.all)
        }
    }
    

    Note: I'm unclear on what the GeometryReader is for in your code -- you declare it, then use UIScreen dimensions -- normally in SwiftUI we just use GeometryReader