有什么方法可以在SwiftUI中获得子视图的大小吗?
我基本上是想做等效于以下内容的UIKit:
self.child.frame.origin.x -= self.child.intrinsicContentSize.width/2.0
我认为GeometryReader不起作用,因为它会返回父级中的可用大小。
[编辑]我发现可以使用.alignmentGuide(_, computeValue:)
来获取和保存尺寸,尽管这绝对是个技巧。
LessonSliderText(text: self.textForProgress(self.progress), color: self.completedColor)
.alignmentGuide(HorizontalAlignment.leading) { (dimensions) -> Length in
self.textSize = CGSize(width: dimensions.width, height: dimensions.height)
return 0
}
.offset(x: self.width*self.currentPercentage - self.textSize.width / 2.0)
.offset(y: -self.textSize.height/2.0)
.animation(nil)
.opacity(self.isDragging ? 1.0 : 0.0)
.animation(.basic())
答案 0 :(得分:13)
已更新和通用的@arsenius代码。现在,您可以轻松绑定父视图的状态变量。
struct ChildSizeReader<Content: View>: View {
@Binding var size: CGSize
let content: () -> Content
var body: some View {
ZStack {
content()
.background(
GeometryReader { proxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: proxy.size)
}
)
}
.onPreferenceChange(SizePreferenceKey.self) { preferences in
self.size = preferences
}
}
}
struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero
static func reduce(value _: inout Value, nextValue: () -> Value) {
_ = nextValue()
}
}
用法:
struct ChildSizeReaderExample: View {
@State var textSize: CGSize = .zero
var body: some View {
VStack {
ChildSizeReader(size: $textSize) {
Text("Hello I am some arbitrary text.")
}
Text("My size is \(textSize.debugDescription)!")
}
}
}
答案 1 :(得分:0)
基本上,这时的答案是在孩子的background(...)
修饰符的内部 中使用GeometryReader。不过,只有在布局好视图之后,才能进行评估。
// This won't be valid until the first layout pass is complete
@State var childRect: CGRect = .zero
var body: some View {
Text("Hello World!")
.background(
GeometryReader { proxy in
let rect = proxy.frame(in: .global)
// This avoids an infinite layout loop
if rect.integral != self.childRect.integral {
DispatchQueue.main.async(after: .now()) {
self.childRect = rect
}
}
// Just `EmptyView()` results in the background being ignored, so the size won't be calculated.
return AnyView(EmptyView())
}
)
}
答案 2 :(得分:0)
您可以使用AnchorPreferences将有关子视图几何图形的信息冒泡直至父视图。参见:https://swiftui-lab.com/communicating-with-the-view-tree-part-2/
答案 3 :(得分:0)
以下是已接受答案的可重用变体:
protocol SizeReaderKey: PreferenceKey where Value == CGSize {}
extension SizeReaderKey {
static func reduce(value _: inout CGSize, nextValue: () -> CGSize) {
_ = nextValue()
}
}
struct SizeReader<Key: SizeReaderKey>: ViewModifier {
func body(content: Content) -> some View {
content
.background(
GeometryReader { geo in
Color.clear
.preference(key: Key.self, value: geo.size)
}
)
}
}
extension View {
func onSizeChanged<Key: SizeReaderKey>(
_ key: Key.Type,
perform action: @escaping (CGSize) -> Void) -> some View
{
self
.modifier(SizeReader<Key>())
.onPreferenceChange(key) { value in
action(value)
}
}
}
用法:
struct Example: View {
var body: some View {
Text("Hello, World!")
.onSizeChanged(CustomViewSizeKey.self) { size in
print("size: \(size)")
}
}
struct CustomViewSize: SizePreferenceKey {
static var defaultValue: CGSize = .zero
}
}
答案 4 :(得分:0)
我参考了以下所有已经回答的代码。
this answer 的自定义修饰符:
extension View {
func size(size: Binding<CGSize>) -> some View {
ChildSizeReader(size: size) {
self
}
}
}
作为 this comment says,我认为 ZStack
没有必要,所以我也发布了一个删除了 ZStack
的版本。
所有代码:
import SwiftUI
struct ContentView: View {
var body: some View {
ChildSizeReaderExample()
}
}
struct ChildSizeReaderExample: View {
@State var textSize: CGSize = .zero
var body: some View {
VStack {
Text("Hello I am some arbitrary text.").size(size: $textSize) // Usage
Text("My size is \(textSize.debugDescription)")
}
}
}
struct ChildSizeReader<Content: View>: View {
@Binding var size: CGSize
let content: () -> Content
var body: some View {
// Remove ZStack from the existing answer.
content().background(
GeometryReader { proxy in
Color.clear.preference(
key: SizePreferenceKey.self,
value: proxy.size
)
}
)
.onPreferenceChange(SizePreferenceKey.self) { preferences in
self.size = preferences
}
}
}
struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero
static func reduce(value _: inout Value, nextValue: () -> Value) {
_ = nextValue()
}
}
extension View {
func size(size: Binding<CGSize>) -> some View {
ChildSizeReader(size: size) {
self
}
}
}