Search code examples
swiftuigeometryreader

Better solution for getting position when tap on button


I want to get the position when the user taps on a button to display another view at the button's position. The button is placed inside a ScrollView, so its position can change. I'm using GeometryReader to read the position. However, GeometryReader fills all the space of the parent view, so we need to limit its width using the .frame modifier. This approach works, but since my app is localized, the text of the button label has different widths in different languages. Therefore, I want a better method to automatically adjust the button's width instead of setting it to a fixed value.

Here is my current code:

GeometryReader { geometry in
    Button(action: {
        optionPosition = geometry.frame(in: .global)

        isFilterShown = true
    }) {
        Image(systemName: "slider.horizontal.3")
            .font(.system(size: 18, weight: .light))
            .foregroundColor(Color(.label))

        Text("_filter_")
            .font(.regular(size: 16))
            .foregroundColor(Color(.label))
    }
    .padding(8)
    .background(RoundedRectangle(cornerRadius: 8).fill(Color(.secondarySystemBackground)))
}

Does anyone have a better solution for this case?


Solution

  • I found a solution; however, I still feel it is inefficient because we need to update the position on scroll and maintain a separate variable to store it.

    Button(action: {
        isFilterShown = true
    }) {
        Image(systemName: "slider.horizontal.3")
            .font(.system(size: 18, weight: .light))
            .foregroundColor(Color(.label))
    
        Text("_filter_")
            .font(.regular(size: 16))
            .foregroundColor(Color(.label))
    }
    .padding(8)
    .background(
        GeometryReader { geometry in
            RoundedRectangle(cornerRadius: 8).fill(Color(.secondarySystemBackground))
                .onAppear {
                    filterPosition = geometry.frame(in: .global)
                }
                .onChange(of: geometry.frame(in: .global)) {
                    filterPosition = $0
                }
        }
    )