Convert SwiftUI View to NSImage
View+Image.swift
import SwiftUI
extension View {
func renderAsImage() -> NSImage? {
let view = NoInsetHostingView(rootView: self)
view.setFrameSize(view.fittingSize)
return view.bitmapImage()
}
}
NoInsetHostingView.swift
import SwiftUI
class NoInsetHostingView<V>: NSHostingView<V> where V: View {
override var safeAreaInsets: NSEdgeInsets {
return .init()
}
}
NSView+Image.swift
public extension NSView {
func bitmapImage() -> NSImage? {
guard let rep = bitmapImageRepForCachingDisplay(in: bounds) else {
return nil
}
cacheDisplay(in: bounds, to: rep)
guard let cgImage = rep.cgImage else {
return nil
}
return NSImage(cgImage: cgImage, size: bounds.size)
}
}
I'm not sure exactly why NoInsetHostingView
is needed, but with just a normal NSHostingView
the image has undesired insets, and this fixes it.
How can you turn a macOS SwiftUI view into an image?
In macOS same approach can be used. NSHostingController
is analog of UIHostingController
. Also to get it drawn the view should be added to some window:
extension View {
func snapshot() -> NSImage? {
let controller = NSHostingController(rootView: self)
let targetSize = controller.view.intrinsicContentSize
let contentRect = NSRect(origin: .zero, size: targetSize)
let window = NSWindow(
contentRect: contentRect,
styleMask: [.borderless],
backing: .buffered,
defer: false
)
window.contentView = controller.view
guard
let bitmapRep = controller.view.bitmapImageRepForCachingDisplay(in: contentRect)
else { return nil }
controller.view.cacheDisplay(in: contentRect, to: bitmapRep)
let image = NSImage(size: bitmapRep.size)
image.addRepresentation(bitmapRep)
return image
}
}
How to convert value of type 'ImagePickerView' to expected argument type 'String' on Swiftui?
Change #1:
Your model should usually be a struct
unless there's a really compelling reason to make it an ObservableObject
. In this case, struct
works very well:
struct MovieAdd: Identifiable {
var id = UUID()
var movieName = ""
var isComplete : Bool = false
var movieImage : UIImage
}
Note that I've made movieImage
a UIImage
.
Change #2:
Use Image(uiImage:)
in MovieRow
. The MovieAdd
property no longer needs @ObservableObject
since it's just a struct
.
Also notice that types in Swift should be capitalized to follow convention).
struct MovieRow: View {
var movieAdd : MovieAdd
var body: some View {
VStack {
Image(uiImage: movieAdd.movieImage)
.resizable()
.frame(width: 100, height: 100)
Text(movieAdd.movieName)
}
}
}
Complete code in case I forgot to mention any other changes:
struct ContentView: View {
@State var movieAdd: [MovieAdd] = []
@State private var newMovieName: String = ""
@State private var showNewMovie = false
@State private var newMovieImage = UIImage()
var body: some View {
ZStack {
VStack {
HStack {
Text("Movies Watched Ratings")
.font(.system(size: 40, weight: .black, design: .rounded
))
Spacer()
Button(action: {
self.showNewMovie = true
}) {
Image(systemName: "plus.circle.fill")
.font(.largeTitle)
.foregroundColor(.yellow)
}
}
List{
ForEach(movieAdd) {movie in
MovieRow(movieAdd: movie)
}
}
}
if showNewMovie {
BlankView(bGColor: .black)
.opacity(0.5)
.onTapGesture {
self.showNewMovie = false
}
NewMovieView(isShow: $showNewMovie, addMovie: $movieAdd, newMovieName: newMovieName)
.transition(.move(edge: .bottom))
.animation(.interpolatingSpring(stiffness: 200.0, damping: 25.0, initialVelocity: 10.0))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct MovieRow: View {
var movieAdd : MovieAdd
var body: some View {
VStack {
Image(uiImage: movieAdd.movieImage)
.resizable()
.frame(width: 100, height: 100)
Text(movieAdd.movieName)
}
}
}
struct BlankView: View {
var bGColor: Color
var body: some View {
VStack {
Spacer()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(bGColor)
.edgesIgnoringSafeArea(.all)
}
}
struct MovieAdd: Identifiable {
var id = UUID()
var movieName = ""
var isComplete : Bool = false
var movieImage : UIImage
}
struct NewMovieView: View {
@Binding var isShow: Bool
@Binding var addMovie: [MovieAdd]
@State var newMovieName: String = ""
@State var isShowingImagePicker = false
@State var imageInBlackBox = UIImage()
var body: some View {
ScrollView {
VStack {
VStack (alignment: .leading) {
HStack {
Text("Add a New Movie")
.font(.system(.title, design: .rounded))
.bold()
}
ZStack {
VStack {
HStack (alignment: .center){
Spacer()
Image(uiImage: imageInBlackBox)
.resizable()
.scaledToFill()
.frame(width: 200, height: 200)
.border(Color.black, width: 3)
.clipped()
Spacer()
}
VStack {
Spacer()
Button(action: {
self.isShowingImagePicker.toggle()
}, label: {
Text("Select Image")
.font(.system(size: 15))
})
.sheet(isPresented: $isShowingImagePicker, content: { ImagePickerView(isPresented: $isShowingImagePicker, selectedImage: $imageInBlackBox)})
}
}
}
Group {
TextField("Enter the movie name", text: $newMovieName)
.padding()
.background(Color(.systemGray6))
}
Button(action: {
if self.newMovieName.trimmingCharacters(in: .whitespaces) == "" {
return
}
if self.isShowingImagePicker {
return
}
self.isShow = false
self.addMovieTask(movieName: self.newMovieName, movieImage: ImagePickerView(isPresented: $isShowingImagePicker, selectedImage: $imageInBlackBox))
}) {
Text("Save")
.font(.system(.headline, design: .rounded))
.foregroundColor(.red)
}
}
}
.background(Color.white)
}
}
private func addMovieTask(movieName: String, isComplete: Bool = false, movieImage: ImagePickerView) {
let task = MovieAdd(movieName: movieName, movieImage: movieImage.selectedImage)
addMovie.append(task)
}
}
struct NewMovieView_Previews: PreviewProvider {
static var previews: some View {
NewMovieView(isShow: .constant(true), addMovie: .constant([]), newMovieName: "", isShowingImagePicker: true)
}
}
struct ImagePickerView: UIViewControllerRepresentable {
@Binding var isPresented: Bool
@Binding var selectedImage: UIImage
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePickerView>) -> some UIViewController {
let controller = UIImagePickerController()
controller.delegate = context.coordinator
return controller
}
func makeCoordinator() -> ImagePickerView.Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePickerView
init(parent: ImagePickerView){
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let selectedImage = info[.originalImage] as? UIImage {
print(selectedImage)
self.parent.selectedImage = selectedImage
}
self.parent.isPresented = false
}
}
func updateUIViewController(_ uiViewController: ImagePickerView.UIViewControllerType, context: UIViewControllerRepresentableContext<ImagePickerView>) {
//
}
}
SwiftUI - onDrag Argument type 'NSImage' does not conform to expected type 'NSItemProviderWriting'
The following works as Drag&Drop from testing SwiftUI app to TextEdit. Testing image image is stored in Assets.xcassets
Image("image")
.onDrag {
NSItemProvider(item: NSImage(named: "image")?.tiffRepresentation as NSSecureCoding?,
typeIdentifier: kUTTypeTIFF as String)
}
Convert UIImage to NSData and convert back to UIImage in Swift?
UIImage(data:imageData,scale:1.0)
presuming the image's scale is 1.
In swift 4.2, use below code for get Data().
image.pngData()
Related Topics
How to Add "%" to Data in iOS-Chart
How to Zip More Than 4 Publishers
Swift Tableview Cell Set Accessory Type
Swiftui: Dismiss View Within MACos Navigationview
Swift: How to Disable User Interaction While Touch Action Is Being Carried Out
Swift: Force Show Navigation Bar in Modal
Mkmapview Not Clustering Annotation on Zooming Out Map in Swift
An Nsmanagedobject of Class 'Classname' Must Have a Valid Nsentitydescription.' Error
How Does Optional Covariance Work in Swift
Nsuserdefaults in Swift - Implementing Type Safety
Convert Swiftui View to Nsimage
Window Visible on All Spaces (Including Other Fullscreen Apps)
How to Set Title of Navigation Bar in Swift
Resetting Zone Allocator with Allocations Still Alive