Last active
November 19, 2025 11:46
-
-
Save clarkezone/68eb3ee13b5607782ceb2e20cece4ab3 to your computer and use it in GitHub Desktop.
Basic SwiftUI wrapper for PaperKit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // ContentView.swift | |
| // PaperKitPoCiOS | |
| // | |
| // Created by James Clarke on 7/15/25. | |
| // | |
| import SwiftUI | |
| struct ContentView: View { | |
| @State private var coordinator = PaperMarkupCoordinator() | |
| @State private var isEditMode = true | |
| var body: some View { | |
| PaperMarkupView( | |
| canvasSize: CGSize(width: 400, height: 800), | |
| isEditable: isEditMode, | |
| coordinator: coordinator | |
| ) | |
| .frame(maxWidth: .infinity, maxHeight: .infinity) | |
| .toolbar { | |
| ToolbarItemGroup(placement: .navigationBarTrailing) { | |
| Button(isEditMode ? "View" : "Edit") { | |
| isEditMode.toggle() | |
| } | |
| Button("Clear") { | |
| coordinator.clear() | |
| } | |
| Button("Undo") { | |
| coordinator.undo() | |
| } | |
| } | |
| } | |
| .navigationTitle("Markup Canvas") | |
| .navigationBarTitleDisplayMode(.inline) | |
| } | |
| } | |
| #Preview { | |
| ContentView() | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // PaperMarkupView.swift | |
| // PaperKitPoCiOS | |
| // | |
| // Created by James Clarke and Claude Code on 8/3/25. | |
| // | |
| // Description: | |
| // This file defines the SwiftUI view for a PaperKit-based markup canvas. | |
| // It uses a UIViewControllerRepresentable to wrap the PaperMarkupViewController | |
| // from the PaperKit framework, allowing it to be used within a SwiftUI application. | |
| // | |
| // The implementation includes: | |
| // - A coordinator to manage communication between SwiftUI and UIKit. | |
| // - A wrapper view controller (`PaperMarkupWrapperViewController`) to host and | |
| // configure the PaperKit canvas, PencilKit tool picker, and data persistence. | |
| // - Automatic saving and loading of markup data to the Application Support directory. | |
| // | |
| import SwiftUI | |
| import PaperKit | |
| import PencilKit | |
| import UIKit | |
| // MARK: - PaperMarkupView | |
| /// | |
| /// A SwiftUI view that wraps the `PaperMarkupWrapperViewController` to provide a | |
| /// PaperKit drawing canvas. | |
| /// | |
| /// ## SwiftUI-UIKit Bridge Pattern | |
| /// This struct conforms to `UIViewControllerRepresentable`, which is SwiftUI's | |
| /// standard protocol for integrating UIKit view controllers into SwiftUI views. | |
| /// Think of it as a "translator" that lets SwiftUI communicate with UIKit components. | |
| /// | |
| /// ## Why This Pattern Is Needed | |
| /// PaperKit is a UIKit-based framework, but we want to use it in a SwiftUI app. | |
| /// This bridge pattern allows us to: | |
| /// - Wrap UIKit components for use in SwiftUI | |
| /// - Handle the lifecycle differences between SwiftUI and UIKit | |
| /// - Maintain proper state synchronization between the two frameworks | |
| /// | |
| struct PaperMarkupView: UIViewControllerRepresentable { | |
| let canvasSize: CGSize | |
| let isEditable: Bool | |
| let coordinator: PaperMarkupCoordinator | |
| /// Initializes the SwiftUI view for the markup canvas. | |
| /// - Parameters: | |
| /// - canvasSize: The intrinsic size of the drawing canvas. | |
| /// - isEditable: A flag to determine if the canvas is interactive. | |
| /// - coordinator: The coordinator object to manage state and actions. | |
| init(canvasSize: CGSize = CGSize(width: 400, height: 800), isEditable: Bool = true, coordinator: PaperMarkupCoordinator) { | |
| self.canvasSize = canvasSize | |
| self.isEditable = isEditable | |
| self.coordinator = coordinator | |
| } | |
| /// **Required SwiftUI Protocol Method** | |
| /// Creates the coordinator object that manages communication with the view controller. | |
| /// | |
| /// **When Called**: SwiftUI calls this method ONCE when the view is first created. | |
| /// **Purpose**: The coordinator acts as a delegate/communication bridge between | |
| /// SwiftUI (declarative) and UIKit (imperative) worlds. | |
| /// | |
| /// **Why Needed**: SwiftUI views are structs (value types) that get recreated, | |
| /// but we need a persistent object (reference type) to maintain state and handle | |
| /// callbacks from the UIKit side. | |
| func makeCoordinator() -> PaperMarkupCoordinator { | |
| coordinator | |
| } | |
| /// **Required SwiftUI Protocol Method** | |
| /// Creates and configures the `PaperMarkupWrapperViewController` instance. | |
| /// | |
| /// **When Called**: SwiftUI calls this method ONCE when the view first appears. | |
| /// **Purpose**: This is where we create the actual UIKit view controller that | |
| /// will be embedded in our SwiftUI view hierarchy. | |
| /// | |
| /// **Important**: This method should only create and configure - never update. | |
| /// Updates are handled by `updateUIViewController(_:context:)`. | |
| func makeUIViewController(context: Context) -> PaperMarkupWrapperViewController { | |
| let wrapperViewController = PaperMarkupWrapperViewController( | |
| canvasSize: canvasSize, | |
| isEditable: isEditable | |
| ) | |
| // Link the coordinator to the newly created view controller. | |
| context.coordinator.setWrapperViewController(wrapperViewController) | |
| return wrapperViewController | |
| } | |
| /// **Required SwiftUI Protocol Method** | |
| /// Updates the `PaperMarkupWrapperViewController` when the SwiftUI view's state changes. | |
| /// | |
| /// **When Called**: SwiftUI calls this method whenever any `@State`, `@Binding`, | |
| /// or other observed property changes (like `isEditable` in our case). | |
| /// | |
| /// **Purpose**: This is SwiftUI's way of keeping the UIKit component in sync | |
| /// with SwiftUI's declarative state. When SwiftUI re-evaluates our view due to | |
| /// state changes, it calls this method to "push" those changes to UIKit. | |
| /// | |
| /// **Performance Note**: This method should be lightweight since it can be | |
| /// called frequently during SwiftUI's update cycles. | |
| func updateUIViewController(_ uiViewController: PaperMarkupWrapperViewController, context: Context) { | |
| uiViewController.setCanvasEditable(isEditable) | |
| } | |
| } | |
| // MARK: - PaperMarkupWrapperViewController | |
| /// | |
| /// A UIKit view controller that wraps and manages PaperKit's `PaperMarkupViewController`. | |
| /// | |
| /// ## Purpose | |
| /// This wrapper provides a clean interface between the SwiftUI representable view | |
| /// and PaperKit's UIKit-based components. It handles all the UIKit-specific setup | |
| /// and lifecycle management that PaperKit requires. | |
| /// | |
| /// ## Responsibilities | |
| /// 1. **PaperKit Integration**: Hosts `PaperMarkupViewController` as a child view controller | |
| /// 2. **Tool Management**: Sets up and manages `PKToolPicker` for drawing tools | |
| /// 3. **Data Persistence**: Automatically saves/loads drawings to Application Support directory | |
| /// 4. **Delegate Handling**: Responds to PaperKit callbacks for drawing events | |
| /// 5. **State Management**: Manages editable state and canvas clearing | |
| /// | |
| /// ## Architecture & User Interaction Flow | |
| /// ``` | |
| /// SwiftUI PaperMarkupView (SwiftUI declarative layer) | |
| /// ↓ (via UIViewControllerRepresentable protocol) | |
| /// PaperMarkupWrapperViewController (UIKit bridge layer) | |
| /// ↓ (child view controller pattern) | |
| /// PaperKit's PaperMarkupViewController (Apple's drawing framework) | |
| /// ``` | |
| /// | |
| /// ## Complete User Interaction Flow | |
| /// ``` | |
| /// 1. User taps "Clear" button in SwiftUI | |
| /// ↓ | |
| /// 2. ContentView calls coordinator.clear() | |
| /// ↓ | |
| /// 3. Coordinator calls wrapperViewController.clearCanvas() | |
| /// ↓ | |
| /// 4. WrapperViewController creates new empty PaperMarkup | |
| /// ↓ | |
| /// 5. Updates PaperKit's PaperMarkupViewController with empty canvas | |
| /// | |
| /// OR | |
| /// | |
| /// 1. User draws on canvas with Apple Pencil | |
| /// ↓ | |
| /// 2. PaperKit detects drawing and calls delegate method | |
| /// ↓ | |
| /// 3. paperMarkupViewControllerDidChangeMarkup() fired automatically | |
| /// ↓ | |
| /// 4. Auto-save triggered in background using modern async/await | |
| /// ↓ | |
| /// 5. Drawing saved to ~/Library/Application Support/[Bundle ID]/markup.data | |
| /// ``` | |
| /// | |
| /// ## File Storage | |
| /// Drawings are automatically saved to: | |
| /// `[App Support]/[Bundle ID]/markup.data` | |
| /// | |
| class PaperMarkupWrapperViewController: UIViewController, PaperMarkupViewController.Delegate { | |
| // MARK: Properties | |
| private var paperViewController: PaperMarkupViewController! | |
| private var markupModel: PaperMarkup! | |
| private var toolPicker: PKToolPicker! | |
| private var isEditable: Bool | |
| private var canvasSize: CGSize | |
| /// **Weak Reference to Prevent Retain Cycles** | |
| /// A weak reference to the coordinator to communicate back to the SwiftUI layer. | |
| /// | |
| /// **Why Weak?** To prevent "retain cycles" - a memory leak where two objects | |
| /// hold strong references to each other and can never be deallocated. | |
| /// In our case: Coordinator → WrapperViewController → Coordinator would create a cycle. | |
| weak var coordinator: PaperMarkupCoordinator? | |
| // MARK: Initialization | |
| init(canvasSize: CGSize, isEditable: Bool) { | |
| self.isEditable = isEditable | |
| self.canvasSize = canvasSize | |
| super.init(nibName: nil, bundle: nil) | |
| initializeMarkupModel(with: canvasSize) | |
| } | |
| required init?(coder: NSCoder) { | |
| fatalError("init(coder:) has not been implemented") | |
| } | |
| // MARK: View Lifecycle | |
| override func viewDidLoad() { | |
| super.viewDidLoad() | |
| setupPaperMarkupViewController() | |
| setupToolPicker() | |
| } | |
| /// **First Responder Concept** | |
| /// The view controller can only become the "first responder" if it's editable. | |
| /// | |
| /// **What's a First Responder?** In iOS, the "first responder" is the object | |
| /// that receives user input events first (like keyboard input or tool picker actions). | |
| /// Only the first responder can display the PencilKit tool picker. | |
| /// | |
| /// **Why This Matters**: When `isEditable` is false, we don't want to show drawing | |
| /// tools, so we refuse to become first responder. | |
| override var canBecomeFirstResponder: Bool { | |
| return isEditable | |
| } | |
| // MARK: Setup Methods | |
| /// Initializes the `PaperMarkup` data model with the specified canvas size. | |
| private func initializeMarkupModel(with canvasSize: CGSize) { | |
| let bounds = CGRect(origin: .zero, size: canvasSize) | |
| markupModel = PaperMarkup(bounds: bounds) | |
| } | |
| /// Creates, configures, and embeds the `PaperMarkupViewController`. | |
| private func setupPaperMarkupViewController() { | |
| paperViewController = PaperMarkupViewController(markup: markupModel, supportedFeatureSet: .latest) | |
| // **Child View Controller Pattern** | |
| // This is a standard UIKit pattern for embedding one view controller inside another. | |
| // Steps: 1) Add the view to our view hierarchy, 2) Add as child controller, 3) Notify of completion | |
| view.addSubview(paperViewController.view) | |
| addChild(paperViewController) // Tells UIKit about the parent-child relationship | |
| paperViewController.didMove(toParent: self) // Lets the child know it's been added | |
| // Configure constraints to make the child view fill the parent. | |
| paperViewController.view.translatesAutoresizingMaskIntoConstraints = false | |
| NSLayoutConstraint.activate([ | |
| paperViewController.view.topAnchor.constraint(equalTo: view.topAnchor), | |
| paperViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), | |
| paperViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), | |
| paperViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) | |
| ]) | |
| // Set a reasonable zoom range. | |
| paperViewController.zoomRange = 0.1...3.0 | |
| // Attempt to load any previously saved markup data. | |
| loadMarkup() | |
| // Set the delegate to self to handle events like data changes. | |
| paperViewController.delegate = self | |
| } | |
| /// Initializes and configures the PencilKit `PKToolPicker`. | |
| private func setupToolPicker() { | |
| // **Become First Responder to Show Tool Picker** | |
| // This tells iOS "this view controller wants to handle user input" and enables | |
| // the PencilKit tool picker to be displayed for this canvas. | |
| becomeFirstResponder() | |
| toolPicker = PKToolPicker() | |
| toolPicker.addObserver(paperViewController) | |
| // **Modern PencilKit Tool Picker Management** | |
| // Apple's recommended approach (iOS 14+) for managing tool picker visibility. | |
| // This replaces older methods like `setVisible(_:forFirstResponder:)`. | |
| pencilKitResponderState.activeToolPicker = toolPicker | |
| pencilKitResponderState.toolPickerVisibility = .visible | |
| // Add an accessory button to the tool picker for additional actions. | |
| toolPicker.accessoryItem = UIBarButtonItem( | |
| barButtonSystemItem: .add, | |
| target: self, | |
| action: #selector(addOrEditMarkupButtonPressed(_:)) | |
| ) | |
| } | |
| // MARK: Public Control Methods | |
| /// Clears the canvas by replacing the current markup model with a new, empty one. | |
| func clearCanvas() { | |
| // Re-initialize the model with a new empty drawing. | |
| initializeMarkupModel(with: canvasSize) | |
| markupModel?.drawing = PKDrawing() | |
| // Update the view controller with the new, empty model. | |
| paperViewController.markup = markupModel | |
| } | |
| /// Trivial implementation of undo last action | |
| func undoLastAction() { | |
| paperViewController.undoManager?.undo() | |
| } | |
| /// Sets the editable state of the canvas and shows or hides the tool picker accordingly. | |
| func setCanvasEditable(_ editable: Bool) { | |
| isEditable = editable | |
| if editable { | |
| toolPicker.setVisible(true, forFirstResponder: paperViewController) | |
| becomeFirstResponder() | |
| } else { | |
| toolPicker.setVisible(false, forFirstResponder: paperViewController) | |
| resignFirstResponder() | |
| } | |
| } | |
| // MARK: Private Helpers & Actions | |
| /// Action method for the tool picker's accessory button. | |
| @objc private func addOrEditMarkupButtonPressed(_ button: UIBarButtonItem) { | |
| let markupEditViewController = MarkupEditViewController(supportedFeatureSet: .latest) | |
| markupEditViewController.delegate = paperViewController as? MarkupEditViewController.Delegate | |
| markupEditViewController.modalPresentationStyle = .popover | |
| markupEditViewController.popoverPresentationController?.barButtonItem = button | |
| present(markupEditViewController, animated: true) | |
| } | |
| } | |
| // MARK: - Data Persistence | |
| /// | |
| /// Extension handling all data loading and saving operations for the markup canvas. | |
| /// | |
| /// ## Storage Strategy | |
| /// | |
| /// - **Automatic Location**: Saves to Application Support directory (user can't see this folder) | |
| /// - **App Isolation**: Each app gets its own folder using Bundle ID to prevent conflicts | |
| /// - **Auto-Save**: Triggers immediately when user draws (no "Save" button needed) | |
| /// - **Auto-Load**: Restores previous drawing when app launches | |
| /// - **Crash Protection**: Uses atomic writes so files are never corrupted | |
| /// | |
| /// **File Path Example**: | |
| /// `~/Library/Application Support/com.yourcompany.PaperKitApp/markup.data` | |
| /// | |
| extension PaperMarkupWrapperViewController { | |
| /// Computed property for the file URL where the markup data is saved. | |
| /// The data is stored in the app's Application Support directory. | |
| private var markupDataFileURL: URL { | |
| let fileManager = FileManager.default | |
| // Use the app's bundle identifier to create a unique folder. | |
| let bundleID = Bundle.main.bundleIdentifier ?? "com.example.PaperKitApp" | |
| let appSupportURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! | |
| let appDirectory = appSupportURL.appendingPathComponent(bundleID) | |
| // Ensure the directory exists before attempting to write to it. | |
| try? fileManager.createDirectory(at: appDirectory, withIntermediateDirectories: true, attributes: nil) | |
| return appDirectory.appendingPathComponent("markup.data") | |
| } | |
| /// **Automatic Data Loading** | |
| /// Loads markup data from the file system. | |
| /// | |
| /// **When Called**: Automatically during view controller setup (setupPaperMarkupViewController) | |
| /// **What Happens**: If a previous drawing exists, it's loaded silently in the background | |
| /// **If No File**: Gracefully does nothing - user gets a blank canvas | |
| func loadMarkup() { | |
| guard FileManager.default.fileExists(atPath: markupDataFileURL.path) else { | |
| return // No saved data exists - user gets blank canvas (normal for first launch) | |
| } | |
| // We use `Task` to perform file I/O on a background thread, keeping the UI responsive. | |
| Task { | |
| do { | |
| // File loading happens on background thread (good for performance) | |
| let data = try Data(contentsOf: markupDataFileURL) | |
| let loadedMarkup = try PaperMarkup(dataRepresentation: data) | |
| // **MainActor Requirement** | |
| // UI updates MUST happen on the main thread. `await MainActor.run` ensures | |
| // this code runs on the main thread. | |
| await MainActor.run { | |
| self.paperViewController.markup = loadedMarkup | |
| self.markupModel = loadedMarkup | |
| } | |
| } catch { | |
| print("Failed to load markup data: \(error.localizedDescription)") | |
| } | |
| } | |
| } | |
| /// **Automatic Background Saving** | |
| /// Saves the current markup data to persistent storage. | |
| /// | |
| /// **When Called**: Automatically triggered by paperMarkupViewControllerDidChangeMarkup() | |
| /// whenever the user draws, erases, or modifies anything | |
| /// **Background Operation**: File I/O happens off the main thread for smooth UI performance | |
| /// **User Experience**: Completely invisible to user - no save dialogs or loading spinners | |
| private func saveMarkup(_ markup: PaperMarkup) { | |
| // **Modern Async/Await Pattern** | |
| // This replaces older completion handler patterns for cleaner, more readable code. | |
| Task { | |
| do { | |
| let data = try await markup.dataRepresentation() | |
| // `.atomic` option ensures file integrity - either the write succeeds completely | |
| // or fails completely (no partial/corrupted files) | |
| try data.write(to: markupDataFileURL, options: .atomic) | |
| } catch { | |
| print("Failed to save markup data: \(error.localizedDescription)") | |
| } | |
| } | |
| } | |
| } | |
| // MARK: - PaperKit Framework Callbacks (Delegate Methods) | |
| /// | |
| /// Extension implementing the PaperMarkupViewController delegate protocol. | |
| /// | |
| /// ## Important: These Are Automatic Framework Callbacks | |
| /// These methods are **automatically called by the PaperKit framework** when specific | |
| /// events occur. You don't call these methods directly - PaperKit calls them for you. | |
| /// | |
| /// ## The Auto-Save Flow | |
| /// ``` | |
| /// User draws on canvas | |
| /// ↓ | |
| /// PaperKit detects change | |
| /// ↓ | |
| /// PaperKit calls paperMarkupViewControllerDidChangeMarkup(_:) | |
| /// ↓ | |
| /// Our code automatically saves to disk | |
| /// ``` | |
| /// | |
| /// This creates a seamless auto-save experience without any manual save buttons. | |
| /// | |
| extension PaperMarkupWrapperViewController { | |
| /// **PaperKit Framework Callback** | |
| /// Called automatically by PaperKit whenever the user modifies the drawing on the canvas. | |
| /// | |
| /// **When This Fires**: Every time the user: | |
| /// - Draws a new stroke with Apple Pencil or finger | |
| /// - Erases content | |
| /// - Adds or modifies text/shapes | |
| /// - Performs any markup operation | |
| /// | |
| /// **What We Do**: Immediately trigger automatic background saving to ensure | |
| /// no user work is ever lost, even if the app crashes or is terminated. | |
| func paperMarkupViewControllerDidChangeMarkup(_ paperMarkupViewController: PaperMarkupViewController) { | |
| guard let currentMarkup = paperMarkupViewController.markup else { return } | |
| saveMarkup(currentMarkup) | |
| } | |
| /// **PaperKit Framework Callback** | |
| /// Called automatically when the user selects or deselects markup elements. | |
| /// | |
| /// **When This Fires**: When the user taps to select text, shapes, or drawings | |
| /// for editing, moving, or deleting. | |
| /// | |
| /// **Current Implementation**: Empty - we don't need custom selection handling, | |
| /// but PaperKit requires this method to be implemented. | |
| func paperMarkupViewControllerDidChangeSelection(_ paperMarkupViewController: PaperMarkupViewController) {} | |
| /// **PaperKit Framework Callback** | |
| /// Called automatically when the user begins a new drawing stroke. | |
| /// | |
| /// **When This Fires**: The moment the user touches the screen and starts | |
| /// drawing with Apple Pencil, finger, or any drawing tool. | |
| /// | |
| /// **Current Implementation**: Empty - we don't need custom drawing start handling, | |
| /// but PaperKit requires this method to be implemented. | |
| func paperMarkupViewControllerDidBeginDrawing(_ paperMarkupViewController: PaperMarkupViewController) {} | |
| /// **PaperKit Framework Callback** | |
| /// Called automatically when the visible portion of the canvas changes. | |
| /// | |
| /// **When This Fires**: When the user: | |
| /// - Pinches to zoom in/out | |
| /// - Scrolls/pans around the canvas | |
| /// - Rotates the device (if orientation changes are supported) | |
| /// | |
| /// **Current Implementation**: Empty - we don't need custom viewport handling, | |
| /// but PaperKit requires this method to be implemented. | |
| func paperMarkupViewControllerDidChangeContentVisibleFrame(_ paperMarkupViewController: PaperMarkupViewController) {} | |
| } | |
| // MARK: - PaperMarkupCoordinator | |
| /// | |
| /// An observable object that acts as a coordinator between the `PaperMarkupView` (SwiftUI) | |
| /// and the `PaperMarkupWrapperViewController` (UIKit). | |
| /// | |
| /// ## Why We Need This Coordinator | |
| /// SwiftUI is **declarative** ("what should be displayed") while UIKit is **imperative** | |
| /// ("do this action now"). The coordinator bridges this gap by: | |
| /// | |
| /// 1. **Action Translation**: Converts SwiftUI button taps into UIKit method calls | |
| /// 2. **State Management**: Maintains persistent references across SwiftUI view recreations | |
| /// 3. **Communication Bridge**: Allows SwiftUI to trigger actions on UIKit components | |
| /// | |
| /// ## Example Usage Flow | |
| /// ``` | |
| /// SwiftUI Button("Clear") { coordinator.clear() } | |
| /// ↓ | |
| /// coordinator.clear() calls wrapperViewController?.clearCanvas() | |
| /// ↓ | |
| /// UIKit canvas is cleared immediately | |
| /// ``` | |
| /// | |
| @Observable | |
| class PaperMarkupCoordinator { | |
| // A weak reference to the wrapper view controller to avoid retain cycles. | |
| private weak var wrapperViewController: PaperMarkupWrapperViewController? | |
| /// Connects this coordinator with its corresponding `PaperMarkupWrapperViewController`. | |
| /// This method is called during the view controller's creation process. | |
| /// - Parameter controller: The `PaperMarkupWrapperViewController` instance to coordinate with. | |
| func setWrapperViewController(_ controller: PaperMarkupWrapperViewController) { | |
| self.wrapperViewController = controller | |
| controller.coordinator = self // Establish the back-reference. | |
| } | |
| /// Clears all drawings and content from the markup canvas. | |
| func clear() { | |
| wrapperViewController?.clearCanvas() | |
| } | |
| /// Trivial implementation of undo | |
| func undo() { | |
| wrapperViewController?.undoLastAction() | |
| } | |
| /// Toggles the user's ability to draw on the canvas. | |
| /// - Parameter isEditable: A boolean indicating whether the canvas should be editable. | |
| func setEditable(_ isEditable: Bool) { | |
| wrapperViewController?.setCanvasEditable(isEditable) | |
| } | |
| } | |
| // MARK: - SwiftUI Preview | |
| #Preview { | |
| NavigationView { | |
| PaperMarkupView(coordinator: PaperMarkupCoordinator()) | |
| .edgesIgnoringSafeArea(.bottom) | |
| .navigationTitle("Markup Canvas") | |
| .navigationBarTitleDisplayMode(.inline) | |
| } | |
| } |
Can easily trigger a crash
Looks like .drawing property is now deprecated (PaperMarkupView.swift, line 286). Per: https://developer.apple.com/forums/thread/798151
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
this doesnt seem to work anymoreonly crashes in simulator, seems to be a missing simulator capability with the render pipeline. It works on a physical device