SwiftUI - 获取 child 的大小?
SwiftUI - Get size of child?
有什么方法可以在 SwiftUI 中获取 child 视图的大小?
我基本上是想做 UIKit 相当于:
self.child.frame.origin.x -= self.child.intrinsicContentSize.width/2.0
我认为 GeometryReader 无法工作,因为 returns parent 中的可用大小。
[编辑] 我发现可以使用 .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())
基本上,此时的答案是使用 GeometryReader
inside of the child's background(...)
modifier.
// 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()
}
}
更新和通用化@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)!")
}
}
}
这是已接受答案的可重用变体:
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
}
我参考了以下所有已回答的代码。
的自定义修饰符:
extension View {
func size(size: Binding<CGSize>) -> some View {
ChildSizeReader(size: size) {
self
}
}
}
因为,我觉得ZStack
没必要,所以我也post一个去掉了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
}
}
}
// 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()
}
}
将 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[[=10=]] }
.onPreferenceChange(BoundsPreferenceKey.self) { bounds = [=10=] }
}
}
}
private struct BoundsPreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
在这种方法中,您使用 GeometryReader 包围子视图,并使用锚首选项来获取包含 GeometryReader 坐标中视图的位置和大小的 CGRect。
该解决方案的一个潜在缺点是,如果您没有计划,GeometryReader 可能会弄乱您的布局,因为它会扩展以占用所有可用空间 space。但是,如果您出于布局目的需要子视图大小,则很有可能您已经使用 GeometryReader 来测量父视图大小。
有什么方法可以在 SwiftUI 中获取 child 视图的大小?
我基本上是想做 UIKit 相当于:
self.child.frame.origin.x -= self.child.intrinsicContentSize.width/2.0
我认为 GeometryReader 无法工作,因为 returns parent 中的可用大小。
[编辑] 我发现可以使用 .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())
基本上,此时的答案是使用 GeometryReader
inside of the child's background(...)
modifier.
// 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()
}
}
更新和通用化@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)!")
}
}
}
这是已接受答案的可重用变体:
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
}
我参考了以下所有已回答的代码。
extension View {
func size(size: Binding<CGSize>) -> some View {
ChildSizeReader(size: size) {
self
}
}
}
因为ZStack
没必要,所以我也post一个去掉了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
}
}
}
// 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()
}
}
将 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[[=10=]] }
.onPreferenceChange(BoundsPreferenceKey.self) { bounds = [=10=] }
}
}
}
private struct BoundsPreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
在这种方法中,您使用 GeometryReader 包围子视图,并使用锚首选项来获取包含 GeometryReader 坐标中视图的位置和大小的 CGRect。
该解决方案的一个潜在缺点是,如果您没有计划,GeometryReader 可能会弄乱您的布局,因为它会扩展以占用所有可用空间 space。但是,如果您出于布局目的需要子视图大小,则很有可能您已经使用 GeometryReader 来测量父视图大小。