Skip to content

Image Gernerating

Moonkey edited this page Oct 10, 2023 · 1 revision

이미지 μ €μž₯ κΈ°λŠ₯

κ΅¬ν˜„ μˆœμ„œ

  1. TargetμœΌλ‘œν•˜λŠ” Viewλ₯Ό κ΅¬ν˜„ν•œλ‹€.

    1. ν•΄λ‹Ή λ·°λ₯Ό κ·ΈλŒ€λ‘œ μ΄λ―Έμ§€λ‘œ λ§Œλ“€κΈ° λ•Œλ¬Έμ— λ”°λ‘œ Viewλ₯Ό λ§Œλ“€κ±°λ‚˜ μ•„λž˜μ™€ 같이 λ°”λ€ŒλŠ” λ‚΄μš©μ΄ μ—†λ‹€λ©΄ extension에 computed property둜 κ΅¬ν˜„ν•΄λ„ λœλ‹€.
    // λ Œλ”ν•΄μ•Ό ν•˜λŠ” λ·°
    extension RendererTestView {
        private var TargetImageView: some View {
            ZStack{
                Color.white
                VStack {
                    Text("HELLO\nWORLD")
                        .font(.title)
                        .multilineTextAlignment(.center)
                }
                .padding(40)
                .background(.purple)
                .foregroundColor(.white)
                .shadow(color: .purple, radius: 10)
            }
        }
    }
  2. TargetImageView의 μ‚¬μ΄μ¦ˆλ₯Ό GeometryReaderλ₯Ό 톡해 μ €μž₯ν•œλ‹€.

    GeometryReader { geo in
        TargetImageView
            .onAppear {
                self.geoSize = CGSize(width: geo.size.width, height: geo.size.height)
            }
    }
  3. λ Œλ” λ²„νŠΌμ„ 클릭할 경우 μ•„λž˜μ™€ 같이 asImageλ₯Ό 톡해 이미지λ₯Ό λ Œλ”ν•œλ‹€.

    Button {
        renderImage = TargetImageView.asImage(size: self.geoSize)
    } label: {
        Text("generate image")
    }
  4. View에 λŒ€ν•œ extension에 μ•„λž˜μ™€ 같이 UIHostingControllerλ₯Ό 톡해 ν•΄λ‹Ή Viewλ₯Ό UIView둜 μ‚¬μš©κ°€λŠ₯ν•˜κ²Œ ν•œλ‹€.

    extension View {
        func asImage(size: CGSize) -> UIImage {
            let controller = UIHostingController(rootView: self)
            controller.view.bounds = CGRect(origin: .zero, size: size)
            let image = controller.view.asImage(size: size)
            return image
        }
    }
  5. UIView에 λŒ€ν•΄μ„œ μ—­μ‹œ Extension을 톡해 asImageλ₯Ό κ΅¬ν˜„ν•œλ‹€. UIGraphicsImageRenderλ₯Ό 톡해 ν•΄λ‹Ή 뷰의 크기만큼 이미지λ₯Ό λ Œλ”ν•΄μ„œ returnν•΄μ€€λ‹€.

    extension UIView {
        func asImage(size: CGSize) -> UIImage {
            let format = UIGraphicsImageRendererFormat()
            format.scale = 1
            return UIGraphicsImageRenderer(size: size, format: format).image { context in
                self.drawHierarchy(in: self.layer.bounds, afterScreenUpdates: true)
            }
        }
    }
  6. 받은 이미지λ₯Ό λ³΄μ—¬μ£Όκ±°λ‚˜ μ²˜λ¦¬ν•œλ‹€.

    struct RendererTestView: View {
        @State var geoSize: CGSize = .init(width: 0, height: 0)
        @State var renderImage: UIImage?
        
        var body: some View {
            VStack {
                GeometryReader { geo in
                    TargetImageView
                        .onAppear {
                            self.geoSize = CGSize(width: geo.size.width, height: geo.size.height)
                        }
                }
    						// Imageλ₯Ό 받은 경우 unWrappingν•΄μ„œ 보여쀀닀. 
                if let uiImage = renderImage {
                    Image(uiImage: uiImage)
                } else {
                    Spacer()
                        .frame(height: 200)
                }
                
                Button {
                    renderImage = TargetImageView.asImage(size: self.geoSize)
                } label: {
                    Text("generate image")
                }
            }
            .padding()
        }
    }

Core Graphicμ—μ„œ μ§€μ›μ§€μ›ν•˜λŠ” λ Œλ”λŸ¬λ‘œ UIGraphicsImageRendererλ₯Ό 톡해 UIImageλ₯Ό λ Œλ”ν•  수 μžˆλ‹€.

  • [UIGraphicsImageRendererFormat](https://developer.apple.com/documentation/uikit/uigraphicsimagerendererformat) : λ Œλ”ν•˜λŠ” image의 scale 등을 μ„€μ •ν•  수 μžˆλ‹€.
  • [drawHierarchy(in:afterScreenUpdates:)](https://developer.apple.com/documentation/uikit/uiview/1622589-drawhierarchy) : ν˜„μž¬ ν™”λ©΄μ—μ„œ κ·Έλ €μ§„ 화면을 renderν•©λ‹ˆλ‹€.

UIHostingControllerλŠ” SwiftUIλ·°λ₯Ό UIKitμ—μ„œ μ‚¬μš©ν•  수 있게 도와쀀닀. μ œλ„€λ¦­μœΌλ‘œ κ°€μ§€λŠ” Content에 SwiftUIλ·°λ₯Ό λ„£μ–΄μ„œ μ„ μ–Έν•˜λ©΄ ν•΄λ‹Ή λ·°λŠ” UIKitμ—μ„œ μ‚¬μš©κ°€λŠ₯ν•œ 뷰둜 ν™œμš© κ°€λŠ₯ν•˜κ²Œ λœλ‹€. μ—¬κΈ°μ„œλŠ” ν•΄λ‹Ή 뷰에 λŒ€ν•΄ UIGraphicsImageRendererλΌλŠ” UIKit κΈ°λŠ₯을 μ‚¬μš©ν•΄μ•Ό ν•΄μ„œ UIHostingControllerλ₯Ό μ‚¬μš©ν•˜κ²Œ λ˜μ—ˆλ‹€.

open class UIHostingController<Content> : UIViewController where Content : View


전체 μ½”λ“œ

//
//  RendererTestView.swift
//  test
//
//  Created by Seungui Moon on 10/9/23.
//

import SwiftUI

struct RendererTestView: View {
    @State var geoSize: CGSize = .init(width: 0, height: 0)
    @State var highresImage: UIImage = UIImage()
    @State var renderImage: UIImage?
    
    var body: some View {
        VStack {
            GeometryReader { geo in
                TargetImageView(cgSize: geo.size)
                    .onAppear {
                        self.geoSize = CGSize(width: geo.size.width, height: geo.size.height)
                    }
            }
            if let uiImage = renderImage {
                Image(uiImage: uiImage)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200,height: 200)
            }
            
            Button {
                renderImage = TargetImageView(cgSize: self.geoSize).asImage(size: self.geoSize)
                
            } label: {
                Text("generate image")
                    .frame(height: 200)
            }
        }
        .padding()
    }
}

#Preview {
    RendererTestView()
}

extension UIView {
    func asImage(size: CGSize) -> UIImage {
        let format = UIGraphicsImageRendererFormat()
        format.scale = 1
        return UIGraphicsImageRenderer(size: size, format: format).image { context in
            self.drawHierarchy(in: self.layer.bounds, afterScreenUpdates: true)
        }
    }
}

extension View {
    func asImage(size: CGSize) -> UIImage {
        let controller = UIHostingController(rootView: self)
        controller.view.bounds = CGRect(origin: .zero, size: size)
        let image = controller.view.asImage(size: size)
        return image
    }
}

struct TargetImageView: View {
    @State var cgSize: CGSize
    
    var body: some View {
        ZStack{
            Color.white
            VStack {
                Text("HELLO\nWORLD")
                    .font(.title)
                    .multilineTextAlignment(.center)
            }
            .padding(40)
            .background(.purple)
            .foregroundColor(.white)
            .shadow(color: .purple, radius: 10)
        }
    }
}

Clone this wiki locally