Add drawing attachments using PencilKit
This commit is contained in:
parent
9f75106706
commit
4fdafa893e
|
@ -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 = "<group>"; };
|
||||
D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBRequestSpec.swift; sourceTree = "<group>"; };
|
||||
D6757A812157E8FA00721E32 /* XCBSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBSession.swift; sourceTree = "<group>"; };
|
||||
D67895BB24671E6D00D4CD9E /* PKDrawing+Render.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PKDrawing+Render.swift"; sourceTree = "<group>"; };
|
||||
D679C09E215850EF00DA27FE /* XCBActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActions.swift; sourceTree = "<group>"; };
|
||||
D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeAccountDetailView.swift; sourceTree = "<group>"; };
|
||||
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = "<group>"; };
|
||||
|
@ -458,6 +461,7 @@
|
|||
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = "<group>"; };
|
||||
D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposingPrefsView.swift; sourceTree = "<group>"; };
|
||||
D68015412401A74600D6103B /* MediaPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPrefsView.swift; sourceTree = "<group>"; };
|
||||
D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeDrawingViewController.swift; sourceTree = "<group>"; };
|
||||
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
||||
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = "<group>"; };
|
||||
D693DE5823FE24300061E07D /* InteractivePushTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractivePushTransition.swift; sourceTree = "<group>"; };
|
||||
|
@ -910,6 +914,7 @@
|
|||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */,
|
||||
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
||||
D60309B42419D4F100A465FF /* ComposeAttachmentsViewController.swift */,
|
||||
D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */,
|
||||
);
|
||||
path = Compose;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1025,6 +1030,7 @@
|
|||
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */,
|
||||
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */,
|
||||
D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */,
|
||||
D67895BB24671E6D00D4CD9E /* PKDrawing+Render.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue