Reputation: 1
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
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
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