【问题标题】:SwiftUI - Get size of child?SwiftUI - 获取孩子的大小?
【发布时间】:2019-06-13 04:28:01
【问题描述】:

有什么方法可以在 SwiftUI 中获取子视图的大小?

我基本上是在寻找相当于 UIKit 的:

self.child.frame.origin.x -= self.child.intrinsicContentSize.width/2.0

我认为 GeometryReader 不会起作用,因为它会返回父级中的可用大小。

[编辑] 我发现可以使用 .alignmentGuide(_, computeValue:) 获取和保存尺寸,尽管这绝对是一个 hack。

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())

【问题讨论】:

  • 你能否展示你的代码并解释你希望你的视图是什么。
  • 你能告诉我们你想在绘画中实现什么吗?我认为您可能误解了 SwiftUI 的布局方式......
  • 我已经附上了一张图片。我正在尝试将该文本气泡放置在线上方并以白色圆圈为中心。在不影响线路布局的情况下。我粘贴的代码确实做到了。
  • 我说的对吗?你需要有一个比你的文字更大的气泡吗?还有圆角?
  • 您可以使用 AnchorPreferences 将有关子视图几何图形的信息冒泡到父视图。见:swiftui-lab.com/communicating-with-the-view-tree-part-2

标签: swiftui


【解决方案1】:

更新和通用的@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)!")
        }
    }
}

【讨论】:

  • 太棒了!如果孩子是List 的迭代行,如果你只需要一个并且它们的大小都一样,会有什么改变吗?将ChildSizeReader 应用于所有行可能会有点矫枉过正。
【解决方案2】:

基本上,此时的答案是使用子background(...) 修饰符的GeometryReader inside

// This won't be valid until the first layout pass is complete
@State var childSize: CGSize = .zero

var body: some View {
    ZStack {
        Text("Hello World!")
            .background(
                GeometryReader { proxy in
                    Color.clear
                       .preference(
                           key: SizePreferenceKey.self, 
                           value: proxy.size
                        )
                }
            )
      }
      .onPreferenceChange(SizePreferenceKey.self) { preferences in
          self.childSize = preferences
      }
}

struct SizePreferenceKey: PreferenceKey {
    typealias Value = CGSize
    static var defaultValue: Value = .zero

    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = nextValue()
    }
}     

【讨论】:

  • 别这么想,获取子视图的框架很难:(应该是一些简单的方法
  • ZStack 有什么用?
【解决方案3】:

这是已接受答案的可重复使用变体:

public protocol CGSizePreferenceKey: PreferenceKey where Value == CGSize {}

public extension CGSizePreferenceKey {
    static func reduce(value _: inout CGSize, nextValue: () -> CGSize) {
        _ = nextValue()
    }
}

public extension View {
    func onSizeChanged<Key: CGSizePreferenceKey>(
        _ key: Key.Type,
        perform action: @escaping (CGSize) -> Void) -> some View
    {
        self.background(GeometryReader { geo in
            Color.clear
                .preference(key: Key.self, value: geo.size)
        })
        .onPreferenceChange(key) { value in
            action(value)
        }
    }
}

用法:

struct Example: View {
    var body: some View {
        Text("Hello, World!")
            .onSizeChanged(ExampleViewSize.self) { size in
                print("size: \(size)")
            }
    }
}

struct ExampleViewSize: CGSizePreferenceKey {
    static var defaultValue: CGSize = .zero
}

【讨论】:

  • 泛型类型不支持静态存储属性
【解决方案4】:

将 GeometryReader 添加到视图的背景并测量 Color.clear 的大小可以工作,但对我来说似乎很笨拙。我找到了不同的方法,我想分享。

struct SomeView: View {

    @State
    var bounds: CGRect = .zero

    var body: some View {
        GeometryReader { geometry in
            Text("Hello Stack Overflow")
                .anchorPreference(key: BoundsPreferenceKey.self, value: .bounds) { geometry[$0] }
                .onPreferenceChange(BoundsPreferenceKey.self) { bounds = $0 }
        }
    }

}

private struct BoundsPreferenceKey: PreferenceKey {

    static var defaultValue: CGRect = .zero

    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
        value = nextValue()
    }

}

在这种方法中,您使用 GeometryReader 围绕子视图,并使用锚首选项来获取包含 GeometryReader 坐标中视图的位置和大小的 CGRect。

该解决方案的一个潜在缺点是,如果您没有计划,GeometryReader 可能会弄乱您的布局,因为它会扩展以占用所有可用空间。但是,如果您出于布局目的需要子视图大小,则很有可能您已经使用 GeometryReader 来测量父视图大小。

【讨论】:

  • 我认为这是正确的答案。为了解决您的担忧:GeometryReader 的贪婪,您有时可以对其进行修改,使其准确地拥抱内容:GeometryReader{...}.frame(width: bounds.width, height: bounds.height)
【解决方案5】:

我参考了以下所有已经回答的代码。

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
    }
  }
}

【讨论】:

    【解决方案6】:
    // SizeModifier.swift
    import Foundation
    import SwiftUI
    
    struct SizePreferenceKey: PreferenceKey {
        static var defaultValue: CGSize = .zero
        
        static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
            value = nextValue()
        }
    }
    
    struct SizeModifer: ViewModifier {
        
        private var sizeView: some View {
            GeometryReader { geometry in
                Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size)
            }
        }
        
        func body(content: Content) -> some View {
            content
                .background(sizeView)
        }
        
    }
    
    extension View {
        func onSizeChanged(_ handler: @escaping (CGSize) -> Void) -> some View {
            self
                .modifier(SizeModifer())
                .onPreferenceChange(SizePreferenceKey.self, perform: { value in
                    handler(value)
                })
        }
    }
    

    这里是如何使用它:

    // ContentView
    import SwiftUI
    
    struct ContentView: View {
        
        @State private var childSize: CGSize = .zero
        
        var body: some View {
            Text("My size \(childSize.width)x\(childSize.height)")
                .padding()
                .onSizeChanged { size in
                    childSize = size
                }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-10
      • 1970-01-01
      • 2018-03-15
      • 1970-01-01
      相关资源
      最近更新 更多