123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- //
- // AttachmentManager.swift
- // InputBarAccessoryView
- //
- // Copyright © 2017-2019 Nathan Tannar.
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in all
- // copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- // SOFTWARE.
- //
- // Created by Nathan Tannar on 10/4/17.
- //
- import UIKit
- open class AttachmentManager: NSObject, InputPlugin {
-
- public enum Attachment {
- case image(UIImage)
- case url(URL)
- case data(Data)
-
- @available(*, deprecated, message: ".other(AnyObject) has been depricated as of 2.0.0")
- case other(AnyObject)
- }
-
- // MARK: - Properties [Public]
-
- /// A protocol that can recieve notifications from the `AttachmentManager`
- open weak var delegate: AttachmentManagerDelegate?
-
- /// A protocol to passes data to the `AttachmentManager`
- open weak var dataSource: AttachmentManagerDataSource?
-
- open lazy var attachmentView: AttachmentCollectionView = { [weak self] in
- let attachmentView = AttachmentCollectionView()
- attachmentView.dataSource = self
- attachmentView.delegate = self
- return attachmentView
- }()
-
- /// The attachments that the managers holds
- private(set) public var attachments = [Attachment]() { didSet { reloadData() } }
-
- /// A flag you can use to determine if you want the manager to be always visible
- open var isPersistent = false { didSet { attachmentView.reloadData() } }
-
- /// A flag to determine if the AddAttachmentCell is visible
- open var showAddAttachmentCell = true { didSet { attachmentView.reloadData() } }
-
- /// The color applied to the backgroundColor of the deleteButton in each `AttachmentCell`
- open var tintColor: UIColor = UIColor(red: 0, green: 0.5, blue: 1, alpha: 1)
-
- // MARK: - Initialization
-
- public override init() {
- super.init()
- }
-
- // MARK: - InputPlugin
-
- open func reloadData() {
- attachmentView.reloadData()
- delegate?.attachmentManager(self, didReloadTo: attachments)
- delegate?.attachmentManager(self, shouldBecomeVisible: attachments.count > 0 || isPersistent)
- }
-
- /// Invalidates the `AttachmentManagers` session by removing all attachments
- open func invalidate() {
- attachments = []
- }
-
- /// Appends the object to the attachments
- ///
- /// - Parameter object: The object to append
- @discardableResult
- open func handleInput(of object: AnyObject) -> Bool {
- let attachment: Attachment
- if let image = object as? UIImage {
- attachment = .image(image)
- } else if let url = object as? URL {
- attachment = .url(url)
- } else if let data = object as? Data {
- attachment = .data(data)
- } else {
- return false
- }
-
- insertAttachment(attachment, at: attachments.count)
- return true
- }
-
- // MARK: - API [Public]
-
- /// Performs an animated insertion of an attachment at an index
- ///
- /// - Parameter index: The index to insert the attachment at
- open func insertAttachment(_ attachment: Attachment, at index: Int) {
-
- attachmentView.performBatchUpdates({
- self.attachments.insert(attachment, at: index)
- self.attachmentView.insertItems(at: [IndexPath(row: index, section: 0)])
- }, completion: { success in
- self.attachmentView.reloadData()
- self.delegate?.attachmentManager(self, didInsert: attachment, at: index)
- self.delegate?.attachmentManager(self, shouldBecomeVisible: self.attachments.count > 0 || self.isPersistent)
- })
- }
-
- /// Performs an animated removal of an attachment at an index
- ///
- /// - Parameter index: The index to remove the attachment at
- open func removeAttachment(at index: Int) {
-
- let attachment = attachments[index]
- attachmentView.performBatchUpdates({
- self.attachments.remove(at: index)
- self.attachmentView.deleteItems(at: [IndexPath(row: index, section: 0)])
- }, completion: { success in
- self.attachmentView.reloadData()
- self.delegate?.attachmentManager(self, didRemove: attachment, at: index)
- self.delegate?.attachmentManager(self, shouldBecomeVisible: self.attachments.count > 0 || self.isPersistent)
- })
- }
-
- }
- extension AttachmentManager: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
-
- // MARK: - UICollectionViewDelegate
-
- final public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
- if indexPath.row == attachments.count {
- delegate?.attachmentManager(self, didSelectAddAttachmentAt: indexPath.row)
- delegate?.attachmentManager(self, shouldBecomeVisible: attachments.count > 0 || isPersistent)
- }
- }
-
- // MARK: - UICollectionViewDataSource
-
- final public func numberOfItems(inSection section: Int) -> Int {
- return 1
- }
-
- final public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
- return attachments.count + (showAddAttachmentCell ? 1 : 0)
- }
-
- final public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
-
- if indexPath.row == attachments.count && showAddAttachmentCell {
- return createAttachmentCell(in: collectionView, at: indexPath)
- }
-
- let attachment = attachments[indexPath.row]
-
- if let cell = dataSource?.attachmentManager(self, cellFor: attachment, at: indexPath.row) {
- return cell
- } else {
-
- // Only images are supported by default
- switch attachment {
- case .image(let image):
- guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageAttachmentCell.reuseIdentifier, for: indexPath) as? ImageAttachmentCell else {
- fatalError()
- }
- cell.attachment = attachment
- cell.indexPath = indexPath
- cell.manager = self
- cell.imageView.image = image
- cell.imageView.tintColor = tintColor
- cell.deleteButton.backgroundColor = tintColor
- return cell
- default:
- return collectionView.dequeueReusableCell(withReuseIdentifier: AttachmentCell.reuseIdentifier, for: indexPath) as! AttachmentCell
- }
-
- }
- }
-
- // MARK: - UICollectionViewDelegateFlowLayout
-
- final public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
-
- var height = attachmentView.intrinsicContentHeight
- if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
- height -= (layout.sectionInset.bottom + layout.sectionInset.top + collectionView.contentInset.top + collectionView.contentInset.bottom)
- }
- return CGSize(width: height, height: height)
- }
-
- open func createAttachmentCell(in collectionView: UICollectionView, at indexPath: IndexPath) -> AttachmentCell {
-
- guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AttachmentCell.reuseIdentifier, for: indexPath) as? AttachmentCell else {
- fatalError()
- }
- cell.deleteButton.isHidden = true
- // Draw a plus
- let frame = CGRect(origin: CGPoint(x: cell.bounds.origin.x,
- y: cell.bounds.origin.y),
- size: CGSize(width: cell.bounds.width - cell.padding.left - cell.padding.right,
- height: cell.bounds.height - cell.padding.top - cell.padding.bottom))
- let strokeWidth: CGFloat = 3
- let length: CGFloat = frame.width / 2
- let vLayer = CAShapeLayer()
- vLayer.path = UIBezierPath(roundedRect: CGRect(x: frame.midX - (strokeWidth / 2),
- y: frame.midY - (length / 2),
- width: strokeWidth,
- height: length), cornerRadius: 5).cgPath
- vLayer.fillColor = UIColor.lightGray.cgColor
- let hLayer = CAShapeLayer()
- hLayer.path = UIBezierPath(roundedRect: CGRect(x: frame.midX - (length / 2),
- y: frame.midY - (strokeWidth / 2),
- width: length,
- height: strokeWidth), cornerRadius: 5).cgPath
- hLayer.fillColor = UIColor.lightGray.cgColor
- cell.containerView.layer.addSublayer(vLayer)
- cell.containerView.layer.addSublayer(hLayer)
- return cell
- }
- }
|