-2

I am new to the swift, I met an issue and I have been searching on the internet for a whole day but still not got the solution.

There is a similar question, and I also try to use simultaneous gestures to get the position but it's quite not precise and has relative deviation base on the direction I drag. So I wondered is there any other way to invoke my code after the button is rendered and also give me the reference of the button so that I can get the position? Just found out I can use the startLocation attribute but it may not be the center point of the button because it depends on user drag started position.

SwiftUI - how to get coordinate/position of clicked Button

Question: How to get the x and y location relative to the screen for the button after it renders?

My goal: I want to generate a few other views base on the position of the specified button. Once the User press it, it and a few other views pop up and disappear when the user releases the button. Thus need a precise original position to calculate the new position.

This function I want to create is kinda like the game app but I am trying to create a normal simple app. Much appreciate it if anyone can help!

yimkong
  • 71
  • 1
  • 7

1 Answers1

2

One way to accomplish this is utilising "anchor preferences".

The idea is, to create the bounds anchor of the button when it is created and store it into an anchor preference.

To get an actual bounds value, we need a GeometryProxy where we relate the bounds anchor and get the bounds value.

When we have the bounds value, we store it in a state variable where they are accessible when the button action executes.

The following solution creates a number of buttons where the bounds are accessible via a Dictionary where the key is the button's label.

import SwiftUI

struct ContentView: View {

    let labels = (0...4).map { "- \($0) -" }

    @State private var bounds: [String: CGRect] = [:]

    var body: some View {
        VStack {
            ForEach(labels, id: \.self) { label in
                Button(action: {
                    let bounds = bounds[label]
                    print(bounds ?? "")
                }) {
                    Text(verbatim: label)
                }
                // Store bounds anchors into BoundsAnchorsPreferenceKey:
                .anchorPreference(
                    key: BoundsAnchorsPreferenceKey.self,
                    value: .bounds,
                    transform: { [label: $0] })
            }
        }
        .frame(width: 300, height: 300, alignment: .center)
        .backgroundPreferenceValue(BoundsAnchorsPreferenceKey.self) { anchors in
            // Get bounds relative to VStack:
            GeometryReader { proxy in
                let localBoundss = anchors.mapValues { anchor in
                    CGRect(origin: proxy[anchor].origin, size: proxy[anchor].size)
                }
                Color.clear.border(Color.blue, width: 1)
                    .preference(key: BoundsPreferenceKey.self, value: localBoundss)
            }
        }
        .onPreferenceChange(BoundsPreferenceKey.self) { bounds in
            // Store bounds into the state variable:
            self.bounds = bounds
        }
    }

}

extension CGRect: Hashable {
    public func hash(into hasher: inout Hasher) {
        hasher.combine(origin.x)
        hasher.combine(origin.y)
        hasher.combine(size.width)
        hasher.combine(size.height)
    }
}

struct BoundsAnchorsPreferenceKey: PreferenceKey {
    typealias Value = [String: Anchor<CGRect>]
    static var defaultValue: Value = [:]

    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = value.merging(nextValue()) { (_, new) in new }
    }
}

struct BoundsPreferenceKey: PreferenceKey {
    typealias Value = [String:  CGRect]
    static var defaultValue: Value = [:]

    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = value.merging(nextValue()) { (_, new) in new }
    }
}


import PlaygroundSupport
PlaygroundPage.current.setLiveView(
    NavigationView {
        ContentView()
    }
    .navigationViewStyle(.stack)
)

The solution as is, looks a bit elaborated - but doesn't use any "tricks". We may alleviate this somewhat with using ViewModifiers.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67