diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..270910de --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +# Set default charset +[*.{js,py,swift,m,json}] +charset = utf-8 + +# 4 space indentation +[*.swift] +indent_style = space +indent_size = 4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 310c756d..872fb8c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,9 +46,9 @@ The more information you can provide, the easier it will be for us to resolve yo - This ensures the feature is in scope and no ones time is wasted. - **Please DO NOT submit pull requests to the `master` branch** - - This branch is always stable and represents a relase. + - This branch is always stable and represents a release. -- **Please DO submit your pull request to the branch repersenting the next release version** +- **Please DO submit your pull request to the branch representing the next release version** 1. Link to any issues the pull request resolves. If none exist, create one. 2. Write unit tests for new functionality or fix any broken by your changes. diff --git a/Example/Sources/SampleData.swift b/Example/Sources/SampleData.swift index d6eb14de..1f8bd9a7 100644 --- a/Example/Sources/SampleData.swift +++ b/Example/Sources/SampleData.swift @@ -198,6 +198,3 @@ final class SampleData { } } - - - diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index e7bd42fa..35dace98 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 171D5AB91F36712B0053DF69 /* InputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171D5AB81F36712B0053DF69 /* InputTextView.swift */; }; + 2EB618EF1F8462CA007FBA0E /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB618EE1F8462CA007FBA0E /* UICollectionView+Extensions.swift */; }; 372F6AEB1F36C15600B57FBD /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372F6AEA1F36C15600B57FBD /* AvatarView.swift */; }; 372F6AEF1F36C61000B57FBD /* AvatarViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372F6AEE1F36C61000B57FBD /* AvatarViewTests.swift */; }; 376AD1821F4258D80083072A /* TestMessageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376AD1811F4258D80083072A /* TestMessageModel.swift */; }; @@ -74,6 +75,7 @@ /* Begin PBXFileReference section */ 171D5AB81F36712B0053DF69 /* InputTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTextView.swift; sourceTree = ""; }; + 2EB618EE1F8462CA007FBA0E /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = ""; }; 372F6AEA1F36C15600B57FBD /* AvatarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = ""; }; 372F6AEE1F36C61000B57FBD /* AvatarViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarViewTests.swift; sourceTree = ""; }; 376AD1811F4258D80083072A /* TestMessageModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestMessageModel.swift; sourceTree = ""; }; @@ -150,6 +152,27 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2EB618F01F84676A007FBA0E /* Cells */ = { + isa = PBXGroup; + children = ( + B01049111F6F86E8008DBA58 /* LocationMessageCell.swift */, + B0D943BD1F6DC9AB008B7BFD /* MediaMessageCell.swift */, + B0655A4C1F244C0600542A83 /* MessageCollectionViewCell.swift */, + B0291DA81F6DBB9F00BEDF03 /* TextMessageCell.swift */, + ); + name = Cells; + sourceTree = ""; + }; + 2EB618F11F846899007FBA0E /* Headers & Footers */ = { + isa = PBXGroup; + children = ( + B074EE921F35587100ABB8C8 /* MessageHeaderView.swift */, + B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */, + B074EE941F35588A00ABB8C8 /* MessageFooterView.swift */, + ); + name = "Headers & Footers"; + sourceTree = ""; + }; 376AD1801F4258C50083072A /* Support Files */ = { isa = PBXGroup; children = ( @@ -167,6 +190,7 @@ 88916B231CF0DF2F00469F91 /* Products */, ); sourceTree = ""; + usesTabs = 0; }; 88916B231CF0DF2F00469F91 /* Products */ = { isa = PBXGroup; @@ -216,11 +240,12 @@ B09643981F295D43004D0129 /* Extensions */ = { isa = PBXGroup; children = ( - 38C867991F50EA1000811974 /* UIView+Extensions.swift */, - B09643851F286C9E004D0129 /* String+Extensions.swift */, - B096438F1F289142004D0129 /* UIColor+Extensions.swift */, - B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */, B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */, + B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */, + B09643851F286C9E004D0129 /* String+Extensions.swift */, + 2EB618EE1F8462CA007FBA0E /* UICollectionView+Extensions.swift */, + B096438F1F289142004D0129 /* UIColor+Extensions.swift */, + 38C867991F50EA1000811974 /* UIView+Extensions.swift */, ); name = Extensions; sourceTree = ""; @@ -246,21 +271,16 @@ B096439A1F295D68004D0129 /* Views */ = { isa = PBXGroup; children = ( - B0655A2D1F23D8BC00542A83 /* MessagesCollectionView.swift */, - B0655A4C1F244C0600542A83 /* MessageCollectionViewCell.swift */, + 2EB618F11F846899007FBA0E /* Headers & Footers */, + 2EB618F01F84676A007FBA0E /* Cells */, 372F6AEA1F36C15600B57FBD /* AvatarView.swift */, - B0655A371F23EE8B00542A83 /* MessageInputBar.swift */, 38C8679D1F50EA4A00811974 /* InputBarItem.swift */, 171D5AB81F36712B0053DF69 /* InputTextView.swift */, - B074EE921F35587100ABB8C8 /* MessageHeaderView.swift */, - B074EE941F35588A00ABB8C8 /* MessageFooterView.swift */, - B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */, E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */, - B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */, - B0291DA81F6DBB9F00BEDF03 /* TextMessageCell.swift */, - B0D943BD1F6DC9AB008B7BFD /* MediaMessageCell.swift */, + B0655A371F23EE8B00542A83 /* MessageInputBar.swift */, + B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */, + B0655A2D1F23D8BC00542A83 /* MessagesCollectionView.swift */, B010490F1F6F8684008DBA58 /* PlayButtonView.swift */, - B01049111F6F86E8008DBA58 /* LocationMessageCell.swift */, ); name = Views; sourceTree = ""; @@ -456,6 +476,7 @@ B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */, B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */, B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */, + 2EB618EF1F8462CA007FBA0E /* UICollectionView+Extensions.swift in Sources */, B0147C981F61AF930035B36E /* LabelAlignment.swift in Sources */, 376AD1881F4259D20083072A /* TestMessagesViewControllerModel.swift in Sources */, B0655A2A1F23D77200542A83 /* Sender.swift in Sources */, diff --git a/Sources/LocationMessageCell.swift b/Sources/LocationMessageCell.swift index 7f449ff8..401fbe56 100644 --- a/Sources/LocationMessageCell.swift +++ b/Sources/LocationMessageCell.swift @@ -26,6 +26,7 @@ import UIKit import MapKit open class LocationMessageCell: MessageCollectionViewCell { + open override class func reuseIdentifier() -> String { return "messagekit.cell.location" } // MARK: - Properties diff --git a/Sources/LocationMessageDisplayDelegate.swift b/Sources/LocationMessageDisplayDelegate.swift index a8ca804c..54cdb6a9 100644 --- a/Sources/LocationMessageDisplayDelegate.swift +++ b/Sources/LocationMessageDisplayDelegate.swift @@ -25,7 +25,6 @@ import Foundation import MapKit - /// Conform to this protocol to customize location messages's style public protocol LocationMessageDisplayDelegate: MessagesDisplayDelegate { @@ -50,7 +49,6 @@ public protocol LocationMessageDisplayDelegate: MessagesDisplayDelegate { /// - Returns: Your customized MKAnnotationView or nil to not show any. func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView? - /// Ask the delegate for a custom animation block to run when whe map screenshot is ready to be displaied in the given location message /// The animation block is called with the image view to be animated. You can animate it with CoreAnimation, UIView.animate or any library you prefer. /// diff --git a/Sources/MediaMessageCell.swift b/Sources/MediaMessageCell.swift index 001cc1b3..e9bb8921 100644 --- a/Sources/MediaMessageCell.swift +++ b/Sources/MediaMessageCell.swift @@ -25,6 +25,7 @@ import UIKit open class MediaMessageCell: MessageCollectionViewCell { + open override class func reuseIdentifier() -> String { return "messagekit.cell.mediamessage" } // MARK: - Properties diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift index e7b9c666..f3f068f3 100644 --- a/Sources/MessageCollectionViewCell.swift +++ b/Sources/MessageCollectionViewCell.swift @@ -24,7 +24,8 @@ import UIKit -open class MessageCollectionViewCell: UICollectionViewCell { +open class MessageCollectionViewCell: UICollectionViewCell, CollectionViewReusable { + open class func reuseIdentifier() -> String { return "messagekit.cell.base-cell" } // MARK: - Properties diff --git a/Sources/MessageDateHeaderView.swift b/Sources/MessageDateHeaderView.swift index 60add5f4..d271fb6e 100644 --- a/Sources/MessageDateHeaderView.swift +++ b/Sources/MessageDateHeaderView.swift @@ -25,6 +25,7 @@ import UIKit open class MessageDateHeaderView: MessageHeaderView { + open override class func reuseIdentifier() -> String { return "messagekit.header.date" } // MARK: - Properties diff --git a/Sources/MessageFooterView.swift b/Sources/MessageFooterView.swift index ac6f8556..c3c784d6 100644 --- a/Sources/MessageFooterView.swift +++ b/Sources/MessageFooterView.swift @@ -24,7 +24,8 @@ import UIKit -open class MessageFooterView: UICollectionReusableView { +open class MessageFooterView: UICollectionReusableView, CollectionViewReusable { + open class func reuseIdentifier() -> String { return "messagekit.footer.base" } // MARK: - Initializers diff --git a/Sources/MessageHeaderView.swift b/Sources/MessageHeaderView.swift index 406d3f97..1543779b 100644 --- a/Sources/MessageHeaderView.swift +++ b/Sources/MessageHeaderView.swift @@ -24,12 +24,11 @@ import UIKit -open class MessageHeaderView: UICollectionReusableView { +open class MessageHeaderView: UICollectionReusableView, CollectionViewReusable { + open class func reuseIdentifier() -> String { return "messagekit.header.base" } // MARK: - Properties - static let identifier = "MessageHeaderView" - public override init(frame: CGRect) { super.init(frame: frame) } diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index f9c9825c..e965729b 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -34,7 +34,6 @@ open class MessagesViewController: UIViewController { open var scrollsToBottomOnFirstLayout: Bool = false - open var scrollsToBottomOnKeybordBeginsEditing: Bool = false open var additionalTopContentInset: CGFloat = 0 { @@ -107,26 +106,13 @@ open class MessagesViewController: UIViewController { private func registerReusableViews() { - messagesCollectionView.register(TextMessageCell.self, - forCellWithReuseIdentifier: "TextMessageCell") + messagesCollectionView.register(TextMessageCell.self) + messagesCollectionView.register(MediaMessageCell.self) + messagesCollectionView.register(LocationMessageCell.self) - messagesCollectionView.register(MediaMessageCell.self, - forCellWithReuseIdentifier: "MediaMessageCell") - - messagesCollectionView.register(LocationMessageCell.self, - forCellWithReuseIdentifier: "LocationMessageCell") - - messagesCollectionView.register(MessageFooterView.self, - forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, - withReuseIdentifier: "MessageFooterView") - - messagesCollectionView.register(MessageHeaderView.self, - forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, - withReuseIdentifier: "MessageHeaderView") - - messagesCollectionView.register(MessageDateHeaderView.self, - forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, - withReuseIdentifier: "MessageDateHeaderView") + messagesCollectionView.register(MessageFooterView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter) + messagesCollectionView.register(MessageHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader) + messagesCollectionView.register(MessageDateHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader) } @@ -186,21 +172,15 @@ extension MessagesViewController: UICollectionViewDataSource { switch message.data { case .text, .attributedText, .emoji: - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TextMessageCell", for: indexPath) as? TextMessageCell else { - fatalError("Unable to dequeue TextMessageCell") - } + let cell = collectionView.dequeueReusableCell(TextMessageCell.self, for: indexPath) cell.configure(with: message, at: indexPath, and: messagesCollectionView) return cell case .photo, .video: - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MediaMessageCell", for: indexPath) as? MediaMessageCell else { - fatalError("Unable to dequeue MediaMessageCell") - } + let cell = collectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath) cell.configure(with: message, at: indexPath, and: messagesCollectionView) return cell case .location: - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LocationMessageCell", for: indexPath) as? LocationMessageCell else { - fatalError("Unable to dequeue LocationMessageCell") - } + let cell = collectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath) cell.configure(with: message, at: indexPath, and: messagesCollectionView) return cell } @@ -252,7 +232,6 @@ extension MessagesViewController: UICollectionViewDataSource { extension MessagesViewController { - fileprivate func addKeyboardObservers() { NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidChangeState), name: .UIKeyboardWillChangeFrame, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleTextViewDidBeginEditing), name: .UITextViewTextDidBeginEditing, object: messageInputBar.inputTextView) diff --git a/Sources/NSConstraintLayoutSet.swift b/Sources/NSConstraintLayoutSet.swift index 0176433d..9eb19cf0 100644 --- a/Sources/NSConstraintLayoutSet.swift +++ b/Sources/NSConstraintLayoutSet.swift @@ -49,11 +49,11 @@ public class NSLayoutConstraintSet { self.height = height } - /// All of the currently configured constraints - private var availableConstraints: [NSLayoutConstraint] { - return [top, bottom, left, right, centerX, centerY, width, height] - .flatMap {$0} - } + /// All of the currently configured constraints + private var availableConstraints: [NSLayoutConstraint] { + return [top, bottom, left, right, centerX, centerY, width, height] + .flatMap {$0} + } /// Activates all of the non-nil constraints /// diff --git a/Sources/TextMessageCell.swift b/Sources/TextMessageCell.swift index 49c897b2..33705ed7 100644 --- a/Sources/TextMessageCell.swift +++ b/Sources/TextMessageCell.swift @@ -25,6 +25,7 @@ import UIKit open class TextMessageCell: MessageCollectionViewCell { + open override class func reuseIdentifier() -> String { return "messagekit.cell.text" } // MARK: - Properties diff --git a/Sources/UICollectionView+Extensions.swift b/Sources/UICollectionView+Extensions.swift new file mode 100644 index 00000000..3d2b7c3d --- /dev/null +++ b/Sources/UICollectionView+Extensions.swift @@ -0,0 +1,36 @@ +// +// UICollectionView+Extensions.swift +// MessageKit +// +// Created by Frederic Barthelemy on 10/3/17. +// Copyright © 2017 MessageKit. All rights reserved. +// + +import Foundation + +/// Optional Cell Protocol to Simplify registration/cell type loading in a generic way +protocol CollectionViewReusable: class { + static func reuseIdentifier() -> String +} + +extension UICollectionView { + /// Registers a particular cell using its reuse-identifier + func register(_ cellClass: CellType.Type) { + register(cellClass, forCellWithReuseIdentifier: CellType.reuseIdentifier()) + } + + /// Registers a reusable view for a specific SectionKind + func register(_ headerFooterClass: ViewType.Type, forSupplementaryViewOfKind kind: String) { + register(headerFooterClass, + forSupplementaryViewOfKind: kind, + withReuseIdentifier: ViewType.reuseIdentifier()) + } + + /// Generically dequeues a cell of the correct type allowing you to avoid scattering your code with guard-let-else-fatal + func dequeueReusableCell(_ cellClass: CellType.Type, for indexPath: IndexPath) -> CellType { + guard let cell = dequeueReusableCell(withReuseIdentifier: cellClass.reuseIdentifier(), for: indexPath) as? CellType else { + fatalError("Unable to dequeue \(String(describing: cellClass)) with reuseId of \(cellClass.reuseIdentifier())") + } + return cell + } +} diff --git a/Sources/UIView+Extensions.swift b/Sources/UIView+Extensions.swift index f5858d61..d38f5e72 100644 --- a/Sources/UIView+Extensions.swift +++ b/Sources/UIView+Extensions.swift @@ -32,13 +32,13 @@ extension UIView { } translatesAutoresizingMaskIntoConstraints = false - let constraints: [NSLayoutConstraint] = [ - leftAnchor.constraint(equalTo: superview.leftAnchor), - rightAnchor.constraint(equalTo: superview.rightAnchor), - topAnchor.constraint(equalTo: superview.topAnchor), - bottomAnchor.constraint(equalTo: superview.bottomAnchor) - ] - NSLayoutConstraint.activate(constraints) + let constraints: [NSLayoutConstraint] = [ + leftAnchor.constraint(equalTo: superview.leftAnchor), + rightAnchor.constraint(equalTo: superview.rightAnchor), + topAnchor.constraint(equalTo: superview.topAnchor), + bottomAnchor.constraint(equalTo: superview.bottomAnchor) + ] + NSLayoutConstraint.activate(constraints) } @discardableResult