3

I was wondering how GeometryReader works under cover, and I am interested to build a custom GeometryReader for learning purpose!

Frankly I think every single view that we use in body is kind of GeometryReaderView with this difference that they do not use a closure for sending the proxy for us and it would annoying that every single view call back it's proxy! Therefore apple decided to give Geometry reading function to GeometryReader! So it is just my thoughts!

So I am looking a possible and also more likely SwiftUI-isch approach to reading proxy of view, or in other words see my codes in down:

struct ContentView: View {
    
    var body: some View {
        
        CustomGeometryReaderView { proxy in
            
            Color.red
                .onAppear() {
                    print(proxy)
                }
            
        }
        
    }
    
}

struct CustomGeometryReaderView<Content: View>: View {
    
    @ViewBuilder let content: (CGSize) -> Content
    
    var body: some View {
        
        // Here I most find a way to reading the available size for CustomGeometryReaderView and reporting it back!
        return  Color.clear.overlay(content(CGSize(width: 100.0, height: 100.0)), alignment: .topLeading)
        
    }
    
}

Also I know that reading and reporting proxy of a view is not just the size of view, also it is about CoordinateSpace, frame ... But for now for making things easier to solve I am just working on size! So size matter!

As I said I am not interested to working with UIKit or UIViewRepresentable for reading the size! May apple using something like that under cover or may not! My goal was trying solve the issue with pure SwiftUI or may some of you have some good link about source code of GeometryReader for reading and learning of it.

ios coder
  • 1
  • 4
  • 31
  • 91
  • `GeometryReader` will just use some stuff which is hidden away in SwiftUI. After all, most components in SwiftUI are just `UIView`s. SwiftUI will be able to read the `frame`s of those, the layout constraints, and anything else relevant. It's not reasonable possible to do this. – George Nov 10 '21 at 19:45
  • Thanks, that is also my issue that I am not sure that GeometryReader really use `UIView` or not! I mean we all know that most of the things of SwiftUI coming from `UIKit` but I do not know if it is so as you said like 100%. If we could be sure that there is no other way rather than using `UIKit` so I call it 1 step closer to goal. – ios coder Nov 10 '21 at 19:51
  • 1
    Some things in SwiftUI are not views, but might be `CALayer`s instead. But again, you won't know if it's *really* a `CALayer` or just something `CALayer`-like so chances are you can't reliably do what you want to do. – Scott Thompson Nov 11 '21 at 01:04

2 Answers2

2

Ok, there are several instruments in SwiftUI providing access to view size (except GeometryReader of course).

The problem of course is to transfer that size value into view build phase, because only GeometryReader allows to do it in same build cycle.

Here is a demo of possible approach using Shape - a shape by design has no own size and consumes everything available, so covers all area, and has been provided that area rect as input.

Tested with Xcode 13 / iOS 15

struct CustomGeometryReaderView<Content: View>: View {

    @ViewBuilder let content: (CGSize) -> Content

    private struct AreaReader: Shape {
        @Binding var size: CGSize

        func path(in rect: CGRect) -> Path {
            DispatchQueue.main.async {
                size = rect.size
            }
            return Rectangle().path(in: rect)
        }
    }

    @State private var size = CGSize.zero

    var body: some View {
        // by default shape is black so we need to clear it explicitly
        AreaReader(size: $size).foregroundColor(.clear)
            .overlay(Group {
                if size != .zero {
                    content(size)
                }
            })
    }
}

Alternate: same, but using callback-based pattern

struct CustomGeometryReaderView<Content: View>: View {

    @ViewBuilder let content: (CGSize) -> Content

    private struct AreaReader: Shape {
        var callback: (CGSize) -> Void

        func path(in rect: CGRect) -> Path {
            callback(rect.size)
            return Rectangle().path(in: rect)
        }
    }

    @State private var size = CGSize.zero

    var body: some View {
        AreaReader { size in
            if size != self.size {
                DispatchQueue.main.async {
                    self.size = size
                }
            }
        }
        .foregroundColor(.clear)
        .overlay(Group {
            if size != .zero {
                content(size)
            }
        })
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thanks for your answer, frankly I were aware to try using Shape to read the size! But I did not tried to do that, because in that case my question would be still unanswered! more info: I do not know how geometryReader works under cover, same issue with Shape, i do not know how Shape do work under cover! I hope I could say what I mean! The question is How geometryReader or Shape, they can measure the available space! I just questioned about geometryReader for now but once we know how it works, it can be used to know how Shape works also. – ios coder Nov 11 '21 at 21:36
0

Whenever I need to know the frame of a view, GeometryReader is just unnecessarily complicated and the 'Bound preference tried to update multiple times per frame' warnings are horrible. So I decided to create CustomGeometryReader

Arutyun Enfendzhyan
  • 1,612
  • 1
  • 12
  • 15