1

I would like to adapt the position of one View (Rectangle) to another View (Button1). I only found solutions between a parent and a child-view or two child-views of the same parent view. Any tips how I could do that in this situation?

struct View: View {
    var body: some View {
        ZStack {
            VStack {
                Spacer()
                HStack {
                    Spacer()
                    Button {} label: {
                        Text("1").foregroundColor(.white).padding().border(Color.white, width: 3).cornerRadius(5)
                    }
                    Spacer()
                    Button {} label: {
                        Text("2").foregroundColor(.white).padding().border(Color.white, width: 3).cornerRadius(5)
                    }
                    Spacer()
                    Button {} label: {
                        Text("3").foregroundColor(.white).padding().border(Color.white, width: 3).cornerRadius(5)
                    }
                    Spacer()
                    Button {} label: {
                        Text("4").foregroundColor(.white).padding().border(Color.white, width: 3).cornerRadius(5)
                    }
                    Spacer()
                }
                Spacer()
            }
            ZStack {
                Color.black.opacity(0.5)
                    .ignoresSafeArea()
                Rectangle() // adapt this Rectangle to the button
                    .cornerRadius(10)
                    .frame(width: 200, height: 200)
                    .blendMode(.destinationOut)
            }
            .compositingGroup()
        }
        .background(Color.blue)
    }
}

edit: I want to align the rectangle with the button to code a tutorial.

screenshot

Thanks a lot!

buleva
  • 99
  • 7
  • Is there any reason to this? why not directly set a ZStack at the button level ? The only way to achieve what you want is set the button1 in a Geometry reader and onAppear set some state var with its geometry then use this geometry for the rectangle . But there must be a simpler solution for you need if you explain it. – Ptit Xav Nov 01 '22 at 12:27
  • I think ZStack is not possible, because I want some kind of "hole"-effect and therefore the button and the rectangle can't be in the same ZStack. I will try using Geometry Reader. Thanks – buleva Nov 01 '22 at 13:18

1 Answers1

0

This was the solution I found.

struct View: View {
    @State private var sizeOfHole = CGSize()
    @State private var positionOfHole = CGPoint()
    var body: some View {
        ZStack {
            VStack {
                Spacer()
                HStack {
                    Spacer()
                    Button {
                    } label: {
                            Text("1").foregroundColor(.white).padding().border(Color.white, width: 3).cornerRadius(5)
                    }
                    .readSize { size in
                        sizeOfHole = size
                    } // get the size of the button
                    .readPosition{ position in
                        positionOfHole = position
                    } // get the position of the button
                    Spacer()
                    Button {} label: {
                        Text("2").foregroundColor(.white).padding().border(Color.white, width: 3).cornerRadius(5)
                    }
                    Spacer()
                    Button {} label: {
                        Text("3").foregroundColor(.white).padding().border(Color.white, width: 3).cornerRadius(5)
                    }
                    Spacer()
                    Button {} label: {
                        Text("4").foregroundColor(.white).padding().border(Color.white, width: 3).cornerRadius(5)
                    }
                    Spacer()
                }
                Spacer()
            }
            ZStack {
                Color.black.opacity(0.5)
                    .ignoresSafeArea()
                Rectangle()
                    .cornerRadius(10)
                    .frame(width: sizeOfHole.width + 10, height: sizeOfHole.height + 10) // set the size of the holw
                    .position(x: positionOfHole.x + (sizeOfHole.width / 2), y: positionOfHole.y - (sizeOfHole.height / 2)+1) // set the position of the hole
                    .blendMode(.destinationOut)
            }
            .compositingGroup()
        }
        .background(Color.blue)
    }
}

// added this
extension View {
  func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
    background(
      GeometryReader { geometryProxy in
        Color.clear
              .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
      }
    )
    .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
  }
}

extension View {
  func readPosition(onChange: @escaping (CGPoint) -> Void) -> some View {
    background(
      GeometryReader { geometryProxy in
        Color.clear
              .preference(key: PositionPreferenceKey.self, value: geometryProxy.frame(in: CoordinateSpace.global).origin)
      }
    )
    .onPreferenceChange(PositionPreferenceKey.self, perform: onChange)
  }
}

private struct SizePreferenceKey: PreferenceKey {
  static var defaultValue: CGSize = .zero
  static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}

private struct PositionPreferenceKey: PreferenceKey {
  static var defaultValue: CGPoint = .zero
  static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {}
}

this is how it looks like

Credit go to: Get width of a view using in SwiftUI and https://stackoverflow.com/a/56520321/

General Grievance
  • 4,555
  • 31
  • 31
  • 45
buleva
  • 99
  • 7