UIHostingController 大小太大

UIHostingController size too big

我正在尝试再次在 View 中将 SwiftUI View 嵌入到 UIKit UIView 中。它看起来像这样:

View
↓
UIView
↓
View

当前代码:

struct ContentView: View {
    var body: some View {
        Representable {
            Text("Hello world!")
        }
    }
}


struct Representable<Content: View>: UIViewRepresentable {
    private let content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    func makeUIView(context: Context) -> UIView {
        let host = UIHostingController(rootView: content())
        let hostView = host.view!

        return hostView
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        uiView.backgroundColor = .systemRed
    }
}

我希望 Representable 只设置 TextbackgroundColor。应该不会再大了。另外,这只是一个例子,所以这不仅仅是 Text 和设置背景颜色。

Now Aim

如果文本真的很长,也会有一个问题 - 它不受屏幕大小的限制/parent(在这种情况下使用拥抱优先级):

如何确保 Representable 仅与内容本身一样大,在本例中为 Text?如果文本被限制在一定宽度时换行,它也应该工作。

最简单的方法是使用 SwiftUI-Introspect 并从中获取 UIView。这是所有需要的代码:

Text("This is some really long text that will have to wrap to multiple lines")
    .introspect(selector: TargetViewSelector.siblingOfType) { target in
        target.backgroundColor = .systemRed
    }

如果视图有点复杂并且没有专门针对它的 UIView,您可以将它嵌入 ScrollView,这样内容现在将是 UIView:

ScrollView {
    Text("Complex content here")
}
.introspectScrollView { scrollView in
    scrollView.isScrollEnabled = false
    scrollView.clipsToBounds = false

    scrollView.subviews.first!.backgroundColor = .systemRed
}

如果您不想使用 Introspect(我会高度推荐),下面有第二种解决方案。第二种解决方案适用于大多数情况,但不适用于 all.


先看上面的解决方法

我创建了一个有效的答案。它看起来很复杂,但它确实有效。

它的工作原理基本上是使用内部 GeometryReader 来测量要包装的内容的大小,使用外部 GeometryReader 来测量整个容器的大小。这意味着 Text 现在将换行,因为它受到外部容器大小的限制。

代码:

struct ContentView: View {
    var body: some View {
        Wrapper {
            Text("This is some really long text that will have to wrap to multiple lines")
        }
    }
}
struct Wrapper<Content: View>: View {
    @State private var size: CGSize?
    @State private var outsideSize: CGSize?
    private let content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    var body: some View {
        GeometryReader { outside in
            Color.clear.preference(
                key: SizePreferenceKey.self,
                value: outside.size
            )
        }
        .onPreferenceChange(SizePreferenceKey.self) { newSize in
            outsideSize = newSize
        }
        .frame(width: size?.width, height: size?.height)
        .overlay(
            outsideSize != nil ?
                Representable {
                    content()
                        .background(
                            GeometryReader { inside in
                                Color.clear.preference(
                                    key: SizePreferenceKey.self,
                                    value: inside.size
                                )
                            }
                            .onPreferenceChange(SizePreferenceKey.self) { newSize in
                                size = newSize
                            }
                        )
                        .frame(width: outsideSize!.width, height: outsideSize!.height)
                        .fixedSize()
                        .frame(width: size?.width ?? 0, height: size?.height ?? 0)
                }
                .frame(width: size?.width ?? 0, height: size?.height ?? 0)
            : nil
        )
    }
}
struct SizePreferenceKey: PreferenceKey {
    static let defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}
struct Representable<Content: View>: UIViewRepresentable {
    private let content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    func makeUIView(context: Context) -> UIView {
        let host = UIHostingController(rootView: content())
        let hostView = host.view!
        return hostView
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        uiView.backgroundColor = .systemRed
    }
}

结果:

显示它确实使包装器的大小与 SwiftUI 视图完全相同的另一个示例:

struct ContentView: View {
    var body: some View {
        VStack {
            Wrapper {
                Text("This is some really long text that will have to wrap to multiple lines")
            }
            .border(Color.green, width: 3)

            Wrapper {
                Text("This is some really long text that will have to wrap to multiple lines. However, this bottom text is a bit longer and may wrap more lines - but this isn't a problem here")
            }
            .border(Color.blue, width: 3)
        }
    }
}