-
SwiftUI에서 화면 캡쳐하기(with UIKit / UIGraphicsImageRenderer)Swift 2023. 10. 11. 14:23
SwiftUI에서 화면 캡쳐하기
최근에 하는 프로젝트에서 image를 만들어서 instagram으로 공유하는 기능을 구현해야 했다. 먼저는 화면의 이미지를 image로 렌더해야 하는 기능을 구현해야 해서 UIGraphicsImageRenderer를 활용한 캡쳐 기능을 구현하였다.
구현 순서
1. Target으로하는 View를 구현한다.
- 해당 뷰를 그대로 이미지로 만들기 때문에 따로 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() } }
UIGraphicsImageRenderer
[iOS] - UIGraphicsImageRenderer
자세히 - Apple documentCore Graphics 지원 이미지를 만들기 위한 그래픽 렌더러입니다.애플문서에는 다음과 같이 설명되어 있는데UIGraphicsImageRenderer 이니까 UIKit과 관련 있는 클래스 인것 같은데 Core Gra
velog.io
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
UIHostingController | Apple Developer Documentation
A UIKit view controller that manages a SwiftUI view hierarchy.
developer.apple.com
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) } } }
구현 화면
'Swift' 카테고리의 다른 글
[Swift] 값타입의 Heap Allocation(Heap boxing)은 언제 일어날까? (0) 2025.03.06 [Swift] Error Handling 마스터하기(do-catch, throw-try) (1) 2024.01.25 [Swift] Codable 개념 확실히 이해하기 💬 (0) 2023.10.06 [Swift] defer는 무엇인가 🤔 (0) 2023.10.03 [Swift] \. 는 무엇인가 (KeyPath 정리) (0) 2023.03.23