Reputation: 11
I am creating a SwiftUI app with PencilKit
& UIViewRepresentable
. I am adding a image coming from server on top of the PKCanvasView
. Code works fine when I click on a image and open a canvas view then close it and reopen it. But I am not able load a next image by having a next button on same screen to load a next image in array as a foreground of a CanvasView (like a image carousel). Since makeUIView in UIViewRepresentable loads only once, unless I close the view and reopen, canvas view does not update with next image when I am clicking on next button.
I tried sending @Binding
Data or by accessing a @Published
data from Environment Object to a UIViewRepresentable
struct.
I tried adding edit mode on and off button and updating a view through updateUIView function and it works fine. But I am not able to update the next image in array. I am sure I must be doing something stupid
I am fairly new in SwiftUI, please call out any stupid code here.
struct HomeView: View {
// MARK: - Properties
@EnvironmentObject var viewModel: ViewModels.ImageDataViewModel
@Environment(\.dismiss) var dismiss
@Binding var name: String
@State var editable: Bool = true
var body: some View {
// currentImageData is a @Published property coming from viewmodel
if let currentSelectedImageData = viewModel.currentImageData {
VStack {
// Display image number text, pause/UnPause editing and a Next button to go to next image
HStack {
Text("Image Number: \(currentSelectedImageData.number)")
.font(.largeTitle)
.foregroundColor(.white)
.frame(height: 100)
.frame(maxWidth: .infinity)
Button {
editable.toggle()
} label: {
Image(systemName: "pencil")
.resizable()
.frame(width: 40, height: 40)
.padding()
}
// Next button will perform Save currently edited image, remove it from array and set next image // as currentSelectedImage to show next image.
Button {
DispatchQueue.main.async {
viewModel.saveImage()
viewModel.removeEditedImage(imageId: currentSelectedImageData.id)
}
} label: {
Image(systemName: "arrow.right.circle.fill")
.resizable()
.frame(width: 40, height: 40)
.padding()
}
}
// Canvas to load PKCanvas and image as a foreground
Canvas(editable: $editable)
.environmentObject(viewModel)
.frame(maxHeight: .infinity) }
} else {
// When all the images are finished from stack.
Text("There are No images left to edit")
}
}
}
struct Canvas: View {
@EnvironmentObject var viewModel: ViewModels.ImageDataViewModel
@Binding var editable: Bool
var body: some View {
VStack {
// Drawing View
GeometryReader { proxy -> AnyView in
let size = proxy.frame(in: .global).size
DispatchQueue.main.async {
if viewModel.rect == .zero {
viewModel.rect = proxy.frame(in: .global)
}
}
return AnyView(
// Pencil kit drawing view
DrawingView(canvas: $viewModel.canvas, imageData: $viewModel.imageData, editable: $editable, toolPicker: $viewModel.toolPicker, rect: size)
.environmentObject($viewModel)
)
}
}
}
}
struct DrawingView: UIViewRepresentable {
@Binding var canvas: PKCanvasView
@Binding var imageData: Data
@Binding var editable: Bool
@Binding var toolPicker: PKToolPicker
// View size
var rect: CGSize
func makeUIView(context: Context) -> PKCanvasView {
canvas.drawingPolicy = .anyInput
canvas.backgroundColor = .clear
canvas.isOpaque = false
print("Make canvas with image")
// append image in canvas subview
if let image = UIImage(data: imageData) {
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: 0, y: 0, width: rect.width, height: rect.height)
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
// Setting image to the back of the canvas
let subView = canvas.subviews[0]
subView.addSubview(imageView)
subView.sendSubviewToBack(imageView)
// Show tool picker
// Add tool picker visible and first responder
toolPicker.setVisible(true, forFirstResponder: canvas)
toolPicker.addObserver(canvas)
canvas.becomeFirstResponder()
}
return canvas
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {
print("Update canvas")
uiView.drawingPolicy = editable ? .anyInput : .pencilOnly
uiView.isUserInteractionEnabled = editable
toolPicker.setVisible(editable, forFirstResponder: uiView)
toolPicker.addObserver(uiView)
}
}
Upvotes: 1
Views: 909
Reputation: 21
I am facing a similar problem.. I use almost the same DrawingView: UIViewRepresentable and my issue is that when the image/photo that is loaded is taken in portrait mode with the camera, the DrawingView will rotate the image to the right. I found out how to rotate the UIIMage but the view will not refresh unless I close and open again... In my understanding the updateUIView understands changes in the canvas but not in the subview..
Update: Ok I solved it. As Swapster mentioned, I replaced the image in the UIIViewupdate method. Just a note for future adventurers, I used the below to remove the existing subView
canvas.subviews[0].subviews[0].removeFromSuperview()
Upvotes: 0
Reputation: 30746
There are a few mistakes
DispatchQueue.main.async
.makeUIView
needs to init and return the UIView, it should not come from somewhere else or be stored in a var in the struct.updateUIView
needs to update the UIView
only with things that have changed since the last time update was called.Upvotes: 0