I’m engaged on a SwiftUI challenge the place I’m making an attempt to implement interactive Sticky Notes that may be moved, resized, and rotated on a Canvas-like view. The Sticky Notes must also have editable textual content fields. Nevertheless, I’ve encountered an issue the place:
-
The place of the Sticky Notes doesn’t align with the seen notice on the display.
-
When the Sticky Word is moved or resized, the interactive body (used for context menus or gestures) doesn’t observe the notice.
-
The rotation and scaling transformations don’t apply constantly to the textual content or body.
I’ve tried debugging the problem by inspecting the frames and positions in each native and international coordinate areas, and it looks like the worldwide offset of the Canvas is perhaps interfering with the Sticky Notes’ alignment.
I’ve my Code under:
struct StickyNoteView: View {
@Binding var stickyNote: StickyNote
@State personal var isEditing: Bool = false
var onDelete: () -> Void
var onDuplicate: () -> Void
var physique: some View {
InteractiveElementView(aspect: $stickyNote) {
ZStack {
Rectangle()
.fill(stickyNote.shade.toColor())
SelectableTextEditor(textual content: $stickyNote.textual content, isEditing: $isEditing)
.padding()
.background(Colour.clear)
}
.highPriorityGesture(
TapGesture(rely: 2).onEnded {
isEditing = true
}
)
}
.contextMenu {
Button("Löschen") {
onDelete()
}
Button("Duplizieren") {
onDuplicate()
}
}
}
}
import PencilKit
struct NotesDetailView: View {
@Surroundings(.presentationMode) var presentationMode
@Surroundings(NotesViewModel.self) personal var viewModel
let noteIndex: Int
@State personal var canvasView = PKCanvasView()
@State personal var toolPicker = PKToolPicker()
@State personal var isShowingImagePicker = false
@State personal var isInteractingWithElement = false
var physique: some View {
@Bindable var viewModel = viewModel
VStack {
GeometryReader { geometry in
ZStack {
CanvasRepresentable(
canvasView: $canvasView,
toolPicker: $toolPicker,
notice: $viewModel.notes[noteIndex],
isInteractingWithElement: $isInteractingWithElement
)
.disabled(isInteractingWithElement) // Disable Canvas when interacting with StickyNotes
.body(width: geometry.dimension.width, peak: geometry.dimension.peak)
.onAppear {
toolPicker.addObserver(canvasView)
toolPicker.setVisible(true, forFirstResponder: canvasView)
canvasView.becomeFirstResponder()
}
.onDisappear {
saveDrawing()
}
// Overlay StickyNotes
ForEach($viewModel.notes[noteIndex].stickyNotes) { $stickyNote in
StickyNoteView(
stickyNote: $stickyNote,
onDelete: {
if let index = viewModel.notes[noteIndex].stickyNotes.firstIndex(the place: { $0.id == stickyNote.id }) {
viewModel.notes[noteIndex].stickyNotes.take away(at: index)
}
},
onDuplicate: {
let newStickyNote = stickyNote.duplicate()
viewModel.notes[noteIndex].stickyNotes.append(newStickyNote)
}
)
}
// Overlay NoteImages
ForEach($viewModel.notes[noteIndex].photographs) { $noteImage in
NoteImageView(
noteImage: $noteImage,
onDelete: {
if let index = viewModel.notes[noteIndex].photographs.firstIndex(the place: { $0.id == noteImage.id }) {
viewModel.notes[noteIndex].photographs.take away(at: index)
}
},
onDuplicate: {
let newImage = noteImage.duplicate()
viewModel.notes[noteIndex].photographs.append(newImage)
}
)
}
}
}
}
.navigationBarBackButtonHidden(true)
.toolbar {
// Linker Button: Zurück zur Notizen-Übersicht + Titel
ToolbarItem(placement: .navigationBarLeading) {
HStack {
Button(motion: {
presentationMode.wrappedValue.dismiss()
}) {
Picture(systemName: "chevron.left")
.font(.title2)
}
Textual content(viewModel.notes[noteIndex].title)
.font(.headline)
.lineLimit(1)
.truncationMode(.tail)
}
}
// Mittiges Menü
ToolbarItem(placement: .principal) {
NotesMenu(
isShowingImagePicker: $isShowingImagePicker,
notice: $viewModel.notes[noteIndex]
)
}
// Rechter Button: Fertig
ToolbarItem(placement: .navigationBarTrailing) {
Button("Fertig") {
presentationMode.wrappedValue.dismiss()
}
.font(.title2)
}
}
}
personal func saveDrawing() {
// Zeichnung speichern, wenn die Ansicht geschlossen wird
let newDrawingData = canvasView.drawing.dataRepresentation()
if newDrawingData != viewModel.notes[noteIndex].drawingData {
viewModel.notes[noteIndex].drawingData = newDrawingData
}
}
}
import PencilKit
struct CanvasRepresentable: UIViewRepresentable {
@Binding var canvasView: PKCanvasView
@Binding var toolPicker: PKToolPicker
@Binding var notice: Word
@Binding var isInteractingWithElement: Bool
func makeUIView(context: Context) -> PKCanvasView {
updateBackground(for: canvasView)
canvasView.drawingPolicy = .anyInput
canvasView.delegate = context.coordinator
// Zeichnung laden, falls vorhanden
if let knowledge = notice.drawingData, let drawing = attempt? PKDrawing(knowledge: knowledge) {
canvasView.drawing = drawing
}
toolPicker.addObserver(canvasView)
toolPicker.setVisible(true, forFirstResponder: canvasView)
canvasView.becomeFirstResponder()
return canvasView
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {
uiView.isUserInteractionEnabled = !isInteractingWithElement
if let knowledge = notice.drawingData, let newDrawing = attempt? PKDrawing(knowledge: knowledge) {
if newDrawing != uiView.drawing {
uiView.drawing = newDrawing
}
}
updateBackground(for: uiView)
}
personal func updateBackground(for canvasView: PKCanvasView) {
change notice.background {
case .none:
canvasView.backgroundColor = UIColor.systemBackground
case .grid:
canvasView.backgroundColor = UIColor(patternImage: generateGridBackground())
case .dotted:
canvasView.backgroundColor = UIColor(patternImage: generateDottedBackground())
case .lined:
canvasView.backgroundColor = UIColor(patternImage: generateLinedBackground())
}
}
// Dynamisch generierte Hintergründe
personal func generateGridBackground() -> UIImage {
let renderer = UIGraphicsImageRenderer(dimension: CGSize(width: 30, peak: 30))
return renderer.picture { context in
let path = UIBezierPath()
path.transfer(to: CGPoint(x: 0, y: 15))
path.addLine(to: CGPoint(x: 30, y: 15))
path.transfer(to: CGPoint(x: 15, y: 0))
path.addLine(to: CGPoint(x: 15, y: 30))
UIColor.lightGray.setStroke()
path.lineWidth = 0.5
path.stroke()
}
}
personal func generateDottedBackground() -> UIImage {
let renderer = UIGraphicsImageRenderer(dimension: CGSize(width: 10, peak: 10))
return renderer.picture { context in
let path = UIBezierPath(ovalIn: CGRect(x: 4, y: 4, width: 2, peak: 2))
UIColor.systemGray.setFill()
path.fill()
}
}
personal func generateLinedBackground() -> UIImage {
let renderer = UIGraphicsImageRenderer(dimension: CGSize(width: 30, peak: 30))
return renderer.picture { context in
let path = UIBezierPath()
path.transfer(to: CGPoint(x: 0, y: 15))
path.addLine(to: CGPoint(x: 30, y: 15))
UIColor.lightGray.setStroke()
path.lineWidth = 0.5
path.stroke()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, PKCanvasViewDelegate {
var father or mother: CanvasRepresentable
init(_ father or mother: CanvasRepresentable) {
self.father or mother = father or mother
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
let newDrawingData = canvasView.drawing.dataRepresentation()
if newDrawingData != father or mother.notice.drawingData {
DispatchQueue.fundamental.async {
self.father or mother.notice.drawingData = newDrawingData
}
}
}
}
}
Any recommendation on easy methods to resolve this is able to be significantly appreciated!