SwiftUI视图无法正确呈现为UIImage

问题描述 投票:0回答:3

由于我定义的方法,这可能是一个非常小众的问题,但希望有人可以帮助发现此代码中的错误。

因此,一方面,我将多行文本字段定义为:

struct MultilineTextField: View {

    @State var dynamicHeight: CGFloat = 60

    @Binding var text: String

    var body: some View {
        UITextViewWrapper(text: $text, calculatedHeight: $dynamicHeight)
            .frame(height: dynamicHeight)
    }

}


fileprivate struct UITextViewWrapper: UIViewRepresentable {

    @Binding var text: String
    @Binding var calculatedHeight: CGFloat

    func makeUIView(context: Context) -> UITextView {
        let textField = UITextView()

        textField.delegate = context.coordinator
        textField.isEditable = true
        textField.font = UIFont.preferredFont(forTextStyle: .headline)
        textField.isSelectable = true
        textField.isUserInteractionEnabled = true
        textField.isScrollEnabled = false
        textField.backgroundColor = UIColor.clear
        textField.returnKeyType = .done
        textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        textField.textAlignment = .center
        textField.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

        return textField
    }

    func updateUIView(_ uiView: UITextView, context: Context) {

        uiView.text = self.text

        UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
    }

    fileprivate static func recalculateHeight(view: UIView, result: Binding<CGFloat>) {

        let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))

        if result.wrappedValue != newSize.height {
            DispatchQueue.main.async {
                result.wrappedValue = newSize.height // !! must be called asynchronously
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(text: $text, height: $calculatedHeight)
    }

    final class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>
        var calculatedHeight: Binding<CGFloat>

        init(text: Binding<String>, height: Binding<CGFloat>) {
            self.text = text
            self.calculatedHeight = height
        }

        func textViewDidChange(_ uiView: UITextView) {
            text.wrappedValue = uiView.text
            UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight)
        }

        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText 
            return
        }

    }

}

然后我有一种将SwiftUI视图转换为UIImage的方法,定义为:

extension View {
    func asImage() -> UIImage {
        let controller = UIHostingController(rootView: self)

        controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
        UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)

        let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
        controller.view.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        controller.view.sizeToFit()

        controller.view.backgroundColor  = UIColor.white.withAlphaComponent(0.0)

        let image = controller.view.asImage()
        controller.view.removeFromSuperview()
        return image
    }
}

extension UIView {
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

但是,当我尝试将MultilineTextField转换为UIImage时,MultilineTextField的高度始终不正确。无论里面有多少文本,它似乎始终默认为60。

为了进行比较,当我在其他SwiftUI视图中使用MultilineTextField时,高度可以正常工作和更新,并根据MultilineTextField内部的文本来增加和减少。

[我的假设是,当我尝试将MultilineTextField视图转换为UIView时,我没有正确创建一个虚拟的subView来添加它(出于将其捕获为UIImage的目的)。但是我真的不知道。

ios swift uiview swiftui
3个回答
0
投票

我通常发现最好使用UIViewControllerRepresentable而不是UIViewRepresentable ...在那里您将拥有更多的访问生命周期,可以调整大小。


0
投票

尝试一下:

一如既往,调试器是您的朋友;)您的错误在这里:

您写道:

 fileprivate static func recalculateHeight(view: UIView, result: Binding<CGFloat>) {

        let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))

但是视图是您自己的视图,大小为零,因此newSize计算出错。

如果您采取此方法:(当然,您可以将宽度从UIScreen更改为所需的任何值)

 fileprivate static func recalculateHeight(view: UIView, result: Binding<CGFloat>) {

        let newSize = view.sizeThatFits(CGSize(width: UIScreen.main.bounds.size.width, height: CGFloat.greatestFiniteMagnitude))

您将获得:(请注意,我没有检查您的图像构建代码,这只是我检查过的multitextview代码。...

是的,我将背景设为红色以查看textview有多大...

enter image description here

struct MultilineTextField: View {

    @State var dynamicHeight: CGFloat = 260

    @Binding var text: String

    var body: some View {
        UITextViewWrapper(text: $text, calculatedHeight: $dynamicHeight)

            .frame(height: dynamicHeight)
    }

}


fileprivate struct UITextViewWrapper: UIViewRepresentable {

    @Binding var text: String
    @Binding var calculatedHeight: CGFloat

    func makeUIView(context: Context) -> UITextView {
        let textField = UITextView()

        textField.delegate = context.coordinator
        textField.isEditable = true
        textField.font = UIFont.preferredFont(forTextStyle: .headline)
        textField.isSelectable = true
        textField.isUserInteractionEnabled = true
        textField.isScrollEnabled = false
        textField.backgroundColor = UIColor.clear
        textField.returnKeyType = .done
        textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        textField.textAlignment = .center
        textField.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)


        return textField
    }

    func updateUIView(_ uiView: UITextView, context: Context) {

        uiView.text = self.text

        UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
    }

    fileprivate static func recalculateHeight(view: UIView, result: Binding<CGFloat>) {

        let newSize = view.sizeThatFits(CGSize(width: UIScreen.main.bounds.size.width, height: CGFloat.greatestFiniteMagnitude))
//        let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))

        if result.wrappedValue != newSize.height {
            DispatchQueue.main.async {
                result.wrappedValue = newSize.height // !! must be called asynchronously
                print(newSize.height)
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(text: $text, height: $calculatedHeight)
    }

    final class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>
        var calculatedHeight: Binding<CGFloat>

        init(text: Binding<String>, height: Binding<CGFloat>) {
            self.text = text
            self.calculatedHeight = height
        }

        func textViewDidChange(_ uiView: UITextView) {
            text.wrappedValue = uiView.text
            UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight)
        }

    }

}


extension View {
    func asImage() -> UIImage {
        let controller = UIHostingController(rootView: self)

        controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
        UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)

        let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
        controller.view.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        controller.view.sizeToFit()

        controller.view.backgroundColor  = UIColor.white.withAlphaComponent(0.0)

        let image = controller.view.asImage()
        controller.view.removeFromSuperview()
        return image
    }
}

extension UIView {
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

struct ContentView : View {

    var body: some View {
        ScrollView {
            Spacer()
            Rectangle()
            Rectangle()
            Rectangle()
            Rectangle()

            MultilineTextField(text: .constant("asldfjkas dfal sdf jasldjf laksd flkas dflasj flasj dflasd jfölasdj flkasj dflkj asldf aslkdfj asldjf aslkdf alksdf klasd jflkasj dflköas jdflasj dflk asdlkfj alsdf alsdjf alsd flkas dfla dflkjas dflk alfd al dflka dlfja sdföal dsfa öföa dölkaj sdl ENDE"))
                .background(Color.red)
      Rectangle()
        }
    }
}

0
投票

我没有深入探讨您的方法不起作用的原因,尽管我怀疑您只是为捕获而创建新视图的事实给您带来了很多麻烦。我认为布局过程可能需要几个周期才能达到正确的大小。到那时,您已经创建了图像。

我修改了您的代码以使用现有的视图,并仅捕获其所在的矩形。

此外,如果您的应用程序支持多个窗口,请小心一点,您可能需要稍微调整代码(我的意思是,UIApplication.shared.windows [0] .rootViewController部分!]]]

enter image description here

import SwiftUI

struct ContentView: View {
    @State private var text = "Line #1\nLine #2\nLine #3\nLine #4"
    @State var img: UIImage? = nil
    @State var rect: CGRect = .zero

    var body: some View {
        let multiLineView = MultilineTextField(text: $text)

        return VStack {
            if img != nil {
                Image(uiImage: img!)
            }

            multiLineView
                .border(Color.red, width: 3.0)
                .background(RectGetter(rect: $rect))
                .padding(.horizontal, 20)

            Button("Capture") {
                print("capture = \(self.rect)")
                self.img = multiLineView.asImage(rect: self.rect)
            }

            Spacer()
        }
    }
}

struct MultilineTextField: View {

    @State var dynamicHeight: CGFloat = 60

    @Binding var text: String

    var body: some View {
        return UITextViewWrapper(text: $text, calculatedHeight: $dynamicHeight)
            .frame(height: dynamicHeight)
    }
}


fileprivate struct UITextViewWrapper: UIViewRepresentable {

    @Binding var text: String
    @Binding var calculatedHeight: CGFloat

    func makeUIView(context: Context) -> UITextView {
        let textField = UITextView()

        textField.delegate = context.coordinator
        textField.isEditable = true
        textField.font = UIFont.preferredFont(forTextStyle: .headline)
        textField.isSelectable = true
        textField.isUserInteractionEnabled = true
        textField.isScrollEnabled = false
        textField.backgroundColor = UIColor.clear
        textField.returnKeyType = .done
        textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        textField.textAlignment = .center
        textField.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

        return textField
    }

    func updateUIView(_ uiView: UITextView, context: Context) {

        uiView.text = self.text

        UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
    }

    fileprivate static func recalculateHeight(view: UIView, result: Binding<CGFloat>) {

        let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))

        if result.wrappedValue != newSize.height {
            DispatchQueue.main.async {
                result.wrappedValue = newSize.height // !! must be called asynchronously
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(text: $text, height: $calculatedHeight)
    }

    final class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>
        var calculatedHeight: Binding<CGFloat>

        init(text: Binding<String>, height: Binding<CGFloat>) {
            self.text = text
            self.calculatedHeight = height
        }

        func textViewDidChange(_ uiView: UITextView) {
            text.wrappedValue = uiView.text
            UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight)
        }

    }
}

extension View {
    func asImage(rect: CGRect) -> UIImage {
        return UIApplication.shared.windows[0].rootViewController?.view.asImage(rect: rect) ?? UIImage()
    }
}

struct RectGetter: View {
    @Binding var rect: CGRect

    var body: some View {
        GeometryReader { proxy in
            self.createView(proxy: proxy)
        }
    }

    func createView(proxy: GeometryProxy) -> some View {
        DispatchQueue.main.async {
            self.rect = proxy.frame(in: .global)
        }

        return Rectangle().fill(Color.clear)
    }
}

extension UIView {
    func asImage(rect: CGRect) -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: rect)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.