From 4fdafa893e52dface54395bc4b921fe7511bf388 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 9 May 2020 22:14:48 -0400 Subject: [PATCH] Add drawing attachments using PencilKit --- Tusker.xcodeproj/project.pbxproj | 8 + Tusker/Extensions/PKDrawing+Render.swift | 33 ++++ Tusker/Models/CompositionAttachmentData.swift | 22 ++- .../AssetPreviewViewController.swift | 3 + .../ComposeAttachmentsViewController.swift | 87 ++++++++- .../ComposeDrawingViewController.swift | 174 ++++++++++++++++++ .../ComposeAttachmentTableViewCell.swift | 7 +- .../Views/Draft Cell/DraftTableViewCell.swift | 7 + 8 files changed, 332 insertions(+), 9 deletions(-) create mode 100644 Tusker/Extensions/PKDrawing+Render.swift create mode 100644 Tusker/Screens/Compose/ComposeDrawingViewController.swift diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index cce6ba02..6e97253e 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -157,6 +157,7 @@ D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7B2157E01900721E32 /* XCBManager.swift */; }; D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */; }; D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A812157E8FA00721E32 /* XCBSession.swift */; }; + D67895BC24671E6D00D4CD9E /* PKDrawing+Render.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67895BB24671E6D00D4CD9E /* PKDrawing+Render.swift */; }; D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D679C09E215850EF00DA27FE /* XCBActions.swift */; }; D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */; }; D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; }; @@ -164,6 +165,7 @@ D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; }; D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */; }; D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68015412401A74600D6103B /* MediaPrefsView.swift */; }; + D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */; }; D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; }; D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; }; D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5823FE24300061E07D /* InteractivePushTransition.swift */; }; @@ -451,6 +453,7 @@ D6757A7B2157E01900721E32 /* XCBManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBManager.swift; sourceTree = ""; }; D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBRequestSpec.swift; sourceTree = ""; }; D6757A812157E8FA00721E32 /* XCBSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBSession.swift; sourceTree = ""; }; + D67895BB24671E6D00D4CD9E /* PKDrawing+Render.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PKDrawing+Render.swift"; sourceTree = ""; }; D679C09E215850EF00DA27FE /* XCBActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActions.swift; sourceTree = ""; }; D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeAccountDetailView.swift; sourceTree = ""; }; D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = ""; }; @@ -458,6 +461,7 @@ D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = ""; }; D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposingPrefsView.swift; sourceTree = ""; }; D68015412401A74600D6103B /* MediaPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPrefsView.swift; sourceTree = ""; }; + D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeDrawingViewController.swift; sourceTree = ""; }; D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = ""; }; D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = ""; }; D693DE5823FE24300061E07D /* InteractivePushTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractivePushTransition.swift; sourceTree = ""; }; @@ -910,6 +914,7 @@ D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */, D66362702136338600C9CBA2 /* ComposeViewController.swift */, D60309B42419D4F100A465FF /* ComposeAttachmentsViewController.swift */, + D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */, ); path = Compose; sourceTree = ""; @@ -1025,6 +1030,7 @@ D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */, D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */, D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */, + D67895BB24671E6D00D4CD9E /* PKDrawing+Render.swift */, ); path = Extensions; sourceTree = ""; @@ -1758,11 +1764,13 @@ D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */, D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */, D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */, + D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */, 04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */, D620483223D2A6A3008A63EF /* CompositionState.swift in Sources */, D6BC9DB5232D4CE3002CA326 /* NotificationsMode.swift in Sources */, D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */, D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */, + D67895BC24671E6D00D4CD9E /* PKDrawing+Render.swift in Sources */, 04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */, D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */, D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */, diff --git a/Tusker/Extensions/PKDrawing+Render.swift b/Tusker/Extensions/PKDrawing+Render.swift new file mode 100644 index 00000000..10c2ccaf --- /dev/null +++ b/Tusker/Extensions/PKDrawing+Render.swift @@ -0,0 +1,33 @@ +// +// PKDrawing+Render.swift +// Tusker +// +// Created by Shadowfacts on 5/9/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import PencilKit + +extension PKDrawing { + + func imageInLightMode(from rect: CGRect, scale: CGFloat = UIScreen.main.scale) -> UIImage { + let lightTraitCollection = UITraitCollection(userInterfaceStyle: .light) + var drawingImage: UIImage! + lightTraitCollection.performAsCurrent { + drawingImage = self.image(from: rect, scale: scale) + } + + let imageRect = CGRect(origin: .zero, size: rect.size) + let format = UIGraphicsImageRendererFormat() + format.opaque = false + format.scale = scale + let renderer = UIGraphicsImageRenderer(size: rect.size, format: format) + return renderer.image { (context) in + UIColor.white.setFill() + context.fill(imageRect) + drawingImage.draw(in: imageRect) + } + } + +} diff --git a/Tusker/Models/CompositionAttachmentData.swift b/Tusker/Models/CompositionAttachmentData.swift index 588fc142..b8f424a6 100644 --- a/Tusker/Models/CompositionAttachmentData.swift +++ b/Tusker/Models/CompositionAttachmentData.swift @@ -9,11 +9,13 @@ import UIKit import Photos import MobileCoreServices +import PencilKit enum CompositionAttachmentData { case asset(PHAsset) case image(UIImage) case video(URL) + case drawing(PKDrawing) var type: AttachmentType { switch self { @@ -23,6 +25,8 @@ enum CompositionAttachmentData { return .image case .video(_): return .video + case .drawing(_): + return .image } } @@ -44,7 +48,7 @@ enum CompositionAttachmentData { } } - func getData(completion: @escaping (Data, String) -> Void) { + func getData(completion: @escaping (_ data: Data, _ mimeType: String) -> Void) { switch self { case let .image(image): completion(image.pngData()!, "image/png") @@ -90,6 +94,10 @@ enum CompositionAttachmentData { fatalError("failed to create export session") } CompositionAttachmentData.exportVideoData(session: session, completion: completion) + + case let .drawing(drawing): + let image = drawing.imageInLightMode(from: drawing.bounds, scale: 1) + completion(image.pngData()!, "image/png") } } @@ -138,6 +146,10 @@ extension CompositionAttachmentData: Codable { try container.encode(image.pngData()!, forKey: .imageData) case .video(_): throw EncodingError.invalidValue(self, EncodingError.Context(codingPath: [], debugDescription: "video CompositionAttachments cannot be encoded")) + case let .drawing(drawing): + try container.encode("drawing", forKey: .type) + let drawingData = drawing.dataRepresentation() + try container.encode(drawingData, forKey: .drawing) } } @@ -156,6 +168,10 @@ extension CompositionAttachmentData: Codable { throw DecodingError.dataCorruptedError(forKey: .imageData, in: container, debugDescription: "Could not decode UIImage from image data") } self = .image(image) + case "drawing": + let drawingData = try container.decode(Data.self, forKey: .drawing) + let drawing = try PKDrawing(data: drawingData) + self = .drawing(drawing) default: throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "CompositionAttachment type must be one of 'image' or 'asset'") } @@ -166,6 +182,8 @@ extension CompositionAttachmentData: Codable { case imageData /// The local identifier of the PHAsset for this attachment case assetIdentifier + /// The PKDrawing object for this attachment. + case drawing } } @@ -178,6 +196,8 @@ extension CompositionAttachmentData: Equatable { return a == b case let (.video(a), .video(b)): return a == b + case let (.drawing(a), .drawing(b)): + return a == b default: return false } diff --git a/Tusker/Screens/Asset Picker/AssetPreviewViewController.swift b/Tusker/Screens/Asset Picker/AssetPreviewViewController.swift index eab2e45c..8771d0d1 100644 --- a/Tusker/Screens/Asset Picker/AssetPreviewViewController.swift +++ b/Tusker/Screens/Asset Picker/AssetPreviewViewController.swift @@ -52,6 +52,9 @@ class AssetPreviewViewController: UIViewController { default: fatalError("asset mediaType must be image or video") } + case let .drawing(drawing): + let image = drawing.imageInLightMode(from: drawing.bounds) + showImage(image) } } diff --git a/Tusker/Screens/Compose/ComposeAttachmentsViewController.swift b/Tusker/Screens/Compose/ComposeAttachmentsViewController.swift index ad58543d..aef273d7 100644 --- a/Tusker/Screens/Compose/ComposeAttachmentsViewController.swift +++ b/Tusker/Screens/Compose/ComposeAttachmentsViewController.swift @@ -9,6 +9,7 @@ import UIKit import Pachyderm import MobileCoreServices +import PencilKit protocol ComposeAttachmentsViewControllerDelegate: class { func composeSelectedAttachmentsDidChange() @@ -37,6 +38,8 @@ class ComposeAttachmentsViewController: UITableViewController { return false } } + + private var currentlyEditedDrawingIndex: Int? init(attachments: [CompositionAttachment], mastodonController: MastodonController) { self.attachments = attachments @@ -139,6 +142,23 @@ class ComposeAttachmentsViewController: UITableViewController { } } + func presentComposeDrawingViewController(editingAttachmentAt attachmentIndex: Int? = nil) { + let drawingVC: ComposeDrawingViewController + + if let index = attachmentIndex, + case let .drawing(drawing) = attachments[index].data { + drawingVC = ComposeDrawingViewController(editing: drawing) + currentlyEditedDrawingIndex = index + } else { + drawingVC = ComposeDrawingViewController() + } + + drawingVC.delegate = self + let nav = UINavigationController(rootViewController: drawingVC) + nav.modalPresentationStyle = .fullScreen + present(nav, animated: true) + } + func uploadAll(stepProgress: @escaping () -> Void, completion: @escaping (_ success: Bool, _ uploadedAttachments: [Attachment]) -> Void) { let group = DispatchGroup() @@ -270,19 +290,49 @@ class ComposeAttachmentsViewController: UITableViewController { } override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - guard indexPath.section == 0 else { return nil } - - let attachment = attachments[indexPath.row] - // cast to NSIndexPath because identifier needs to conform to NSCopying - return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: { () -> UIViewController? in - return AssetPreviewViewController(attachment: attachment.data) - }) { (_) -> UIMenu? in + if indexPath.section == 0 { + let attachment = attachments[indexPath.row] + // cast to NSIndexPath because identifier needs to conform to NSCopying + return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: { () -> UIViewController? in + return AssetPreviewViewController(attachment: attachment.data) + }) { (_) -> UIMenu? in + var actions = [UIAction]() + + switch attachment.data { + case .drawing(_): + actions.append(UIAction(title: "Edit Drawing", image: UIImage(systemName: "hand.draw"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in + self.presentComposeDrawingViewController(editingAttachmentAt: indexPath.row) + })) + default: + break + } + + if actions.isEmpty { + return nil + } else { + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions) + } + } + } else if indexPath.section == 1 { + guard isAddAttachmentsButtonEnabled() else { + return nil + } + // show context menu for drawing/file uploads + return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (_) -> UIMenu? in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: "Draw Something", image: UIImage(systemName: "hand.draw"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in + self.presentComposeDrawingViewController() + }) + ]) + } + } else { return nil } } private func targetedPreview(forConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { if let indexPath = (configuration.identifier as? NSIndexPath) as IndexPath?, + indexPath.section == 0, let cell = tableView.cellForRow(at: indexPath) as? ComposeAttachmentTableViewCell { let parameters = UIPreviewParameters() parameters.backgroundColor = .black @@ -452,3 +502,26 @@ extension ComposeAttachmentsViewController: ComposeAttachmentTableViewCellDelega delegate?.composeRequiresAttachmentDescriptionsDidChange() } } + +extension ComposeAttachmentsViewController: ComposeDrawingViewControllerDelegate { + func composeDrawingViewControllerClose(_ drawingController: ComposeDrawingViewController) { + dismiss(animated: true) + currentlyEditedDrawingIndex = nil + } + + func composeDrawingViewController(_ drawingController: ComposeDrawingViewController, saveDrawing drawing: PKDrawing) { + let newAttachment = CompositionAttachment(data: .drawing(drawing)) + + if let currentlyEditedDrawingIndex = currentlyEditedDrawingIndex { + attachments[currentlyEditedDrawingIndex] = newAttachment + tableView.reloadRows(at: [IndexPath(row: currentlyEditedDrawingIndex, section: 0)], with: .automatic) + } else { + attachments.append(newAttachment) + tableView.insertRows(at: [IndexPath(row: self.attachments.count - 1, section: 0)], with: .automatic) + updateHeightConstraint() + } + + dismiss(animated: true) + currentlyEditedDrawingIndex = nil + } +} diff --git a/Tusker/Screens/Compose/ComposeDrawingViewController.swift b/Tusker/Screens/Compose/ComposeDrawingViewController.swift new file mode 100644 index 00000000..a8aa166f --- /dev/null +++ b/Tusker/Screens/Compose/ComposeDrawingViewController.swift @@ -0,0 +1,174 @@ +// +// 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.allowsFingerDrawing = true + 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) + + 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) + } +} + +extension ComposeDrawingViewController: UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return otherGestureRecognizer == canvasView.drawingGestureRecognizer + } +} diff --git a/Tusker/Views/Attachment Cells/ComposeAttachmentTableViewCell.swift b/Tusker/Views/Attachment Cells/ComposeAttachmentTableViewCell.swift index 2de3fe67..03667017 100644 --- a/Tusker/Views/Attachment Cells/ComposeAttachmentTableViewCell.swift +++ b/Tusker/Views/Attachment Cells/ComposeAttachmentTableViewCell.swift @@ -7,7 +7,6 @@ // import UIKit -//import Combine import Photos import AVFoundation @@ -42,6 +41,8 @@ class ComposeAttachmentTableViewCell: UITableViewCell { descriptionTextView.text = attachment.attachmentDescription updateDescriptionPlaceholderLabel() + assetImageView.contentMode = .scaleAspectFill + assetImageView.backgroundColor = .secondarySystemBackground switch attachment.data { case let .image(image): assetImageView.image = image @@ -57,6 +58,10 @@ class ComposeAttachmentTableViewCell: UITableViewCell { if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) { assetImageView.image = UIImage(cgImage: cgImage) } + case let .drawing(drawing): + assetImageView.image = drawing.imageInLightMode(from: drawing.bounds) + assetImageView.contentMode = .scaleAspectFit + assetImageView.backgroundColor = .white } } diff --git a/Tusker/Views/Draft Cell/DraftTableViewCell.swift b/Tusker/Views/Draft Cell/DraftTableViewCell.swift index ae1dd6e4..5a368d47 100644 --- a/Tusker/Views/Draft Cell/DraftTableViewCell.swift +++ b/Tusker/Views/Draft Cell/DraftTableViewCell.swift @@ -34,6 +34,9 @@ class DraftTableViewCell: UITableViewCell { attachmentsStackView.addArrangedSubview(imageView) imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true + imageView.backgroundColor = .secondarySystemBackground + imageView.contentMode = .scaleAspectFill + switch attachment.data { case let .asset(asset): PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in @@ -44,6 +47,10 @@ class DraftTableViewCell: UITableViewCell { case .video(_): // videos aren't saved to drafts, so this is unreachable return + case let .drawing(drawing): + imageView.image = drawing.imageInLightMode(from: drawing.bounds) + imageView.backgroundColor = .white + imageView.contentMode = .scaleAspectFit } } }