web-dev-qa-db-ja.com

ビュー(UIViewではない)を画像に変換する方法は?

this thread と同様

UIViewではなくSwiftUI Viewを画像に変換したいと思います。

7
JHack

画面にないSwiftUIビューをUIImageに保存できるときの解決策を思いつきました。ソリューションは少し奇妙に見えますが、正常に動作します。

最初に、UIHostingControllerとSwiftUIの間の相互作用として機能するクラスを作成します。このクラスでは、「ビュー」の画像をコピーするために呼び出すことができる関数を定義します。これを行った後、新しい値を「公開」してビューを更新します。

class Controller:ObservableObject {

    @Published var update=false

    var img:UIImage?

    var hostingController:MySwiftUIViewHostingController?

    init() {

    }

    func copyImage() {
        img=hostingController?.copyImage()
        update=true
    }
}

次に、UIHostingControllerを介してコピーするSwiftUIビューをラップします。

class MySwiftUIViewHostingController: UIHostingController<TestView> {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    func copyImage()->UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: self.view.bounds)

        return renderer.image(actions: { (c) in
            self.view.layer.render(in: c.cgContext)
        })
    }

}

CopyImage()関数は、コントローラーのビューをUIImageとして返します。

次に、UIHostingControllerを提示する必要があります。

struct MyUIViewController:UIViewControllerRepresentable {

    @ObservedObject var cntrl:Controller

    func makeUIViewController(context: Context) -> MySwiftUIViewHostingController {
        let controller=MySwiftUIViewHostingController(rootView: TestView())
        cntrl.hostingController=controller
        return controller
    }

    func updateUIViewController(_ uiViewController: MySwiftUIViewHostingController, context: Context) {

    }

}

そして残りは次のように:

struct TestView:View {

    var body: some View {
        VStack {
            Text("Title")
            Image("img2")
                .resizable()
                .aspectRatio(contentMode: .fill)
            Text("foot note")
        }
    }
}

import SwiftUI

struct ContentView: View {
    @ObservedObject var cntrl=Controller()
    var body: some View {
        ScrollView {
            VStack {
                HStack {
                    Image("img1")
                        .resizable()
                        .scaledToFit()
                        .border(Color.black, width: 2.0)
                        .onTapGesture(count: 2) {
                            print("tap registered")
                            self.cntrl.copyImage()
                    }
                    Image("img1")
                        .resizable()
                        .scaledToFit()
                        .border(Color.black, width: 2.0)
                }

                TextView()
                ImageCopy(cntrl: cntrl)
                    .border(Color.red, width: 2.0)
                TextView()
                TextView()
                TextView()
                TextView()
                TextView()
                MyUIViewController(cntrl: cntrl)
                    .aspectRatio(contentMode: .fit)
            }

        }
    }
}

struct ImageCopy:View {
    @ObservedObject var cntrl:Controller
    var body: some View {
        VStack {
            Image(uiImage: cntrl.img ?? UIImage())
                .resizable()
                .frame(width: 200, height: 200, alignment: .center)
        }

    }
}

struct TextView:View {

    var body: some View {
        VStack {
            Text("Bla Bla Bla Bla Bla ")
            Text("Bla Bla Bla Bla Bla ")
            Text("Bla Bla Bla Bla Bla ")
            Text("Bla Bla Bla Bla Bla ")
            Text("Bla Bla Bla Bla Bla ")

        }

    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Img1とimg2(コピーされるもの)が必要です。すべてがスクロールビューに配置されているため、画面上にない場合でも画像が正しくコピーされることがわかります。

0
matyasl

kontikiの回答 に続いて、ここに設定方法があります

import SwiftUI

struct ContentView: View {
    @State private var uiImage: UIImage? = nil
    @State private var rect1: CGRect = .zero
    @State private var rect2: CGRect = .zero

    var body: some View {
        VStack {
            HStack {
                VStack {
                    Text("LEFT")
                    Text("VIEW")
                }
                .padding(20)
                .background(Color.green)
                .border(Color.blue, width: 5)
                .getRect($rect1)
                .onTapGesture {
                    self.uiImage =  self.rect1.uiImage
                }

                VStack {
                    Text("RIGHT")
                    Text("VIEW")
                }
                .padding(40)
                .background(Color.yellow)
                .border(Color.green, width: 5)
                .getRect($rect2)
                .onTapGesture {
                    self.uiImage =  self.rect2.uiImage
                }
            }

            if uiImage != nil {
                VStack {
                    Text("Captured Image")
                    Image(uiImage: self.uiImage!).padding(20).border(Color.black)
                }.padding(20)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        InviteView()
    }
}

extension CGRect {
    var uiImage: UIImage? {
        UIApplication.shared.windows
            .filter{ $0.isKeyWindow }
            .first?.rootViewController?.view
            .asImage(rect: self)
    }
}

extension View {
    func getRect(_ rect: Binding<CGRect>) -> some View {
        self.modifier(GetRect(rect: rect))
    }
}

struct GetRect: ViewModifier {

    @Binding var rect: CGRect

    var measureRect: some View {
        GeometryReader { proxy in
            Rectangle().fill(Color.clear)
                .preference(key: RectPreferenceKey.self, value:  proxy.frame(in: .global))
        }
    }

    func body(content: Content) -> some View {
        content
            .background(measureRect)
            .onPreferenceChange(RectPreferenceKey.self) { (rect) in
                if let rect = rect {
                    self.rect = rect
                }
            }

    }
}

extension GetRect {
    struct RectPreferenceKey: PreferenceKey {
        static func reduce(value: inout CGRect?, nextValue: () -> CGRect?) {
            value = nextValue()
        }

        typealias Value = CGRect?

        static var defaultValue: CGRect? = nil
    }
}

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

0
kumar shivang