// // ComposeDrawingViewController.swift // Tusker // // Created by Shadowfacts on 5/7/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import UIKit import PencilKit protocol ComposeDrawingViewControllerDelegate: class { func composeDrawingViewControllerClose(_ drawingController: ComposeDrawingViewController) func composeDrawingViewController(_ drawingController: ComposeDrawingViewController, saveDrawing drawing: PKDrawing) } class ComposeDrawingViewController: UIViewController { weak var delegate: ComposeDrawingViewControllerDelegate? private(set) var canvasView: PKCanvasView! private(set) var cancelBarButtonItem: UIBarButtonItem! private(set) var undoBarButtonItem: UIBarButtonItem! private(set) var redoBarButtonItem: UIBarButtonItem! private var initialDrawing: PKDrawing? init() { super.init(nibName: nil, bundle: nil) } convenience init(editing initialDrawing: PKDrawing) { self.init() self.initialDrawing = initialDrawing } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() overrideUserInterfaceStyle = .light navigationItem.title = NSLocalizedString("Draw", comment: "compose drawing screen title") cancelBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelPressed)) undoBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "arrow.uturn.left.circle"), style: .plain, target: self, action: #selector(undoPressed)) undoBarButtonItem.isEnabled = false redoBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "arrow.uturn.right.circle"), style: .plain, target: self, action: #selector(redoPressed)) redoBarButtonItem.isEnabled = false navigationItem.leftBarButtonItems = [cancelBarButtonItem] navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(savePressed)) canvasView = PKCanvasView() if let initialDrawing = initialDrawing { canvasView.drawing = initialDrawing } canvasView.delegate = self canvasView.drawingPolicy = .anyInput canvasView.minimumZoomScale = 0.5 canvasView.maximumZoomScale = 2 canvasView.backgroundColor = .systemBackground canvasView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(canvasView) NSLayoutConstraint.activate([ canvasView.leadingAnchor.constraint(equalTo: view.leadingAnchor), canvasView.trailingAnchor.constraint(equalTo: view.trailingAnchor), canvasView.topAnchor.constraint(equalTo: view.topAnchor), canvasView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // todo: should the PKToolPicker be owned by this VC or something else? if let window = parent?.view.window, let toolPicker = PKToolPicker.shared(for: window) { toolPicker.setVisible(true, forFirstResponder: canvasView) toolPicker.addObserver(canvasView) toolPicker.addObserver(self) updateLayout(for: toolPicker) canvasView.becomeFirstResponder() // wait until the next run loop iteration so that the canvas view has become first responder and it's undo manager exists DispatchQueue.main.async { NotificationCenter.default.addObserver(self, selector: #selector(self.updateUndoRedoButtonState), name: .NSUndoManagerDidUndoChange, object: self.undoManager!) NotificationCenter.default.addObserver(self, selector: #selector(self.updateUndoRedoButtonState), name: .NSUndoManagerDidRedoChange, object: self.undoManager!) } } } func updateLayout(for toolPicker: PKToolPicker) { let obscuredFrame = toolPicker.frameObscured(in: view) // if there is no obscured frame, the tool picker is floating // which means we don't need to change inset or show undo/redo if obscuredFrame.isNull { navigationItem.leftBarButtonItems = [cancelBarButtonItem] canvasView.contentInset = .zero canvasView.automaticallyAdjustsScrollIndicatorInsets = true } else { navigationItem.leftBarButtonItems = [cancelBarButtonItem, undoBarButtonItem, redoBarButtonItem] canvasView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: obscuredFrame.height, right: 0) canvasView.automaticallyAdjustsScrollIndicatorInsets = false // we don't care about the botom safe area inset becaused the tool picker obscured frame includes the safe area canvasView.scrollIndicatorInsets = UIEdgeInsets(top: view.safeAreaInsets.top, left: view.safeAreaInsets.left, bottom: obscuredFrame.height, right: view.safeAreaInsets.right) } } func updateContentSizeForDrawing() { let overscrollAmount: CGFloat = 100 let drawingBounds = canvasView.drawing.bounds let newSize: CGSize if !drawingBounds.isNull { let width = max(canvasView.bounds.width, (drawingBounds.maxX + overscrollAmount) * canvasView.zoomScale) let height = max(canvasView.bounds.height, (drawingBounds.maxY + overscrollAmount) * canvasView.zoomScale) newSize = CGSize(width: width, height: height) } else { newSize = canvasView.bounds.size } canvasView.contentSize = newSize } @objc func updateUndoRedoButtonState() { undoBarButtonItem.isEnabled = undoManager!.canUndo redoBarButtonItem.isEnabled = undoManager!.canRedo } // MARK: Interaction @objc func cancelPressed() { delegate?.composeDrawingViewControllerClose(self) } @objc func savePressed() { delegate?.composeDrawingViewController(self, saveDrawing: canvasView.drawing) } @objc func undoPressed() { undoManager!.undo() } @objc func redoPressed() { undoManager!.redo() } } extension ComposeDrawingViewController: PKCanvasViewDelegate { func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) { if !undoManager!.isUndoing && !undoManager!.isRedoing { updateUndoRedoButtonState() } updateContentSizeForDrawing() } } extension ComposeDrawingViewController: PKToolPickerObserver { func toolPickerFramesObscuredDidChange(_ toolPicker: PKToolPicker) { updateLayout(for: toolPicker) } func toolPickerVisibilityDidChange(_ toolPicker: PKToolPicker) { updateLayout(for: toolPicker) } }