swiftPunk
swiftPunk

Reputation: 1

How can make a function which take View and returns custom result in SwiftUI?

I want build a function stand alone from ContentView which I could use this func to initialize some value, for example in this down code I want get size of View with the function, but for unknown reason for me it returns zero, I think the background modification do not work as I wanted in this build. any help?

  func viewSizeReaderFunction<Content: View>(content: Content) -> CGSize {
    
    var sizeOfView: CGSize = CGSize()
    
    content
        .background(
            GeometryReader { geometry in
                Color
                    .clear
                    .onAppear() { sizeOfView =  geometry.size }
                
            })

    return sizeOfView

 }

let sizeOfText: CGSize = viewSizeReaderFunction(content: Text("Hello, world!"))

struct ContentView: View {

    var body: some View {

        Color.red
            .onAppear() {
                print(sizeOfText)
            }

    }
  
}

Upvotes: 1

Views: 1430

Answers (2)

New Dev
New Dev

Reputation: 49590

The general idea is to have the view report its size using preference, and create a view modifier to capture that. But, like @RobNapier said, the struct has to be in the view hierarchy, and so within a rendering context, to be able to talk about sizes.

struct SizeReporter: ViewModifier {
   @Binding var size: CGSize
   func body(content: Content) -> some View {
      content
         .background(GeometryReader { geo in
             Color.clear
                  .preference(key: SizePreferenceKey.self, value: geo.size)
         })
         .onPreferenceChange(SizePreferenceKey.self, perform: { value in
             size = value
         })
   }
}

And we'd need to define SizePreferenceKey:

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

You could create a convenience method on View:

extension View {
   func getSize(_ size: Binding<CGSize>) -> some View {
      self.modifier(SizeReporter(size: size))
   }
}

and use it like so:

@State var size: CGSize = .zero

var body: some View {
   Text("hello").getSize($size)
}

Upvotes: 3

Rob Napier
Rob Napier

Reputation: 299325

Views are just data that describe view layout. They are not objects that represent the actual "view" in the way that UIView is. They do not have their own logic or state (which is why they require @State variables rather than just var).

The code you've written assigns a zero-size to sizeOfView, then creates a View struct that is immediately thrown away, and then returns sizeOfView. There is nothing that evaluates the View struct. I would expect that the compiler is giving you a warning about this, something like "result of call to background is unused."

The way you do what you're describing is with Preferences, .onPreferenceChange and usually @State. There are a lot of answers to that around Stack Overflow.

https://stackoverflow.com/search?q=%5Bswiftui%5D+size+of+view

Here's one example, note in particular the use of .hidden():

extension View {
    func saveSize(handler: @escaping (CGSize) -> Void) -> some View {
        return self
            .background(
                GeometryReader { geometry in
                    Color.clear
                        .onAppear {
                            handler(geometry.size)
                        }
                })
    }
}

struct ContentView: View {
    @State var sizeOfText: CGSize = .zero

    var body: some View {
        ZStack {
        Color.red
            .onAppear() {
                print(sizeOfText)
            }
            Text("Hello, world!")
                .hidden()
                .saveSize { sizeOfText = $0 }
        }
    }
}

Note that this code is slightly dangerous in that it relies on the order that onAppear gets called, and that's not promised. In practice, you generally need to handle the case where the size hasn't been set yet. This can be made more robust with Preferences, but that tends to be a lot of hassle.

Upvotes: 3

Related Questions