mirror of
https://github.com/MessageKit/MessageKit.git
synced 2026-02-06 19:03:19 +00:00
Merge pull request #555 from MessageKit/refactor/move-open
MessageKit 1.0 Staging Area
This commit is contained in:
+6
-2
@@ -11,6 +11,10 @@ custom_rules:
|
||||
regex: "override (open|public|private|internal|fileprivate)" # matching pattern
|
||||
message: "Use like open override or public override instead" # violation message. optional.
|
||||
severity: warning # violation severity. optional.
|
||||
opt_in_rules:
|
||||
- explicit_acl
|
||||
- explicit_top_level_acl
|
||||
explicit_acl: error
|
||||
explicit_top_level_acl: error
|
||||
included:
|
||||
- Sources
|
||||
- Tests
|
||||
- Sources
|
||||
+102
-24
@@ -8,38 +8,113 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa
|
||||
|
||||
### Added
|
||||
|
||||
- **Breaking Change** Adds new `DetectorType` called `.transitInformation` to message label.
|
||||
- **Breaking Change** Added new `MessagesDataSource` delegate methods, `numberOfSections(in)` and `numberOfItems(inSection:in)`.
|
||||
[#601](https://github.com/MessageKit/MessageKit/pull/601) by [@SD10](https://github.com/sd10), [@zhongwuzw](https://github.com/zhongwuzw).
|
||||
|
||||
- **Breaking Change** Added new protocol `MediaItem` as the associated value for the
|
||||
`MessageData.video` and `MessageData.photo` cases.
|
||||
[#587](https://github.com/MessageKit/MessageKit/pull/587) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Added new protocol `LocationItem` as the associated value for
|
||||
the `MessageData.location` case.
|
||||
[#587](https://github.com/MessageKit/MessageKit/pull/587) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Added new `DetectorType` called `.transitInformation` to message label.
|
||||
[#520](https://github.com/MessageKit/MessageKit/pull/520) by [@nosarj](https://github.com/nosarj).
|
||||
|
||||
- **Breaking Change** Adds `.custom(Any?)` case to `MessageData`.
|
||||
- **Breaking Change** Added `.custom(Any?)` case to `MessageData`.
|
||||
[#498](https://github.com/MessageKit/MessageKit/pull/498) by [@SD10](https://github.com/SD10).
|
||||
|
||||
- Adds the following methods to `MessagesCollectionViewFlowLayout`:
|
||||
- `messageLabelInsets(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets`
|
||||
- `messageContainerPadding(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets`
|
||||
- `messageContainerMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat`
|
||||
- `messageContainerSize(for message: MessageType, at indexPath: IndexPath) -> CGSize`
|
||||
- `cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath) -> LabelAlignment`
|
||||
- `cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize`
|
||||
- `cellTopLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat`
|
||||
- `cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath) -> LabelAlignment`
|
||||
- `cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize`
|
||||
- `cellBottomLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat`
|
||||
- `avatarPosition(for message: MessageType, at indexPath: IndexPath) -> AvatarPosition`
|
||||
- `avatarSize(for message: MessageType, at indexPath: IndexPath) -> CGSize`
|
||||
- `cellContentHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat`
|
||||
[#491](https://github.com/MessageKit/MessageKit/pull/491) by [@SD10](https://github.com/SD10).
|
||||
- Added `CellSizeCalculator` protocol that is responsible for sizing and configuring attributes of a `MessageCollectionViewCell`.
|
||||
[#579](https://github.com/MessageKit/MessageKit/pull/579) by [@SD10](https://github.com/SD10).
|
||||
|
||||
- Adds the following methods to `MessageCollectionViewCell`:
|
||||
- `layoutAvatarView(with attributes: MessagesCollectionViewLayoutAttributes)`
|
||||
- `layoutMessageContainerView(with attributes: MessagesCollectionViewLayoutAttributes)`
|
||||
- `layoutTopLabel(with attributes: MessagesCollectionViewLayoutAttributes)`
|
||||
- `layoutBottomLabel(with attributes: MessagesCollectionViewLayoutAttributes)`
|
||||
[#491](https://github.com/MessageKit/MessageKit/pull/491) by [@SD10](https://github.com/SD10).
|
||||
- Added `MessageSizeCalculator`, `MediaMessageSizeCalculator`, `TextMessageSizeCalculator`, and `LocationMessageSizeCalculator`
|
||||
classes that are responsible for sizing the `MessagesCollectionViewCell` types provided by MessageKit.
|
||||
[#579](https://github.com/MessageKit/MessageKit/pull/579) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- Added `shouldManageSendButtonEnabledState` to `MessageInputBar` to disable automatically managing `MessageInputBar.sendButton`'s `isEnabled` state when text changes. (Default value is `true`).
|
||||
- Added `shouldManageSendButtonEnabledState` to `MessageInputBar` to disable automatically managing `MessageInputBar.sendButton`'s
|
||||
`isEnabled` state when text changes. (Default value is `true`).
|
||||
[#530](https://github.com/MessageKit/MessageKit/pull/530) by [@clayellis](https://github.com/clayellis).
|
||||
|
||||
- Added two new methods `cellTopLabelHeight(for:at:in)` and `cellBottomLabelHeight(for:at:in)` to `MessagesLayoutDelegate`
|
||||
[#580](https://github.com/MessageKit/MessageKit/pull/580) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- Added new class `InsetLabel`.
|
||||
[#580](https://github.com/MessageKit/MessageKit/pull/580) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- Added new method `configureMediaMessageImageView(_:for:at:in)` to configure the `UIImageView` of a
|
||||
`MediaMessageCell` asynchronously.
|
||||
[#592](https://github.com/MessageKit/MessageKit/pull/592) by [@zhongwuzw](https://github.com/zhongwuzw), [@SD10](https://github.com/sd10)
|
||||
|
||||
- Added new `MessageContentCell` class and `MessageReusableView` class.
|
||||
[#596](https://github.com/MessageKit/MessageKit/pull/596) by [@SD10](https://github.com/sd10).
|
||||
|
||||
### Changed
|
||||
|
||||
- **Breaking Change** Changed the `messageFooterView(for:in)` and `messageHeaderView(for:in)` methods of
|
||||
`MessagesDisplayDelegate` by removing the `message` parameter.
|
||||
[#615](https://github.com/MessageKit/MessageKit/pull/615) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Changed the `footerViewSize(for:in)` and `headerViewSize(for:in)` methods of `MessagesLayoutDelegate`
|
||||
by removing the `MessageType` and `IndexPath` parameters and replacing them with a new `section: Int` parameter.
|
||||
[#615](https://github.com/MessageKit/MessageKit/pull/615) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** The reuse identifiers of all `MessageReusableView`s and `MessageCollectionViewCell`s have been
|
||||
changed to match their class name exactly.
|
||||
[#615](https://github.com/MessageKit/MessageKit/pull/615) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Changed the superclass of `MessageDateHeaderView` from `MessageHeaderView` to `MessageReusableView`.
|
||||
[#615](https://github.com/MessageKit/MessageKit/pull/615) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Change `NSLayoutConstraintSet` class from `public` to `internal`.
|
||||
[#607](https://github.com/MessageKit/MessageKit/pull/607) by [@zhongwuzw](https://github.com/zhongwuzw).
|
||||
|
||||
- **Breaking Change** `MessageHeaderView` and `MessageFooterView` now subclass `MessageReusableView` class.
|
||||
[#596](https://github.com/MessageKit/MessageKit/pull/596) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Renamed `MessageCollectionViewCell` to `MessageContentCell`.
|
||||
The `MessageCollectionViewCell` class is now a bare bones subclass.
|
||||
[#596](https://github.com/MessageKit/MessageKit/pull/596) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Changed `LabelAlignment` to be a `struct` with properties of
|
||||
`textAlignment: NSTextAlignment` and `textInsets: UIEdgeInsets` to position the text in the `cellTopLabel` and `cellBottomLabel`.
|
||||
[#580](https://github.com/MessageKit/MessageKit/pull/580) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** The type of `cellTopLabel` and `cellBottomLabel` has been changed to `InsetLabel`.
|
||||
[#580](https://github.com/MessageKit/MessageKit/pull/580) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- The `MessageData.emoji` case no longer uses a default font of 2x the `messageLabelFont` size.
|
||||
You must now set this font explicitly through the `emojiMessageSizeCalculator` on `MessagesCollectionViewFlowLayout`.
|
||||
[#530](https://github.com/MessageKit/MessageKit/pull/579) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- Changed the `contentMode` of the `UIImageView` for `MediaMessageCell` to be `.scaleAspectFill`.
|
||||
[#587](https://github.com/MessageKit/MessageKit/pull/587) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- The result of the `MessagesDisplayDelegate` method `textColor(for message:...)` no longer applies to `.attributedText` case of `MessageData`.
|
||||
[#625](https://github.com/MessageKit/MessageKit/pull/625) by [@cwalo](https://github.com/cwalo).
|
||||
|
||||
### Removed
|
||||
|
||||
- **Breaking Change** Removed the `showsDateHeaderAfterTimeInterval` property of `MessagesCollectionView`.
|
||||
[#615](https://github.com/MessageKit/MessageKit/pull/615) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Removed the `reuseIdentifer` method from `MessageCollectionViewCell`, `TextMessageCell`,
|
||||
`LocationMessageCell`, `MediaMessageCell`, and `MessageContentCell`.
|
||||
[#615](https://github.com/MessageKit/MessageKit/pull/615) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Removed the `CollectionViewReusable` protocol.
|
||||
[#615](https://github.com/MessageKit/MessageKit/pull/615) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Removed `MessageHeaderView` and `MessageFooterView` in favor of `MessageReusableView`.
|
||||
[#615](https://github.com/MessageKit/MessageKit/pull/615) by [@SD10](https://github.com/sd10).
|
||||
|
||||
- **Breaking Change** Removed `numberOfMessages(in)` method of `MessagesDataSource`, instead please use `numberOfSections(in)` and `numberOfItems(inSection:in)`.
|
||||
[#601](https://github.com/MessageKit/MessageKit/pull/601) by [@SD10](https://github.com/sd10), [@zhongwuzw](https://github.com/zhongwuzw).
|
||||
|
||||
- **Breaking Change** Removed the `messageLabelFont` property from `MessagesCollectionViewFlowLayout`.
|
||||
You can now set this property through `textMessageSizeCalculator` property.
|
||||
[#579](https://github.com/MessageKit/MessageKit/pull/579) by [@SD10](https://github.com/sd10).
|
||||
|
||||
## [[Prerelease] 0.13.1](https://github.com/MessageKit/MessageKit/releases/tag/0.13.1)
|
||||
|
||||
### Fixed
|
||||
@@ -53,6 +128,9 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa
|
||||
- Fixed message bubble tail orientation invalidation in `iOS9`.
|
||||
[#469](https://github.com/MessageKit/MessageKit/pull/469) by [@zhongwuzw](https://github.com/zhongwuzw).
|
||||
|
||||
- Fixed equality checking on `MessagesCollectionViewLayoutAttributes`.
|
||||
[#593](https://github.com/MessageKit/MessageKit/pull/593) by [@zhongwuzw](https://github.com/zhongwuzw), [@SD10](https://github.com/sd10)
|
||||
|
||||
## [[Prerelease] 0.13.0](https://github.com/MessageKit/MessageKit/releases/tag/0.13.0)
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
disabled_rules:
|
||||
- identifier_name
|
||||
- trailing_whitespace
|
||||
- line_length
|
||||
- type_body_length
|
||||
- file_length
|
||||
custom_rules:
|
||||
override_func: # rule identifier
|
||||
name: "override in func" # rule name. optional.
|
||||
regex: "override (open|public|private|internal|fileprivate)" # matching pattern
|
||||
message: "Use like open override or public override instead" # violation message. optional.
|
||||
severity: warning # violation severity. optional.
|
||||
included:
|
||||
- Sources
|
||||
@@ -207,6 +207,7 @@
|
||||
882B5EA41CF7D8D100B6E160 /* Embed Frameworks */,
|
||||
3C8DFCD3063DA0DF099EC7B6 /* [CP] Embed Pods Frameworks */,
|
||||
CD3F8EE3AFF2313DF2D62568 /* [CP] Copy Pods Resources */,
|
||||
7E0AFF9D207BB460004DFD4C /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -266,7 +267,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0730;
|
||||
LastUpgradeCheck = 0900;
|
||||
LastUpgradeCheck = 0930;
|
||||
ORGANIZATIONNAME = MessageKit;
|
||||
TargetAttributes = {
|
||||
882B5E321CF7D4B900B6E160 = {
|
||||
@@ -464,6 +465,19 @@
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ChatExampleUITests/Pods-ChatExampleUITests-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
7E0AFF9D207BB460004DFD4C /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
|
||||
};
|
||||
CD3F8EE3AFF2313DF2D62568 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -556,12 +570,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -611,12 +627,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0920"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -40,9 +40,8 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
codeCoverageEnabled = "YES"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -71,7 +70,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0920"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -10,7 +10,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
@@ -31,7 +30,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -9,8 +9,8 @@ EXTERNAL SOURCES:
|
||||
:path: ../
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
MessageKit: dcf465101a947bf088cb6a607da04c2d2087d8d7
|
||||
MessageKit: a70ca5e4c52fae4155f182f13ea8ad12fcd8f27f
|
||||
|
||||
PODFILE CHECKSUM: cecdb7bc8129cf99f66de9f68eea3256fec30c3d
|
||||
|
||||
COCOAPODS: 1.3.1
|
||||
COCOAPODS: 1.4.0
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
final internal class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
|
||||
UIApplication.shared.statusBarStyle = .lightContent
|
||||
window = UIWindow(frame: UIScreen.main.bounds)
|
||||
|
||||
@@ -26,7 +26,7 @@ import UIKit
|
||||
import MessageKit
|
||||
import MapKit
|
||||
|
||||
class ConversationViewController: MessagesViewController {
|
||||
internal class ConversationViewController: MessagesViewController {
|
||||
|
||||
let refreshControl = UIRefreshControl()
|
||||
|
||||
@@ -91,8 +91,6 @@ class ConversationViewController: MessagesViewController {
|
||||
label.text = "nathan.tannar is typing..."
|
||||
label.font = UIFont.boldSystemFont(ofSize: 16)
|
||||
messageInputBar.topStackView.addArrangedSubview(label)
|
||||
|
||||
|
||||
messageInputBar.topStackViewPadding.top = 6
|
||||
messageInputBar.topStackViewPadding.left = 12
|
||||
|
||||
@@ -156,11 +154,9 @@ class ConversationViewController: MessagesViewController {
|
||||
},
|
||||
makeButton(named: "ic_at").onSelected {
|
||||
$0.tintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
|
||||
print("@ Selected")
|
||||
},
|
||||
makeButton(named: "ic_hashtag").onSelected {
|
||||
$0.tintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
|
||||
print("# Selected")
|
||||
},
|
||||
.flexibleSpace,
|
||||
makeButton(named: "ic_library").onTextViewDidChange { button, textView in
|
||||
@@ -260,8 +256,8 @@ extension ConversationViewController: MessagesDataSource {
|
||||
func currentSender() -> Sender {
|
||||
return SampleData.shared.currentSender
|
||||
}
|
||||
|
||||
func numberOfMessages(in messagesCollectionView: MessagesCollectionView) -> Int {
|
||||
|
||||
func numberOfSections(in messagesCollectionView: MessagesCollectionView) -> Int {
|
||||
return messageList.count
|
||||
}
|
||||
|
||||
@@ -275,15 +271,13 @@ extension ConversationViewController: MessagesDataSource {
|
||||
}
|
||||
|
||||
func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
|
||||
|
||||
struct ConversationDateFormatter {
|
||||
static let formatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return formatter
|
||||
}()
|
||||
}
|
||||
let formatter = ConversationDateFormatter.formatter
|
||||
|
||||
let formatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return formatter
|
||||
}()
|
||||
|
||||
let dateString = formatter.string(from: message.sentDate)
|
||||
return NSAttributedString(string: dateString, attributes: [NSAttributedStringKey.font: UIFont.preferredFont(forTextStyle: .caption2)])
|
||||
}
|
||||
@@ -300,7 +294,7 @@ extension ConversationViewController: MessagesDisplayDelegate {
|
||||
return isFromCurrentSender(message: message) ? .white : .darkText
|
||||
}
|
||||
|
||||
func detectorAttributes(for detector: DetectorType, and message: MessageType, at indexPath: IndexPath) -> [NSAttributedStringKey : Any] {
|
||||
func detectorAttributes(for detector: DetectorType, and message: MessageType, at indexPath: IndexPath) -> [NSAttributedStringKey: Any] {
|
||||
return MessageLabel.defaultAttributes
|
||||
}
|
||||
|
||||
@@ -357,45 +351,17 @@ extension ConversationViewController: MessagesDisplayDelegate {
|
||||
|
||||
extension ConversationViewController: MessagesLayoutDelegate {
|
||||
|
||||
func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition {
|
||||
return AvatarPosition(horizontal: .natural, vertical: .messageBottom)
|
||||
func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
return 16
|
||||
}
|
||||
|
||||
func messagePadding(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets {
|
||||
if isFromCurrentSender(message: message) {
|
||||
return UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 4)
|
||||
} else {
|
||||
return UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 30)
|
||||
}
|
||||
func cellBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
return 16
|
||||
}
|
||||
|
||||
func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
|
||||
if isFromCurrentSender(message: message) {
|
||||
return .messageTrailing(UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10))
|
||||
} else {
|
||||
return .messageLeading(UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0))
|
||||
}
|
||||
}
|
||||
|
||||
func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
|
||||
if isFromCurrentSender(message: message) {
|
||||
return .messageLeading(UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0))
|
||||
} else {
|
||||
return .messageTrailing(UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10))
|
||||
}
|
||||
}
|
||||
|
||||
func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
|
||||
|
||||
private func footerViewSize(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
|
||||
return CGSize(width: messagesCollectionView.bounds.width, height: 10)
|
||||
}
|
||||
|
||||
// MARK: - Location Messages
|
||||
|
||||
func heightForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
return 200
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - MessageCellDelegate
|
||||
@@ -424,7 +390,7 @@ extension ConversationViewController: MessageCellDelegate {
|
||||
|
||||
extension ConversationViewController: MessageLabelDelegate {
|
||||
|
||||
func didSelectAddress(_ addressComponents: [String : String]) {
|
||||
func didSelectAddress(_ addressComponents: [String: String]) {
|
||||
print("Address Selected: \(addressComponents)")
|
||||
}
|
||||
|
||||
@@ -440,7 +406,7 @@ extension ConversationViewController: MessageLabelDelegate {
|
||||
print("URL Selected: \(url)")
|
||||
}
|
||||
|
||||
func didSelectTransitInformation(_ transitInformation: [String : String]) {
|
||||
func didSelectTransitInformation(_ transitInformation: [String: String]) {
|
||||
print("TransitInformation Selected: \(transitInformation)")
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
|
||||
import MessageKit
|
||||
|
||||
class IMessageViewController: MessagesViewController {
|
||||
|
||||
internal class IMessageViewController: MessagesViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
@@ -26,7 +26,7 @@ import UIKit
|
||||
import MessageKit
|
||||
import SafariServices
|
||||
|
||||
final class InboxViewController: UITableViewController {
|
||||
final internal class InboxViewController: UITableViewController {
|
||||
|
||||
let cells = ["Example", "Settings", "Source Code", "Contributors"]
|
||||
|
||||
@@ -37,7 +37,7 @@ final class InboxViewController: UITableViewController {
|
||||
title = "MessageKit"
|
||||
navigationController?.navigationBar.tintColor = .white
|
||||
navigationController?.navigationBar.barTintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
|
||||
navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white, NSAttributedStringKey.font : UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.bold) ]
|
||||
navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white, NSAttributedStringKey.font: UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.bold) ]
|
||||
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
|
||||
tableView.tableFooterView = UIView()
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
|
||||
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
|
||||
@@ -23,42 +23,71 @@
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import MessageKit
|
||||
import CoreLocation
|
||||
import MessageKit
|
||||
|
||||
private struct MockLocationItem: LocationItem {
|
||||
|
||||
var location: CLLocation
|
||||
var size: CGSize
|
||||
|
||||
init(location: CLLocation) {
|
||||
self.location = location
|
||||
self.size = CGSize(width: 240, height: 240)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private struct MockMediaItem: MediaItem {
|
||||
|
||||
var url: URL?
|
||||
var image: UIImage?
|
||||
var placeholderImage: UIImage
|
||||
var size: CGSize
|
||||
|
||||
init(image: UIImage) {
|
||||
self.image = image
|
||||
self.size = CGSize(width: 240, height: 240)
|
||||
self.placeholderImage = UIImage()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal struct MockMessage: MessageType {
|
||||
|
||||
var messageId: String
|
||||
var sender: Sender
|
||||
var sentDate: Date
|
||||
var data: MessageData
|
||||
|
||||
private init(data: MessageData, sender: Sender, messageId: String, date: Date) {
|
||||
self.data = data
|
||||
self.sender = sender
|
||||
self.messageId = messageId
|
||||
self.sentDate = date
|
||||
}
|
||||
|
||||
struct MockMessage: MessageType {
|
||||
|
||||
var messageId: String
|
||||
var sender: Sender
|
||||
var sentDate: Date
|
||||
var data: MessageData
|
||||
|
||||
init(data: MessageData, sender: Sender, messageId: String, date: Date) {
|
||||
self.data = data
|
||||
self.sender = sender
|
||||
self.messageId = messageId
|
||||
self.sentDate = date
|
||||
}
|
||||
|
||||
init(text: String, sender: Sender, messageId: String, date: Date) {
|
||||
self.init(data: .text(text), sender: sender, messageId: messageId, date: date)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
init(attributedText: NSAttributedString, sender: Sender, messageId: String, date: Date) {
|
||||
self.init(data: .attributedText(attributedText), sender: sender, messageId: messageId, date: date)
|
||||
}
|
||||
}
|
||||
|
||||
init(image: UIImage, sender: Sender, messageId: String, date: Date) {
|
||||
self.init(data: .photo(image), sender: sender, messageId: messageId, date: date)
|
||||
let mediaItem = MockMediaItem(image: image)
|
||||
self.init(data: .photo(mediaItem), sender: sender, messageId: messageId, date: date)
|
||||
}
|
||||
|
||||
init(thumbnail: UIImage, sender: Sender, messageId: String, date: Date) {
|
||||
let url = URL(fileURLWithPath: "")
|
||||
self.init(data: .video(file: url, thumbnail: thumbnail), sender: sender, messageId: messageId, date: date)
|
||||
let mediaItem = MockMediaItem(image: thumbnail)
|
||||
self.init(data: .video(mediaItem), sender: sender, messageId: messageId, date: date)
|
||||
}
|
||||
|
||||
init(location: CLLocation, sender: Sender, messageId: String, date: Date) {
|
||||
self.init(data: .location(location), sender: sender, messageId: messageId, date: date)
|
||||
let locationItem = MockLocationItem(location: location)
|
||||
self.init(data: .location(locationItem), sender: sender, messageId: messageId, date: date)
|
||||
}
|
||||
|
||||
init(emoji: String, sender: Sender, messageId: String, date: Date) {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
import MessageKit
|
||||
import CoreLocation
|
||||
|
||||
final class SampleData {
|
||||
final internal class SampleData {
|
||||
|
||||
static let shared = SampleData()
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
import UIKit
|
||||
import MessageKit
|
||||
|
||||
final class SettingsViewController: UITableViewController {
|
||||
final internal class SettingsViewController: UITableViewController {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@@ -100,16 +100,18 @@ final class SettingsViewController: UITableViewController {
|
||||
// MARK: - Helper
|
||||
|
||||
private func configureTextFieldTableViewCell(at indexPath: IndexPath) -> TextFieldTableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldTableViewCell.identifier, for: indexPath) as! TextFieldTableViewCell
|
||||
cell.mainLabel.text = "Mock messages count:"
|
||||
|
||||
let messagesCount = UserDefaults.standard.mockMessagesCount()
|
||||
cell.textField.text = "\(messagesCount)"
|
||||
|
||||
cell.textField.inputView = messagesPicker
|
||||
cell.textField.inputAccessoryView = messagesToolbar
|
||||
|
||||
return cell
|
||||
if let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldTableViewCell.identifier, for: indexPath) as? TextFieldTableViewCell {
|
||||
cell.mainLabel.text = "Mock messages count:"
|
||||
|
||||
let messagesCount = UserDefaults.standard.mockMessagesCount()
|
||||
cell.textField.text = "\(messagesCount)"
|
||||
|
||||
cell.textField.inputView = messagesPicker
|
||||
cell.textField.inputAccessoryView = messagesToolbar
|
||||
|
||||
return cell
|
||||
}
|
||||
return TextFieldTableViewCell()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class TextFieldTableViewCell: UITableViewCell {
|
||||
internal class TextFieldTableViewCell: UITableViewCell {
|
||||
|
||||
static let identifier = "TextFieldTableViewCellIdentifier"
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0EE91E661FDEC888005420A2 /* CGRect+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE91E651FDEC887005420A2 /* CGRect+Extensions.swift */; };
|
||||
0EF0888C206F7E83007F2F58 /* CellSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF0888B206F7E83007F2F58 /* CellSizeCalculator.swift */; };
|
||||
1F066E131FD90BB600E11013 /* MessagesViewControllerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7FC8CB1FD2700B006CC979 /* MessagesViewControllerSpec.swift */; };
|
||||
1F066E141FD90BB700E11013 /* MessageLabelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F066E101FD90A0600E11013 /* MessageLabelSpec.swift */; };
|
||||
1F066E1D1FDA3C1700E11013 /* SenderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F066E1C1FDA3C1700E11013 /* SenderSpec.swift */; };
|
||||
@@ -15,15 +16,21 @@
|
||||
1F066E231FDA3F0200E11013 /* DetectorTypeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F066E221FDA3F0200E11013 /* DetectorTypeSpec.swift */; };
|
||||
1F087D451FD274FD00E95A45 /* Nimble.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1F7FC8C71FD26F49006CC979 /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
1F087D461FD274FD00E95A45 /* Quick.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1F7FC8C51FD26F33006CC979 /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
1F6C040C206A2891007BDE44 /* MessageContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6C040B206A2891007BDE44 /* MessageContentCell.swift */; };
|
||||
1F6C040E206A2AF4007BDE44 /* MessageReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6C040D206A2AF4007BDE44 /* MessageReusableView.swift */; };
|
||||
1F7FC8C61FD26F33006CC979 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F7FC8C51FD26F33006CC979 /* Quick.framework */; };
|
||||
1F7FC8C81FD26F49006CC979 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F7FC8C71FD26F49006CC979 /* Nimble.framework */; };
|
||||
1F82D1431FB1B75B00B81A88 /* AvatarPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F82D1421FB1B75B00B81A88 /* AvatarPosition.swift */; };
|
||||
1FAC3488201E44F7000A1504 /* MessagesCollectionViewFlowLayout+Labels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAC3487201E44F7000A1504 /* MessagesCollectionViewFlowLayout+Labels.swift */; };
|
||||
1FAC348A201E4DE7000A1504 /* MessagesCollectionViewFlowLayout+Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAC3489201E4DE7000A1504 /* MessagesCollectionViewFlowLayout+Avatar.swift */; };
|
||||
1FAC348C201E4F88000A1504 /* MessagesCollectionViewFlowLayout+MessageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAC348B201E4F88000A1504 /* MessagesCollectionViewFlowLayout+MessageContainerView.swift */; };
|
||||
1FAC3490201E50DF000A1504 /* MessageCellLayoutContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAC348F201E50DF000A1504 /* MessageCellLayoutContext.swift */; };
|
||||
1FAC3492201E5346000A1504 /* MessagesCollectionViewFlowLayout+CellSizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAC3491201E5346000A1504 /* MessagesCollectionViewFlowLayout+CellSizing.swift */; };
|
||||
1FCA6D30201C1CC900BC3480 /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FCA6D2F201C1CC900BC3480 /* UIEdgeInsets+Extensions.swift */; };
|
||||
1FD589602064E08A004B5081 /* MediaItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD5895F2064E08A004B5081 /* MediaItem.swift */; };
|
||||
1FD589612064E1B5004B5081 /* MockMessagesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8962AC7A1F87AB230030B058 /* MockMessagesDataSource.swift */; };
|
||||
1FD589622064E1B9004B5081 /* MockMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8962AC7B1F87AB230030B058 /* MockMessage.swift */; };
|
||||
1FD5896420660C1C004B5081 /* LocationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD5896320660C1C004B5081 /* LocationItem.swift */; };
|
||||
1FE7839E20662835007FA024 /* MessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE7839D20662835007FA024 /* MessageSizeCalculator.swift */; };
|
||||
1FE783A220662905007FA024 /* TextMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE783A120662905007FA024 /* TextMessageSizeCalculator.swift */; };
|
||||
1FE783A4206629A5007FA024 /* MediaMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE783A3206629A5007FA024 /* MediaMessageSizeCalculator.swift */; };
|
||||
1FE783A6206629C2007FA024 /* LocationMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE783A5206629C2007FA024 /* LocationMessageSizeCalculator.swift */; };
|
||||
1FE783A8206633C0007FA024 /* InsetLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE783A7206633C0007FA024 /* InsetLabel.swift */; };
|
||||
1FF377A420087C82004FD648 /* MessageKitError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF377A320087C82004FD648 /* MessageKitError.swift */; };
|
||||
1FF377AA20087D78004FD648 /* MessagesViewController+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF377A920087D78004FD648 /* MessagesViewController+Menu.swift */; };
|
||||
1FF377AC20087DA2004FD648 /* MessagesViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF377AB20087DA2004FD648 /* MessagesViewController+Keyboard.swift */; };
|
||||
@@ -41,8 +48,6 @@
|
||||
8962AC941F87AB860030B058 /* MessageKitDateFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8962AC741F87AB230030B058 /* MessageKitDateFormatterTests.swift */; };
|
||||
8962AC951F87AB860030B058 /* MessageStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8962AC751F87AB230030B058 /* MessageStyleTests.swift */; };
|
||||
8962AC991F87AB860030B058 /* MessagesDisplayDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8962AC811F87AB230030B058 /* MessagesDisplayDelegateTests.swift */; };
|
||||
8962AC9A1F87ABB20030B058 /* MockMessagesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8962AC7A1F87AB230030B058 /* MockMessagesDataSource.swift */; };
|
||||
8962AC9C1F87ABC60030B058 /* MockMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8962AC7B1F87AB230030B058 /* MockMessage.swift */; };
|
||||
B0C8D99B1F73076B000A86E4 /* MessageKitAssets.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B0C8D99A1F73076B000A86E4 /* MessageKitAssets.bundle */; };
|
||||
B7A03F181F86682C006AEF79 /* MessagesCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F161F86682C006AEF79 /* MessagesCollectionViewFlowLayout.swift */; };
|
||||
B7A03F191F86682C006AEF79 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F171F86682C006AEF79 /* MessagesCollectionViewLayoutAttributes.swift */; };
|
||||
@@ -56,8 +61,6 @@
|
||||
B7A03F2D1F866895006AEF79 /* LabelAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F221F866895006AEF79 /* LabelAlignment.swift */; };
|
||||
B7A03F2E1F866895006AEF79 /* MessageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F231F866895006AEF79 /* MessageData.swift */; };
|
||||
B7A03F331F866940006AEF79 /* MessageDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F301F866940006AEF79 /* MessageDateHeaderView.swift */; };
|
||||
B7A03F341F866940006AEF79 /* MessageFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F311F866940006AEF79 /* MessageFooterView.swift */; };
|
||||
B7A03F351F866940006AEF79 /* MessageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F321F866940006AEF79 /* MessageHeaderView.swift */; };
|
||||
B7A03F3A1F866946006AEF79 /* TextMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F361F866946006AEF79 /* TextMessageCell.swift */; };
|
||||
B7A03F3C1F866946006AEF79 /* LocationMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F381F866946006AEF79 /* LocationMessageCell.swift */; };
|
||||
B7A03F3D1F866946006AEF79 /* MediaMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F391F866946006AEF79 /* MediaMessageCell.swift */; };
|
||||
@@ -77,12 +80,10 @@
|
||||
B7A03F601F8669CA006AEF79 /* MessagesDisplayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F561F8669C9006AEF79 /* MessagesDisplayDelegate.swift */; };
|
||||
B7A03F611F8669CA006AEF79 /* MessagesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F571F8669CA006AEF79 /* MessagesDataSource.swift */; };
|
||||
B7A03F621F8669CA006AEF79 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F581F8669CA006AEF79 /* MessageInputBarDelegate.swift */; };
|
||||
B7A03F6A1F8669EB006AEF79 /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F641F8669EB006AEF79 /* UICollectionView+Extensions.swift */; };
|
||||
B7A03F6B1F8669EB006AEF79 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F651F8669EB006AEF79 /* UIColor+Extensions.swift */; };
|
||||
B7A03F6C1F8669EB006AEF79 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F661F8669EB006AEF79 /* UIView+Extensions.swift */; };
|
||||
B7A03F6D1F8669EB006AEF79 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F671F8669EB006AEF79 /* Bundle+Extensions.swift */; };
|
||||
B7A03F6E1F8669EB006AEF79 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F681F8669EB006AEF79 /* NSAttributedString+Extensions.swift */; };
|
||||
B7A03F6F1F8669EB006AEF79 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F691F8669EB006AEF79 /* String+Extensions.swift */; };
|
||||
B7A03F731F866A06006AEF79 /* MessageKit+Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F701F866A06006AEF79 /* MessageKit+Availability.swift */; };
|
||||
B7A03F751F866A06006AEF79 /* MessageKit.h in Headers */ = {isa = PBXBuildFile; fileRef = B7A03F721F866A06006AEF79 /* MessageKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B7A03F7B1F866B85006AEF79 /* MessageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A03F7A1F866B85006AEF79 /* MessageCollectionViewCell.swift */; };
|
||||
@@ -114,21 +115,26 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0EE91E651FDEC887005420A2 /* CGRect+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGRect+Extensions.swift"; sourceTree = "<group>"; };
|
||||
0EF0888B206F7E83007F2F58 /* CellSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellSizeCalculator.swift; sourceTree = "<group>"; };
|
||||
1F066E101FD90A0600E11013 /* MessageLabelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLabelSpec.swift; sourceTree = "<group>"; };
|
||||
1F066E1C1FDA3C1700E11013 /* SenderSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SenderSpec.swift; sourceTree = "<group>"; };
|
||||
1F066E201FDA3DEA00E11013 /* AvatarSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarSpec.swift; sourceTree = "<group>"; };
|
||||
1F066E221FDA3F0200E11013 /* DetectorTypeSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectorTypeSpec.swift; sourceTree = "<group>"; };
|
||||
1F6C040B206A2891007BDE44 /* MessageContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContentCell.swift; sourceTree = "<group>"; };
|
||||
1F6C040D206A2AF4007BDE44 /* MessageReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReusableView.swift; sourceTree = "<group>"; };
|
||||
1F7FC8C31FD26F22006CC979 /* Carthage */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Carthage; sourceTree = "<group>"; };
|
||||
1F7FC8C51FD26F33006CC979 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/iOS/Quick.framework; sourceTree = "<group>"; };
|
||||
1F7FC8C71FD26F49006CC979 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/iOS/Nimble.framework; sourceTree = "<group>"; };
|
||||
1F7FC8CB1FD2700B006CC979 /* MessagesViewControllerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesViewControllerSpec.swift; sourceTree = "<group>"; };
|
||||
1F82D1421FB1B75B00B81A88 /* AvatarPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarPosition.swift; sourceTree = "<group>"; };
|
||||
1FAC3487201E44F7000A1504 /* MessagesCollectionViewFlowLayout+Labels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessagesCollectionViewFlowLayout+Labels.swift"; sourceTree = "<group>"; };
|
||||
1FAC3489201E4DE7000A1504 /* MessagesCollectionViewFlowLayout+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessagesCollectionViewFlowLayout+Avatar.swift"; sourceTree = "<group>"; };
|
||||
1FAC348B201E4F88000A1504 /* MessagesCollectionViewFlowLayout+MessageContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessagesCollectionViewFlowLayout+MessageContainerView.swift"; sourceTree = "<group>"; };
|
||||
1FAC348F201E50DF000A1504 /* MessageCellLayoutContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCellLayoutContext.swift; sourceTree = "<group>"; };
|
||||
1FAC3491201E5346000A1504 /* MessagesCollectionViewFlowLayout+CellSizing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessagesCollectionViewFlowLayout+CellSizing.swift"; sourceTree = "<group>"; };
|
||||
1FCA6D2F201C1CC900BC3480 /* UIEdgeInsets+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Extensions.swift"; sourceTree = "<group>"; };
|
||||
1FD5895F2064E08A004B5081 /* MediaItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaItem.swift; sourceTree = "<group>"; };
|
||||
1FD5896320660C1C004B5081 /* LocationItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationItem.swift; sourceTree = "<group>"; };
|
||||
1FE7839D20662835007FA024 /* MessageSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSizeCalculator.swift; sourceTree = "<group>"; };
|
||||
1FE783A120662905007FA024 /* TextMessageSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageSizeCalculator.swift; sourceTree = "<group>"; };
|
||||
1FE783A3206629A5007FA024 /* MediaMessageSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaMessageSizeCalculator.swift; sourceTree = "<group>"; };
|
||||
1FE783A5206629C2007FA024 /* LocationMessageSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationMessageSizeCalculator.swift; sourceTree = "<group>"; };
|
||||
1FE783A7206633C0007FA024 /* InsetLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLabel.swift; sourceTree = "<group>"; };
|
||||
1FF377A320087C82004FD648 /* MessageKitError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageKitError.swift; sourceTree = "<group>"; };
|
||||
1FF377A920087D78004FD648 /* MessagesViewController+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessagesViewController+Menu.swift"; sourceTree = "<group>"; };
|
||||
1FF377AB20087DA2004FD648 /* MessagesViewController+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessagesViewController+Keyboard.swift"; sourceTree = "<group>"; };
|
||||
@@ -163,8 +169,6 @@
|
||||
B7A03F221F866895006AEF79 /* LabelAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelAlignment.swift; sourceTree = "<group>"; };
|
||||
B7A03F231F866895006AEF79 /* MessageData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageData.swift; sourceTree = "<group>"; };
|
||||
B7A03F301F866940006AEF79 /* MessageDateHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDateHeaderView.swift; sourceTree = "<group>"; };
|
||||
B7A03F311F866940006AEF79 /* MessageFooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageFooterView.swift; sourceTree = "<group>"; };
|
||||
B7A03F321F866940006AEF79 /* MessageHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHeaderView.swift; sourceTree = "<group>"; };
|
||||
B7A03F361F866946006AEF79 /* TextMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextMessageCell.swift; sourceTree = "<group>"; };
|
||||
B7A03F381F866946006AEF79 /* LocationMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationMessageCell.swift; sourceTree = "<group>"; };
|
||||
B7A03F391F866946006AEF79 /* MediaMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaMessageCell.swift; sourceTree = "<group>"; };
|
||||
@@ -184,12 +188,10 @@
|
||||
B7A03F561F8669C9006AEF79 /* MessagesDisplayDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDisplayDelegate.swift; sourceTree = "<group>"; };
|
||||
B7A03F571F8669CA006AEF79 /* MessagesDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDataSource.swift; sourceTree = "<group>"; };
|
||||
B7A03F581F8669CA006AEF79 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = "<group>"; };
|
||||
B7A03F641F8669EB006AEF79 /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = "<group>"; };
|
||||
B7A03F651F8669EB006AEF79 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
|
||||
B7A03F661F8669EB006AEF79 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = "<group>"; };
|
||||
B7A03F671F8669EB006AEF79 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = "<group>"; };
|
||||
B7A03F681F8669EB006AEF79 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = "<group>"; };
|
||||
B7A03F691F8669EB006AEF79 /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
||||
B7A03F701F866A06006AEF79 /* MessageKit+Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageKit+Availability.swift"; sourceTree = "<group>"; };
|
||||
B7A03F711F866A06006AEF79 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B7A03F721F866A06006AEF79 /* MessageKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MessageKit.h; sourceTree = "<group>"; };
|
||||
@@ -233,6 +235,7 @@
|
||||
B7A03F381F866946006AEF79 /* LocationMessageCell.swift */,
|
||||
B7A03F391F866946006AEF79 /* MediaMessageCell.swift */,
|
||||
B7A03F7A1F866B85006AEF79 /* MessageCollectionViewCell.swift */,
|
||||
1F6C040B206A2891007BDE44 /* MessageContentCell.swift */,
|
||||
B7A03F361F866946006AEF79 /* TextMessageCell.swift */,
|
||||
);
|
||||
path = Cells;
|
||||
@@ -242,8 +245,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B7A03F301F866940006AEF79 /* MessageDateHeaderView.swift */,
|
||||
B7A03F311F866940006AEF79 /* MessageFooterView.swift */,
|
||||
B7A03F321F866940006AEF79 /* MessageHeaderView.swift */,
|
||||
1F6C040D206A2AF4007BDE44 /* MessageReusableView.swift */,
|
||||
);
|
||||
path = "Headers & Footers";
|
||||
sourceTree = "<group>";
|
||||
@@ -372,8 +374,6 @@
|
||||
0EE91E651FDEC887005420A2 /* CGRect+Extensions.swift */,
|
||||
B7A03F671F8669EB006AEF79 /* Bundle+Extensions.swift */,
|
||||
B7A03F681F8669EB006AEF79 /* NSAttributedString+Extensions.swift */,
|
||||
B7A03F691F8669EB006AEF79 /* String+Extensions.swift */,
|
||||
B7A03F641F8669EB006AEF79 /* UICollectionView+Extensions.swift */,
|
||||
B7A03F651F8669EB006AEF79 /* UIColor+Extensions.swift */,
|
||||
B7A03F661F8669EB006AEF79 /* UIView+Extensions.swift */,
|
||||
1FCA6D2F201C1CC900BC3480 /* UIEdgeInsets+Extensions.swift */,
|
||||
@@ -408,6 +408,7 @@
|
||||
B7A03F411F86694F006AEF79 /* InputBarItem.swift */,
|
||||
B7A03F401F86694F006AEF79 /* InputTextView.swift */,
|
||||
38C57C7B1F9AE4870043CC03 /* InputStackView.swift */,
|
||||
1FE783A7206633C0007FA024 /* InsetLabel.swift */,
|
||||
B7A03F431F86694F006AEF79 /* MessageContainerView.swift */,
|
||||
B7A03F421F86694F006AEF79 /* MessageInputBar.swift */,
|
||||
B7A03F3F1F86694F006AEF79 /* MessageLabel.swift */,
|
||||
@@ -428,6 +429,8 @@
|
||||
B7A03F561F8669C9006AEF79 /* MessagesDisplayDelegate.swift */,
|
||||
B7A03F541F8669C9006AEF79 /* MessagesLayoutDelegate.swift */,
|
||||
B7A03F511F8669C9006AEF79 /* MessageType.swift */,
|
||||
1FD5895F2064E08A004B5081 /* MediaItem.swift */,
|
||||
1FD5896320660C1C004B5081 /* LocationItem.swift */,
|
||||
);
|
||||
path = Protocols;
|
||||
sourceTree = "<group>";
|
||||
@@ -446,12 +449,12 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B7A03F161F86682C006AEF79 /* MessagesCollectionViewFlowLayout.swift */,
|
||||
1FAC3487201E44F7000A1504 /* MessagesCollectionViewFlowLayout+Labels.swift */,
|
||||
1FAC3489201E4DE7000A1504 /* MessagesCollectionViewFlowLayout+Avatar.swift */,
|
||||
1FAC348B201E4F88000A1504 /* MessagesCollectionViewFlowLayout+MessageContainerView.swift */,
|
||||
1FAC3491201E5346000A1504 /* MessagesCollectionViewFlowLayout+CellSizing.swift */,
|
||||
B7A03F171F86682C006AEF79 /* MessagesCollectionViewLayoutAttributes.swift */,
|
||||
1FAC348F201E50DF000A1504 /* MessageCellLayoutContext.swift */,
|
||||
1FE7839D20662835007FA024 /* MessageSizeCalculator.swift */,
|
||||
1FE783A120662905007FA024 /* TextMessageSizeCalculator.swift */,
|
||||
1FE783A3206629A5007FA024 /* MediaMessageSizeCalculator.swift */,
|
||||
1FE783A5206629C2007FA024 /* LocationMessageSizeCalculator.swift */,
|
||||
0EF0888B206F7E83007F2F58 /* CellSizeCalculator.swift */,
|
||||
);
|
||||
path = Layout;
|
||||
sourceTree = "<group>";
|
||||
@@ -598,56 +601,56 @@
|
||||
1FF377AA20087D78004FD648 /* MessagesViewController+Menu.swift in Sources */,
|
||||
38C57C7C1F9AE4890043CC03 /* InputStackView.swift in Sources */,
|
||||
B7A03F5B1F8669CA006AEF79 /* MessageType.swift in Sources */,
|
||||
B7A03F6F1F8669EB006AEF79 /* String+Extensions.swift in Sources */,
|
||||
1FAC348A201E4DE7000A1504 /* MessagesCollectionViewFlowLayout+Avatar.swift in Sources */,
|
||||
B7A03F491F86694F006AEF79 /* InputBarItem.swift in Sources */,
|
||||
B7A03F601F8669CA006AEF79 /* MessagesDisplayDelegate.swift in Sources */,
|
||||
1FE783A8206633C0007FA024 /* InsetLabel.swift in Sources */,
|
||||
B7A03F5C1F8669CA006AEF79 /* MessageCellDelegate.swift in Sources */,
|
||||
1FF377A420087C82004FD648 /* MessageKitError.swift in Sources */,
|
||||
B7A03F4A1F86694F006AEF79 /* MessageInputBar.swift in Sources */,
|
||||
1F6C040E206A2AF4007BDE44 /* MessageReusableView.swift in Sources */,
|
||||
B7A03F4B1F86694F006AEF79 /* MessageContainerView.swift in Sources */,
|
||||
B7A03F281F866895006AEF79 /* LocationMessageSnapshotOptions.swift in Sources */,
|
||||
1FAC3490201E50DF000A1504 /* MessageCellLayoutContext.swift in Sources */,
|
||||
B7A03F481F86694F006AEF79 /* InputTextView.swift in Sources */,
|
||||
1FAC3488201E44F7000A1504 /* MessagesCollectionViewFlowLayout+Labels.swift in Sources */,
|
||||
B7A03F6C1F8669EB006AEF79 /* UIView+Extensions.swift in Sources */,
|
||||
B7A03F3A1F866946006AEF79 /* TextMessageCell.swift in Sources */,
|
||||
B7A03F191F86682C006AEF79 /* MessagesCollectionViewLayoutAttributes.swift in Sources */,
|
||||
B7A03F461F86694F006AEF79 /* AvatarView.swift in Sources */,
|
||||
1FCA6D30201C1CC900BC3480 /* UIEdgeInsets+Extensions.swift in Sources */,
|
||||
B7A03F3D1F866946006AEF79 /* MediaMessageCell.swift in Sources */,
|
||||
1FE783A220662905007FA024 /* TextMessageSizeCalculator.swift in Sources */,
|
||||
B7A03F331F866940006AEF79 /* MessageDateHeaderView.swift in Sources */,
|
||||
B7A03F2E1F866895006AEF79 /* MessageData.swift in Sources */,
|
||||
B7A03F7B1F866B85006AEF79 /* MessageCollectionViewCell.swift in Sources */,
|
||||
B7A03F6B1F8669EB006AEF79 /* UIColor+Extensions.swift in Sources */,
|
||||
B7A03F5E1F8669CA006AEF79 /* MessagesLayoutDelegate.swift in Sources */,
|
||||
B7A03F261F866895006AEF79 /* MessageKitDateFormatter.swift in Sources */,
|
||||
1FAC3492201E5346000A1504 /* MessagesCollectionViewFlowLayout+CellSizing.swift in Sources */,
|
||||
8962AC9A1F87ABB20030B058 /* MockMessagesDataSource.swift in Sources */,
|
||||
B7A03F6D1F8669EB006AEF79 /* Bundle+Extensions.swift in Sources */,
|
||||
B7A03F6A1F8669EB006AEF79 /* UICollectionView+Extensions.swift in Sources */,
|
||||
8962AC9C1F87ABC60030B058 /* MockMessage.swift in Sources */,
|
||||
1FD589602064E08A004B5081 /* MediaItem.swift in Sources */,
|
||||
B7A03F611F8669CA006AEF79 /* MessagesDataSource.swift in Sources */,
|
||||
B7A03F6E1F8669EB006AEF79 /* NSAttributedString+Extensions.swift in Sources */,
|
||||
B7A03F471F86694F006AEF79 /* MessageLabel.swift in Sources */,
|
||||
B7A03F5F1F8669CA006AEF79 /* MessageLabelDelegate.swift in Sources */,
|
||||
B7A03F4C1F86694F006AEF79 /* PlayButtonView.swift in Sources */,
|
||||
1FD5896420660C1C004B5081 /* LocationItem.swift in Sources */,
|
||||
B7A03F291F866895006AEF79 /* Sender.swift in Sources */,
|
||||
B7A03F4F1F86697C006AEF79 /* MessagesViewController.swift in Sources */,
|
||||
B7A03F251F866895006AEF79 /* NSConstraintLayoutSet.swift in Sources */,
|
||||
B7A03F341F866940006AEF79 /* MessageFooterView.swift in Sources */,
|
||||
B7A03F621F8669CA006AEF79 /* MessageInputBarDelegate.swift in Sources */,
|
||||
0EE91E661FDEC888005420A2 /* CGRect+Extensions.swift in Sources */,
|
||||
B7A03F181F86682C006AEF79 /* MessagesCollectionViewFlowLayout.swift in Sources */,
|
||||
B7A03F2A1F866895006AEF79 /* MessageStyle.swift in Sources */,
|
||||
B7A03F4D1F86694F006AEF79 /* MessagesCollectionView.swift in Sources */,
|
||||
0EF0888C206F7E83007F2F58 /* CellSizeCalculator.swift in Sources */,
|
||||
B7A03F351F866940006AEF79 /* MessageHeaderView.swift in Sources */,
|
||||
1FE783A4206629A5007FA024 /* MediaMessageSizeCalculator.swift in Sources */,
|
||||
B7A03F731F866A06006AEF79 /* MessageKit+Availability.swift in Sources */,
|
||||
1FE7839E20662835007FA024 /* MessageSizeCalculator.swift in Sources */,
|
||||
1FE783A6206629C2007FA024 /* LocationMessageSizeCalculator.swift in Sources */,
|
||||
B7A03F2D1F866895006AEF79 /* LabelAlignment.swift in Sources */,
|
||||
38C57C791F9AE3E50043CC03 /* SeparatorLine.swift in Sources */,
|
||||
1F6C040C206A2891007BDE44 /* MessageContentCell.swift in Sources */,
|
||||
B7A03F2C1F866895006AEF79 /* DetectorType.swift in Sources */,
|
||||
B7A03F271F866895006AEF79 /* Avatar.swift in Sources */,
|
||||
1FAC348C201E4F88000A1504 /* MessagesCollectionViewFlowLayout+MessageContainerView.swift in Sources */,
|
||||
1F82D1431FB1B75B00B81A88 /* AvatarPosition.swift in Sources */,
|
||||
1FF377AC20087DA2004FD648 /* MessagesViewController+Keyboard.swift in Sources */,
|
||||
);
|
||||
@@ -665,12 +668,14 @@
|
||||
1F066E231FDA3F0200E11013 /* DetectorTypeSpec.swift in Sources */,
|
||||
1F066E211FDA3DEA00E11013 /* AvatarSpec.swift in Sources */,
|
||||
8962AC901F87AB7D0030B058 /* MessageDateHeaderViewTests.swift in Sources */,
|
||||
1FD589612064E1B5004B5081 /* MockMessagesDataSource.swift in Sources */,
|
||||
8962AC8F1F87AB7D0030B058 /* MessageInputBarTests.swift in Sources */,
|
||||
1F066E131FD90BB600E11013 /* MessagesViewControllerSpec.swift in Sources */,
|
||||
1F066E1D1FDA3C1700E11013 /* SenderSpec.swift in Sources */,
|
||||
8962AC8A1F87AB7D0030B058 /* MessagesCollectionViewTests.swift in Sources */,
|
||||
8962AC8D1F87AB7D0030B058 /* MessageCollectionViewCellTests.swift in Sources */,
|
||||
8962AC991F87AB860030B058 /* MessagesDisplayDelegateTests.swift in Sources */,
|
||||
1FD589622064E1B9004B5081 /* MockMessage.swift in Sources */,
|
||||
8962AC8B1F87AB7D0030B058 /* InputBarItemTests.swift in Sources */,
|
||||
8962AC951F87AB860030B058 /* MessageStyleTests.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -28,13 +28,13 @@ extension MessagesViewController {
|
||||
|
||||
// MARK: - Register / Unregister Observers
|
||||
|
||||
func addKeyboardObservers() {
|
||||
internal func addKeyboardObservers() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.handleKeyboardDidChangeState(_:)), name: .UIKeyboardWillChangeFrame, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.handleTextViewDidBeginEditing(_:)), name: .UITextViewTextDidBeginEditing, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.adjustScrollViewInset), name: .UIDeviceOrientationDidChange, object: nil)
|
||||
}
|
||||
|
||||
func removeKeyboardObservers() {
|
||||
internal func removeKeyboardObservers() {
|
||||
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillChangeFrame, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: .UITextViewTextDidBeginEditing, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: .UIDeviceOrientationDidChange, object: nil)
|
||||
@@ -72,7 +72,7 @@ extension MessagesViewController {
|
||||
}
|
||||
|
||||
@objc
|
||||
func adjustScrollViewInset() {
|
||||
internal func adjustScrollViewInset() {
|
||||
if #available(iOS 11.0, *) {
|
||||
// No need to add to the top contentInset
|
||||
} else {
|
||||
@@ -86,7 +86,7 @@ extension MessagesViewController {
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
var keyboardOffsetFrame: CGRect {
|
||||
internal var keyboardOffsetFrame: CGRect {
|
||||
guard let inputFrame = inputAccessoryView?.frame else { return .zero }
|
||||
return CGRect(origin: inputFrame.origin, size: CGSize(width: inputFrame.width, height: inputFrame.height - iPhoneXBottomInset))
|
||||
}
|
||||
|
||||
@@ -28,13 +28,11 @@ extension MessagesViewController {
|
||||
|
||||
// MARK: - Register / Unregister Observers
|
||||
|
||||
/// Add observer for `UIMenuControllerWillShowMenu` notification
|
||||
func addMenuControllerObservers() {
|
||||
internal func addMenuControllerObservers() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.menuControllerWillShow(_:)), name: .UIMenuControllerWillShowMenu, object: nil)
|
||||
}
|
||||
|
||||
/// Remove observer for `UIMenuControllerWillShowMenu` notification
|
||||
func removeMenuControllerObservers() {
|
||||
internal func removeMenuControllerObservers() {
|
||||
NotificationCenter.default.removeObserver(self, name: .UIMenuControllerWillShowMenu, object: nil)
|
||||
}
|
||||
|
||||
@@ -57,7 +55,7 @@ extension MessagesViewController {
|
||||
|
||||
currentMenuController.setMenuVisible(false, animated: false)
|
||||
|
||||
guard let selectedCell = messagesCollectionView.cellForItem(at: selectedIndexPath) as? MessageCollectionViewCell else { return }
|
||||
guard let selectedCell = messagesCollectionView.cellForItem(at: selectedIndexPath) as? MessageContentCell else { return }
|
||||
let selectedCellMessageBubbleFrame = selectedCell.convert(selectedCell.messageContainerView.frame, to: view)
|
||||
|
||||
var messageInputBarFrame: CGRect = .zero
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
/// A subclass of `UIViewController` with a `MessagesCollectionView` object
|
||||
/// that is used to display conversation interfaces.
|
||||
open class MessagesViewController: UIViewController,
|
||||
UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
|
||||
// MARK: - Properties [Public]
|
||||
|
||||
/// The `MessagesCollectionView` managed by the messages view controller object.
|
||||
open var messagesCollectionView = MessagesCollectionView()
|
||||
@@ -59,13 +59,11 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
/// A Boolean value used to determine if `viewDidLayoutSubviews()` has been called.
|
||||
private var isFirstLayout: Bool = true
|
||||
|
||||
/// Indicated selected indexPath when handle menu action
|
||||
var selectedIndexPathForMenu: IndexPath?
|
||||
|
||||
var messageCollectionViewBottomInset: CGFloat = 0 {
|
||||
internal var selectedIndexPathForMenu: IndexPath?
|
||||
|
||||
internal var messageCollectionViewBottomInset: CGFloat = 0 {
|
||||
didSet {
|
||||
messagesCollectionView.contentInset.bottom = messageCollectionViewBottomInset
|
||||
messagesCollectionView.scrollIndicatorInsets.bottom = messageCollectionViewBottomInset
|
||||
@@ -106,7 +104,6 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
|
||||
// MARK: - Methods [Private]
|
||||
|
||||
/// Sets the default values for the MessagesViewController
|
||||
private func setupDefaults() {
|
||||
extendedLayoutIncludesOpaqueBars = true
|
||||
automaticallyAdjustsScrollViewInsets = false
|
||||
@@ -115,29 +112,24 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
messagesCollectionView.alwaysBounceVertical = true
|
||||
}
|
||||
|
||||
/// Sets the delegate and dataSource of the messagesCollectionView property.
|
||||
private func setupDelegates() {
|
||||
messagesCollectionView.delegate = self
|
||||
messagesCollectionView.dataSource = self
|
||||
}
|
||||
|
||||
/// Adds the messagesCollectionView to the controllers root view.
|
||||
private func setupSubviews() {
|
||||
view.addSubview(messagesCollectionView)
|
||||
}
|
||||
|
||||
/// Registers all cells and supplementary views of the messagesCollectionView property.
|
||||
private func registerReusableViews() {
|
||||
messagesCollectionView.register(TextMessageCell.self)
|
||||
messagesCollectionView.register(MediaMessageCell.self)
|
||||
messagesCollectionView.register(LocationMessageCell.self)
|
||||
|
||||
messagesCollectionView.register(MessageFooterView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter)
|
||||
messagesCollectionView.register(MessageHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader)
|
||||
messagesCollectionView.register(MessageReusableView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader)
|
||||
messagesCollectionView.register(MessageDateHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader)
|
||||
}
|
||||
|
||||
/// Sets the constraints of the `MessagesCollectionView`.
|
||||
private func setupConstraints() {
|
||||
messagesCollectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
@@ -160,17 +152,14 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
guard let collectionView = collectionView as? MessagesCollectionView else {
|
||||
fatalError(MessageKitError.notMessagesCollectionView)
|
||||
}
|
||||
// Each message is its own section
|
||||
return collectionView.messagesDataSource?.numberOfMessages(in: collectionView) ?? 0
|
||||
return collectionView.messagesDataSource?.numberOfSections(in: collectionView) ?? 0
|
||||
}
|
||||
|
||||
open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
guard let collectionView = collectionView as? MessagesCollectionView else {
|
||||
fatalError(MessageKitError.notMessagesCollectionView)
|
||||
}
|
||||
let messageCount = collectionView.messagesDataSource?.numberOfMessages(in: collectionView) ?? 0
|
||||
// There will only ever be 1 message per section
|
||||
return messageCount > 0 ? 1 : 0
|
||||
return collectionView.messagesDataSource?.numberOfItems(inSection: section, in: collectionView) ?? 0
|
||||
}
|
||||
|
||||
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
@@ -209,21 +198,15 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
fatalError(MessageKitError.notMessagesCollectionView)
|
||||
}
|
||||
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
|
||||
guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
|
||||
fatalError(MessageKitError.nilMessagesDisplayDelegate)
|
||||
}
|
||||
|
||||
let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
|
||||
switch kind {
|
||||
case UICollectionElementKindSectionHeader:
|
||||
return displayDelegate.messageHeaderView(for: message, at: indexPath, in: messagesCollectionView)
|
||||
return displayDelegate.messageHeaderView(for: indexPath, in: messagesCollectionView)
|
||||
case UICollectionElementKindSectionFooter:
|
||||
return displayDelegate.messageFooterView(for: message, at: indexPath, in: messagesCollectionView)
|
||||
return displayDelegate.messageFooterView(for: indexPath, in: messagesCollectionView)
|
||||
default:
|
||||
fatalError(MessageKitError.unrecognizedSectionKind)
|
||||
}
|
||||
@@ -241,32 +224,20 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
|
||||
fatalError(MessageKitError.notMessagesCollectionView)
|
||||
}
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else {
|
||||
fatalError(MessageKitError.nilMessagesLayoutDelegate)
|
||||
}
|
||||
// Could pose a problem if subclass behaviors allows more than one item per section
|
||||
let indexPath = IndexPath(item: 0, section: section)
|
||||
let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
return layoutDelegate.headerViewSize(for: message, at: indexPath, in: messagesCollectionView)
|
||||
return layoutDelegate.headerViewSize(for: section, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
|
||||
guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
|
||||
fatalError(MessageKitError.notMessagesCollectionView)
|
||||
}
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else {
|
||||
fatalError(MessageKitError.nilMessagesLayoutDelegate)
|
||||
}
|
||||
// Could pose a problem if subclass behaviors allows more than one item per section
|
||||
let indexPath = IndexPath(item: 0, section: section)
|
||||
let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
return layoutDelegate.footerViewSize(for: message, at: indexPath, in: messagesCollectionView)
|
||||
return layoutDelegate.footerViewSize(for: section, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
open func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
|
||||
@@ -298,12 +269,14 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
pasteBoard.string = text
|
||||
case .attributedText(let attributedText):
|
||||
pasteBoard.string = attributedText.string
|
||||
case .photo(let image):
|
||||
pasteBoard.image = image
|
||||
case .photo(let mediaItem):
|
||||
pasteBoard.image = mediaItem.image ?? mediaItem.placeholderImage
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func addObservers() {
|
||||
NotificationCenter.default.addObserver(
|
||||
|
||||
@@ -26,7 +26,7 @@ import Foundation
|
||||
|
||||
extension Bundle {
|
||||
|
||||
static func messageKitAssetBundle() -> Bundle {
|
||||
internal static func messageKitAssetBundle() -> Bundle {
|
||||
let podBundle = Bundle(for: MessagesViewController.self)
|
||||
|
||||
guard let resourceBundleUrl = podBundle.url(forResource: "MessageKitAssets", withExtension: "bundle") else {
|
||||
|
||||
@@ -26,7 +26,7 @@ import Foundation
|
||||
|
||||
extension CGRect {
|
||||
|
||||
init(_ x: CGFloat, _ y: CGFloat, _ w: CGFloat, _ h: CGFloat) {
|
||||
internal init(_ x: CGFloat, _ y: CGFloat, _ w: CGFloat, _ h: CGFloat) {
|
||||
self.init(x: x, y: y, width: w, height: h)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import Foundation
|
||||
|
||||
extension NSAttributedString {
|
||||
|
||||
func height(considering width: CGFloat) -> CGFloat {
|
||||
internal func height(considering width: CGFloat) -> CGFloat {
|
||||
|
||||
let constraintBox = CGSize(width: width, height: .greatestFiniteMagnitude)
|
||||
let rect = self.boundingRect(with: constraintBox, options: .usesLineFragmentOrigin, context: nil)
|
||||
@@ -34,7 +34,7 @@ extension NSAttributedString {
|
||||
|
||||
}
|
||||
|
||||
func width(considering height: CGFloat) -> CGFloat {
|
||||
internal func width(considering height: CGFloat) -> CGFloat {
|
||||
|
||||
let constraintBox = CGSize(width: .greatestFiniteMagnitude, height: height)
|
||||
let rect = self.boundingRect(with: constraintBox, options: .usesLineFragmentOrigin, context: nil)
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Optional Cell Protocol to Simplify registration/cell type loading in a generic way
|
||||
public protocol CollectionViewReusable: AnyObject {
|
||||
static func reuseIdentifier() -> String
|
||||
}
|
||||
|
||||
public extension MessagesCollectionView {
|
||||
/// Registers a particular cell using its reuse-identifier
|
||||
func register<CellType: UICollectionViewCell & CollectionViewReusable>(_ cellClass: CellType.Type) {
|
||||
register(cellClass, forCellWithReuseIdentifier: CellType.reuseIdentifier())
|
||||
}
|
||||
|
||||
/// Registers a reusable view for a specific SectionKind
|
||||
func register<ViewType: UICollectionReusableView & CollectionViewReusable>(_ 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<CellType: UICollectionViewCell & CollectionViewReusable>(_ 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
|
||||
}
|
||||
|
||||
/// Generically dequeues a header of the correct type allowing you to avoid scattering your code with guard-let-else-fatal
|
||||
func dequeueReusableHeaderView<ViewType: UICollectionReusableView & CollectionViewReusable>(_ viewClass: ViewType.Type, for indexPath: IndexPath) -> ViewType {
|
||||
let view = dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: viewClass.reuseIdentifier(), for: indexPath)
|
||||
guard let viewType = view as? ViewType else {
|
||||
fatalError("Unable to dequeue \(String(describing: viewClass)) with reuseId of \(viewClass.reuseIdentifier())")
|
||||
}
|
||||
return viewType
|
||||
}
|
||||
|
||||
/// Generically dequeues a footer of the correct type allowing you to avoid scattering your code with guard-let-else-fatal
|
||||
func dequeueReusableFooterView<ViewType: UICollectionReusableView & CollectionViewReusable>(_ viewClass: ViewType.Type, for indexPath: IndexPath) -> ViewType {
|
||||
let view = dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionFooter, withReuseIdentifier: viewClass.reuseIdentifier(), for: indexPath)
|
||||
guard let viewType = view as? ViewType else {
|
||||
fatalError("Unable to dequeue \(String(describing: viewClass)) with reuseId of \(viewClass.reuseIdentifier())")
|
||||
}
|
||||
return viewType
|
||||
}
|
||||
}
|
||||
@@ -26,14 +26,14 @@ import Foundation
|
||||
|
||||
extension UIColor {
|
||||
|
||||
static let incomingGray = UIColor(red: 230/255, green: 230/255, blue: 235/255, alpha: 1.0)
|
||||
internal static let incomingGray = UIColor(red: 230/255, green: 230/255, blue: 235/255, alpha: 1.0)
|
||||
|
||||
static let outgoingGreen = UIColor(red: 69/255, green: 214/255, blue: 93/255, alpha: 1.0)
|
||||
internal static let outgoingGreen = UIColor(red: 69/255, green: 214/255, blue: 93/255, alpha: 1.0)
|
||||
|
||||
static let inputBarGray = UIColor(red: 247/255, green: 247/255, blue: 247/255, alpha: 1.0)
|
||||
internal static let inputBarGray = UIColor(red: 247/255, green: 247/255, blue: 247/255, alpha: 1.0)
|
||||
|
||||
static let playButtonLightGray = UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1.0)
|
||||
internal static let playButtonLightGray = UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1.0)
|
||||
|
||||
static let sendButtonBlue = UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0)
|
||||
internal static let sendButtonBlue = UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0)
|
||||
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ import Foundation
|
||||
|
||||
extension UIEdgeInsets {
|
||||
|
||||
var vertical: CGFloat {
|
||||
internal var vertical: CGFloat {
|
||||
return top + bottom
|
||||
}
|
||||
|
||||
var horizontal: CGFloat {
|
||||
internal var horizontal: CGFloat {
|
||||
return left + right
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import UIKit
|
||||
|
||||
extension UIView {
|
||||
|
||||
func fillSuperview() {
|
||||
internal func fillSuperview() {
|
||||
guard let superview = self.superview else {
|
||||
return
|
||||
}
|
||||
@@ -41,7 +41,7 @@ extension UIView {
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
|
||||
func centerInSuperview() {
|
||||
internal func centerInSuperview() {
|
||||
guard let superview = self.superview else {
|
||||
return
|
||||
}
|
||||
@@ -53,7 +53,7 @@ extension UIView {
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
|
||||
func constraint(equalTo size: CGSize) {
|
||||
internal func constraint(equalTo size: CGSize) {
|
||||
guard superview != nil else { return }
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
let constraints: [NSLayoutConstraint] = [
|
||||
@@ -65,7 +65,7 @@ extension UIView {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func addConstraints(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] {
|
||||
internal func addConstraints(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] {
|
||||
|
||||
if self.superview == nil {
|
||||
return []
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
/// An object is responsible for
|
||||
/// sizing and configuring cells for given `IndexPath`s.
|
||||
open class CellSizeCalculator {
|
||||
|
||||
/// The layout object for which the cell size calculator is used.
|
||||
public weak var layout: UICollectionViewFlowLayout?
|
||||
|
||||
/// Used to configure the layout attributes for a given cell.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - attributes: The attributes of the cell.
|
||||
/// The default does nothing
|
||||
open func configure(attributes: UICollectionViewLayoutAttributes) {}
|
||||
|
||||
/// Used to size an item at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - indexPath: The `IndexPath` of the item to be displayed.
|
||||
/// The default return .zero
|
||||
open func sizeForItem(at indexPath: IndexPath) -> CGSize { return .zero }
|
||||
|
||||
}
|
||||
+18
-19
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
|
||||
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
|
||||
@@ -24,21 +24,20 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
||||
func height(considering width: CGFloat, and font: UIFont) -> CGFloat {
|
||||
|
||||
let constraintBox = CGSize(width: width, height: .greatestFiniteMagnitude)
|
||||
let rect = self.boundingRect(with: constraintBox, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)
|
||||
return rect.height
|
||||
|
||||
}
|
||||
|
||||
func width(considering height: CGFloat, and font: UIFont) -> CGFloat {
|
||||
|
||||
let constraintBox = CGSize(width: .greatestFiniteMagnitude, height: height)
|
||||
let rect = self.boundingRect(with: constraintBox, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)
|
||||
return rect.width
|
||||
open class LocationMessageSizeCalculator: MessageSizeCalculator {
|
||||
|
||||
open override func messageContainerSize(for message: MessageType) -> CGSize {
|
||||
switch message.data {
|
||||
case .location(let item):
|
||||
let maxWidth = messageContainerMaxWidth(for: message)
|
||||
if maxWidth < item.size.width {
|
||||
// Maintain the ratio if width is too great
|
||||
let height = maxWidth * item.size.height / item.size.width
|
||||
return CGSize(width: maxWidth, height: height)
|
||||
}
|
||||
return item.size
|
||||
default:
|
||||
fatalError("messageContainerSize received unhandled MessageDataType: \(message.data)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
open class MediaMessageSizeCalculator: MessageSizeCalculator {
|
||||
|
||||
open override func messageContainerSize(for message: MessageType) -> CGSize {
|
||||
let maxWidth = messageContainerMaxWidth(for: message)
|
||||
let sizeForMediaItem = { (maxWidth: CGFloat, item: MediaItem) -> CGSize in
|
||||
if maxWidth < item.size.width {
|
||||
// Maintain the ratio if width is too great
|
||||
let height = maxWidth * item.size.height / item.size.width
|
||||
return CGSize(width: maxWidth, height: height)
|
||||
}
|
||||
return item.size
|
||||
}
|
||||
switch message.data {
|
||||
case .photo(let item):
|
||||
return sizeForMediaItem(maxWidth, item)
|
||||
case .video(let item):
|
||||
return sizeForMediaItem(maxWidth, item)
|
||||
default:
|
||||
fatalError("messageContainerSize received unhandled MessageDataType: \(message.data)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
open class MessageSizeCalculator: CellSizeCalculator {
|
||||
|
||||
public init(layout: MessagesCollectionViewFlowLayout? = nil) {
|
||||
super.init()
|
||||
|
||||
self.layout = layout
|
||||
}
|
||||
|
||||
public var incomingAvatarSize = CGSize(width: 30, height: 30)
|
||||
public var outgoingAvatarSize = CGSize(width: 30, height: 30)
|
||||
|
||||
public var incomingAvatarPosition = AvatarPosition(vertical: .messageBottom)
|
||||
public var outgoingAvatarPosition = AvatarPosition(vertical: .messageBottom)
|
||||
|
||||
public var incomingMessagePadding = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 30)
|
||||
public var outgoingMessagePadding = UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 4)
|
||||
|
||||
public var incomingCellTopLabelAlignment = LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(left: 42))
|
||||
public var outgoingCellTopLabelAlignment = LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(right: 42))
|
||||
|
||||
public var incomingCellBottomLabelAlignment = LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(left: 42))
|
||||
public var outgoingCellBottomLabelAlignment = LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(right: 42))
|
||||
|
||||
open override func configure(attributes: UICollectionViewLayoutAttributes) {
|
||||
guard let attributes = attributes as? MessagesCollectionViewLayoutAttributes else { return }
|
||||
|
||||
let dataSource = messagesLayout.messagesDataSource
|
||||
let indexPath = attributes.indexPath
|
||||
let message = dataSource.messageForItem(at: indexPath, in: messagesLayout.messagesCollectionView)
|
||||
|
||||
attributes.avatarSize = avatarSize(for: message)
|
||||
attributes.avatarPosition = avatarPosition(for: message)
|
||||
|
||||
attributes.messageContainerPadding = messageContainerPadding(for: message)
|
||||
attributes.messageContainerSize = messageContainerSize(for: message)
|
||||
attributes.topLabelAlignment = cellTopLabelAlignment(for: message)
|
||||
attributes.topLabelSize = cellTopLabelSize(for: message, at: indexPath)
|
||||
|
||||
attributes.bottomLabelAlignment = cellBottomLabelAlignment(for: message)
|
||||
attributes.bottomLabelSize = cellBottomLabelSize(for: message, at: indexPath)
|
||||
}
|
||||
|
||||
open override func sizeForItem(at indexPath: IndexPath) -> CGSize {
|
||||
let dataSource = messagesLayout.messagesDataSource
|
||||
let message = dataSource.messageForItem(at: indexPath, in: messagesLayout.messagesCollectionView)
|
||||
let itemHeight = cellContentHeight(for: message, at: indexPath)
|
||||
return CGSize(width: messagesLayout.itemWidth, height: itemHeight)
|
||||
}
|
||||
|
||||
open func cellContentHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
let avatarVerticalPosition = avatarPosition(for: message).vertical
|
||||
let avatarHeight = avatarSize(for: message).height
|
||||
let messageContainerHeight = messageContainerSize(for: message).height
|
||||
let bottomLabelHeight = cellBottomLabelSize(for: message, at: indexPath).height
|
||||
let topLabelHeight = cellTopLabelSize(for: message, at: indexPath).height
|
||||
let messageVerticalPadding = messageContainerPadding(for: message).vertical
|
||||
|
||||
var cellHeight: CGFloat = 0
|
||||
|
||||
switch avatarVerticalPosition {
|
||||
case .cellTop:
|
||||
cellHeight += max(avatarHeight, topLabelHeight)
|
||||
cellHeight += bottomLabelHeight
|
||||
cellHeight += messageContainerHeight
|
||||
cellHeight += messageVerticalPadding
|
||||
case .cellBottom:
|
||||
cellHeight += max(avatarHeight, bottomLabelHeight)
|
||||
cellHeight += topLabelHeight
|
||||
cellHeight += messageContainerHeight
|
||||
cellHeight += messageVerticalPadding
|
||||
case .messageTop, .messageCenter, .messageBottom:
|
||||
cellHeight += max(avatarHeight, messageContainerHeight)
|
||||
cellHeight += messageVerticalPadding
|
||||
cellHeight += topLabelHeight
|
||||
cellHeight += bottomLabelHeight
|
||||
}
|
||||
|
||||
return cellHeight
|
||||
}
|
||||
|
||||
// MARK: - Avatar
|
||||
|
||||
public func avatarPosition(for message: MessageType) -> AvatarPosition {
|
||||
let dataSource = messagesLayout.messagesDataSource
|
||||
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
|
||||
var position = isFromCurrentSender ? outgoingAvatarPosition : incomingAvatarPosition
|
||||
|
||||
switch position.horizontal {
|
||||
case .cellTrailing, .cellLeading:
|
||||
break
|
||||
case .natural:
|
||||
position.horizontal = isFromCurrentSender ? .cellTrailing : .cellLeading
|
||||
}
|
||||
return position
|
||||
}
|
||||
|
||||
public func avatarSize(for message: MessageType) -> CGSize {
|
||||
let dataSource = messagesLayout.messagesDataSource
|
||||
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
|
||||
return isFromCurrentSender ? outgoingAvatarSize : incomingAvatarSize
|
||||
}
|
||||
|
||||
// MARK: - Top Label
|
||||
|
||||
public func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
let layoutDelegate = messagesLayout.messagesLayoutDelegate
|
||||
let collectionView = messagesLayout.messagesCollectionView
|
||||
let height = layoutDelegate.cellTopLabelHeight(for: message, at: indexPath, in: collectionView)
|
||||
return CGSize(width: messagesLayout.itemWidth, height: height)
|
||||
}
|
||||
|
||||
public func cellTopLabelAlignment(for message: MessageType) -> LabelAlignment {
|
||||
let dataSource = messagesLayout.messagesDataSource
|
||||
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
|
||||
return isFromCurrentSender ? outgoingCellTopLabelAlignment : incomingCellTopLabelAlignment
|
||||
}
|
||||
|
||||
// MARK: - Bottom Label
|
||||
|
||||
public func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
let layoutDelegate = messagesLayout.messagesLayoutDelegate
|
||||
let collectionView = messagesLayout.messagesCollectionView
|
||||
let height = layoutDelegate.cellBottomLabelHeight(for: message, at: indexPath, in: collectionView)
|
||||
return CGSize(width: messagesLayout.itemWidth, height: height)
|
||||
}
|
||||
|
||||
public func cellBottomLabelAlignment(for message: MessageType) -> LabelAlignment {
|
||||
let dataSource = messagesLayout.messagesDataSource
|
||||
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
|
||||
return isFromCurrentSender ? outgoingCellBottomLabelAlignment : incomingCellBottomLabelAlignment
|
||||
}
|
||||
|
||||
// MARK: - MessageContainer
|
||||
|
||||
public func messageContainerPadding(for message: MessageType) -> UIEdgeInsets {
|
||||
let dataSource = messagesLayout.messagesDataSource
|
||||
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
|
||||
return isFromCurrentSender ? outgoingMessagePadding : incomingMessagePadding
|
||||
}
|
||||
|
||||
open func messageContainerSize(for message: MessageType) -> CGSize {
|
||||
// Returns .zero by default
|
||||
return .zero
|
||||
}
|
||||
|
||||
open func messageContainerMaxWidth(for message: MessageType) -> CGFloat {
|
||||
let avatarWidth = avatarSize(for: message).width
|
||||
let messagePadding = messageContainerPadding(for: message)
|
||||
return messagesLayout.itemWidth - avatarWidth - messagePadding.horizontal
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
public var messagesLayout: MessagesCollectionViewFlowLayout {
|
||||
guard let layout = layout as? MessagesCollectionViewFlowLayout else {
|
||||
fatalError("Layout object is missing or is not a MessagesCollectionViewFlowLayout")
|
||||
}
|
||||
return layout
|
||||
}
|
||||
|
||||
internal func labelSize(for attributedText: NSAttributedString, considering maxWidth: CGFloat) -> CGSize {
|
||||
let estimatedHeight = attributedText.height(considering: maxWidth)
|
||||
let estimatedWidth = attributedText.width(considering: estimatedHeight)
|
||||
|
||||
let finalHeight = ceil(estimatedHeight)
|
||||
let finalWidth = estimatedWidth > maxWidth ? maxWidth : ceil(estimatedWidth)
|
||||
|
||||
return CGSize(width: finalWidth, height: finalHeight)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension UIEdgeInsets {
|
||||
init(top: CGFloat = 0, bottom: CGFloat = 0, left: CGFloat = 0, right: CGFloat = 0) {
|
||||
self.init(top: top, left: left, bottom: bottom, right: right)
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Open
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
/// Returns the `AvatarPosition` for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
///
|
||||
/// - Note: The default implementation of this method retrieves its value from
|
||||
/// `avatarPosition(for:at:in)` in `MessagesLayoutDelegate`.
|
||||
open func avatarPosition(for message: MessageType, at indexPath: IndexPath) -> AvatarPosition {
|
||||
var position = messagesLayoutDelegate.avatarPosition(for: message, at: indexPath, in: messagesCollectionView)
|
||||
|
||||
switch position.horizontal {
|
||||
case .cellTrailing, .cellLeading:
|
||||
break
|
||||
case .natural:
|
||||
position.horizontal = messagesDataSource.isFromCurrentSender(message: message) ? .cellTrailing : .cellLeading
|
||||
}
|
||||
return position
|
||||
}
|
||||
|
||||
/// Returns the `AvatarSize` for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
/// - Note: The default implementation of this method retrieves its value from
|
||||
/// `avatarSize(for:at:in)` in `MessagesLayoutDelegate`.
|
||||
open func avatarSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
return messagesLayoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
internal func _avatarPosition(for message: MessageType, at indexPath: IndexPath) -> AvatarPosition {
|
||||
|
||||
guard let cachedPosition = currentLayoutContext.avatarPosition else {
|
||||
let position = avatarPosition(for: message, at: indexPath)
|
||||
currentLayoutContext.avatarPosition = position
|
||||
return position
|
||||
}
|
||||
return cachedPosition
|
||||
}
|
||||
|
||||
internal func _avatarSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
guard let cachedSize = currentLayoutContext.avatarSize else {
|
||||
let size = avatarSize(for: message, at: indexPath)
|
||||
currentLayoutContext.avatarSize = size
|
||||
return size
|
||||
}
|
||||
return cachedSize
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Open
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
/// Returns the height of a `MessageCollectionViewCell`'s content at a given `IndexPath`
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
open func cellContentHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
let avatarPosition = _avatarPosition(for: message, at: indexPath)
|
||||
let avatarHeight = _avatarSize(for: message, at: indexPath).height
|
||||
let messageContainerHeight = _messageContainerSize(for: message, at: indexPath).height
|
||||
let bottomLabelHeight = _cellBottomLabelSize(for: message, at: indexPath).height
|
||||
let topLabelHeight = _cellTopLabelSize(for: message, at: indexPath).height
|
||||
let messageVerticalPadding = _messageContainerPadding(for: message, at: indexPath).vertical
|
||||
|
||||
var cellHeight: CGFloat = 0
|
||||
|
||||
switch avatarPosition.vertical {
|
||||
case .cellTop:
|
||||
cellHeight += max(avatarHeight, topLabelHeight)
|
||||
cellHeight += bottomLabelHeight
|
||||
cellHeight += messageContainerHeight
|
||||
cellHeight += messageVerticalPadding
|
||||
case .cellBottom:
|
||||
cellHeight += max(avatarHeight, bottomLabelHeight)
|
||||
cellHeight += topLabelHeight
|
||||
cellHeight += messageContainerHeight
|
||||
cellHeight += messageVerticalPadding
|
||||
case .messageTop, .messageCenter, .messageBottom:
|
||||
cellHeight += max(avatarHeight, messageContainerHeight)
|
||||
cellHeight += messageVerticalPadding
|
||||
cellHeight += topLabelHeight
|
||||
cellHeight += bottomLabelHeight
|
||||
}
|
||||
|
||||
return cellHeight
|
||||
}
|
||||
|
||||
/// Returns the size for the `MessageCollectionViewCell` at a given `IndexPath`
|
||||
/// considering all of the cell's content.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
open func sizeForItem(at indexPath: IndexPath) -> CGSize {
|
||||
let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
let context = cellLayoutContext(for: message, at: indexPath)
|
||||
guard let itemHeight = context.itemHeight else {
|
||||
fatalError("Unexpectedly received a nil itemHeight")
|
||||
}
|
||||
return CGSize(width: itemWidth, height: itemHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
internal func _cellContentHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
guard let cachedHeight = currentLayoutContext.itemHeight else {
|
||||
let height = cellContentHeight(for: message, at: indexPath)
|
||||
currentLayoutContext.itemHeight = height
|
||||
return height
|
||||
}
|
||||
return cachedHeight
|
||||
}
|
||||
|
||||
internal func _sizeForItem(at indexPath: IndexPath) -> CGSize {
|
||||
let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
let itemHeight = cellContentHeight(for: message, at: indexPath)
|
||||
return CGSize(width: itemWidth, height: itemHeight)
|
||||
}
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Open
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
// Top Label
|
||||
|
||||
/// Returns the `LabelAlignment` of the `MessageCollectionViewCell`'s top label
|
||||
/// for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
/// - Note: The default implementation of this method retrieves its value from
|
||||
/// `cellTopLabelAlignment(for:at:in)` in `MessagesLayoutDelegate`.
|
||||
open func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath) -> LabelAlignment {
|
||||
return messagesLayoutDelegate.cellTopLabelAlignment(for: message, at: indexPath, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
/// Returns the size of the `MessageCollectionViewCell`'s top label
|
||||
/// for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
///
|
||||
/// - Note: The default implementation of this method sizes the label to fit.
|
||||
open func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
let text = messagesDataSource.cellTopLabelAttributedText(for: message, at: indexPath)
|
||||
guard let topLabelText = text else { return .zero }
|
||||
let maxWidth = _cellTopLabelMaxWidth(for: message, at: indexPath)
|
||||
return labelSize(for: topLabelText, considering: maxWidth)
|
||||
}
|
||||
|
||||
/// Returns the maximum width of the `MessageCollectionViewCell`'s top label
|
||||
/// for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
open func cellTopLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
let labelAlignment = _cellTopLabelAlignment(for: message, at: indexPath)
|
||||
let avatarPosition = _avatarPosition(for: message, at: indexPath)
|
||||
let avatarWidth = _avatarSize(for: message, at: indexPath).width
|
||||
let messageContainerSize = _messageContainerSize(for: message, at: indexPath)
|
||||
let messagePadding = _messageContainerPadding(for: message, at: indexPath)
|
||||
|
||||
let avatarHorizontal = avatarPosition.horizontal
|
||||
let avatarVertical = avatarPosition.vertical
|
||||
|
||||
switch (labelAlignment, avatarHorizontal) {
|
||||
|
||||
case (.cellLeading, _), (.cellTrailing, _):
|
||||
let width = itemWidth - labelAlignment.insets.horizontal
|
||||
return avatarVertical != .cellTop ? width : width - avatarWidth
|
||||
|
||||
case (.cellCenter, _):
|
||||
let width = itemWidth - labelAlignment.insets.horizontal
|
||||
return avatarVertical != .cellTop ? width : width - (avatarWidth * 2)
|
||||
|
||||
case (.messageTrailing, .cellLeading):
|
||||
let width = messageContainerSize.width + messagePadding.left - labelAlignment.insets.horizontal
|
||||
return avatarVertical == .cellTop ? width : width + avatarWidth
|
||||
|
||||
case (.messageLeading, .cellTrailing):
|
||||
let width = messageContainerSize.width + messagePadding.right - labelAlignment.insets.horizontal
|
||||
return avatarVertical == .cellTop ? width : width + avatarWidth
|
||||
|
||||
case (.messageLeading, .cellLeading):
|
||||
return itemWidth - avatarWidth - messagePadding.left - labelAlignment.insets.horizontal
|
||||
|
||||
case (.messageTrailing, .cellTrailing):
|
||||
return itemWidth - avatarWidth - messagePadding.right - labelAlignment.insets.horizontal
|
||||
|
||||
case (_, .natural):
|
||||
fatalError(MessageKitError.avatarPositionUnresolved)
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom Label
|
||||
|
||||
/// Returns the `LabelAlignment` of the `MessageCollectionViewCell`'s bottom label
|
||||
/// for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
/// - Note: The default implementation of this method retrieves its value from
|
||||
/// `cellBottomLabelAlignment(for:at:in)` in `MessagesLayoutDelegate`.
|
||||
open func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath) -> LabelAlignment {
|
||||
return messagesLayoutDelegate.cellBottomLabelAlignment(for: message, at: indexPath, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
/// Returns the size of the `MessageCollectionViewCell`'s bottom label
|
||||
/// for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
///
|
||||
/// - Note: The default implementation of this method sizes the label to fit.
|
||||
open func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
let text = messagesDataSource.cellBottomLabelAttributedText(for: message, at: indexPath)
|
||||
guard let bottomLabelText = text else { return .zero }
|
||||
let maxWidth = _cellBottomLabelMaxWidth(for: message, at: indexPath)
|
||||
return labelSize(for: bottomLabelText, considering: maxWidth)
|
||||
}
|
||||
|
||||
/// Returns the maximum width of the `MessageCollectionViewCell`'s bottom label
|
||||
/// for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
open func cellBottomLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
let labelAlignment = _cellBottomLabelAlignment(for: message, at: indexPath)
|
||||
let avatarWidth = _avatarSize(for: message, at: indexPath).width
|
||||
let messageContainerSize = _messageContainerSize(for: message, at: indexPath)
|
||||
let messagePadding = _messageContainerPadding(for: message, at: indexPath)
|
||||
let avatarPosition = _avatarPosition(for: message, at: indexPath)
|
||||
|
||||
let avatarHorizontal = avatarPosition.horizontal
|
||||
let avatarVertical = avatarPosition.vertical
|
||||
|
||||
switch (labelAlignment, avatarHorizontal) {
|
||||
|
||||
case (.cellLeading, _), (.cellTrailing, _):
|
||||
let width = itemWidth - labelAlignment.insets.horizontal
|
||||
return avatarVertical != .cellBottom ? width : width - avatarWidth
|
||||
|
||||
case (.cellCenter, _):
|
||||
let width = itemWidth - labelAlignment.insets.horizontal
|
||||
return avatarVertical != .cellBottom ? width : width - (avatarWidth * 2)
|
||||
|
||||
case (.messageTrailing, .cellLeading):
|
||||
let width = messageContainerSize.width + messagePadding.left - labelAlignment.insets.horizontal
|
||||
return avatarVertical == .cellBottom ? width : width + avatarWidth
|
||||
|
||||
case (.messageLeading, .cellTrailing):
|
||||
let width = messageContainerSize.width + messagePadding.right - labelAlignment.insets.horizontal
|
||||
return avatarVertical == .cellBottom ? width : width + avatarWidth
|
||||
|
||||
case (.messageLeading, .cellLeading):
|
||||
return itemWidth - avatarWidth - messagePadding.left - labelAlignment.insets.horizontal
|
||||
|
||||
case (.messageTrailing, .cellTrailing):
|
||||
return itemWidth - avatarWidth - messagePadding.right - labelAlignment.insets.horizontal
|
||||
|
||||
case (_, .natural):
|
||||
fatalError(MessageKitError.avatarPositionUnresolved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
// Top Label
|
||||
|
||||
internal func _cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath) -> LabelAlignment {
|
||||
guard let cachedAlignment = currentLayoutContext.topLabelAlignment else {
|
||||
let alignment = cellTopLabelAlignment(for: message, at: indexPath)
|
||||
currentLayoutContext.topLabelAlignment = alignment
|
||||
return alignment
|
||||
}
|
||||
return cachedAlignment
|
||||
}
|
||||
|
||||
internal func _cellTopLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
guard let cachedMaxWidth = currentLayoutContext.topLabelMaxWidth else {
|
||||
let maxWidth = cellTopLabelMaxWidth(for: message, at: indexPath)
|
||||
currentLayoutContext.topLabelMaxWidth = maxWidth
|
||||
return maxWidth
|
||||
}
|
||||
return cachedMaxWidth
|
||||
}
|
||||
|
||||
internal func _cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
guard let cachedSize = currentLayoutContext.topLabelSize else {
|
||||
let size = cellTopLabelSize(for: message, at: indexPath)
|
||||
currentLayoutContext.topLabelSize = size
|
||||
return size
|
||||
}
|
||||
return cachedSize
|
||||
}
|
||||
|
||||
// Bottom Label
|
||||
|
||||
internal func _cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath) -> LabelAlignment {
|
||||
guard let cachedAlignment = currentLayoutContext.bottomLabelAlignment else {
|
||||
let alignment = cellBottomLabelAlignment(for: message, at: indexPath)
|
||||
currentLayoutContext.bottomLabelAlignment = alignment
|
||||
return alignment
|
||||
}
|
||||
return cachedAlignment
|
||||
}
|
||||
|
||||
internal func _cellBottomLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
guard let cachedMaxWidth = currentLayoutContext.bottomLabelMaxWidth else {
|
||||
let maxWidth = cellBottomLabelMaxWidth(for: message, at: indexPath)
|
||||
currentLayoutContext.bottomLabelMaxWidth = maxWidth
|
||||
return maxWidth
|
||||
}
|
||||
return cachedMaxWidth
|
||||
}
|
||||
|
||||
internal func _cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
guard let cachedSize = currentLayoutContext.bottomLabelSize else {
|
||||
let size = cellBottomLabelSize(for: message, at: indexPath)
|
||||
currentLayoutContext.bottomLabelSize = size
|
||||
return size
|
||||
}
|
||||
return cachedSize
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
/// Returns the size required fit a NSAttributedString considering a constrained max width.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - attributedText: The `NSAttributedString` used to calculate a size that fits.
|
||||
/// - maxWidth: The max width available for the label.
|
||||
internal func labelSize(for attributedText: NSAttributedString, considering maxWidth: CGFloat) -> CGSize {
|
||||
|
||||
let estimatedHeight = attributedText.height(considering: maxWidth)
|
||||
let estimatedWidth = attributedText.width(considering: estimatedHeight)
|
||||
|
||||
let finalHeight = estimatedHeight.rounded(.up)
|
||||
let finalWidth = estimatedWidth > maxWidth ? maxWidth : estimatedWidth.rounded(.up)
|
||||
|
||||
return CGSize(width: finalWidth, height: finalHeight)
|
||||
}
|
||||
|
||||
/// Returns the size required to fit a String considering a constrained max width.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - text: The `String` used to calculate a size that fits.
|
||||
/// - maxWidth: The max width available for the label.
|
||||
internal func labelSize(for text: String, considering maxWidth: CGFloat, and font: UIFont) -> CGSize {
|
||||
|
||||
let estimatedHeight = text.height(considering: maxWidth, and: font)
|
||||
let estimatedWidth = text.width(considering: estimatedHeight, and: font)
|
||||
|
||||
let finalHeight = estimatedHeight.rounded(.up)
|
||||
let finalWidth = estimatedWidth > maxWidth ? maxWidth : estimatedWidth.rounded(.up)
|
||||
|
||||
return CGSize(width: finalWidth, height: finalHeight)
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Open
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
/// Returns the insets of the `MessageLabel` in a `TextMessageCell` for
|
||||
/// the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
///
|
||||
/// - Note: The default implementation of this method retrieves its value from
|
||||
/// `messageLabelInset(for:at:in)` in `MessagesLayoutDelegate`.
|
||||
open func messageLabelInsets(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets {
|
||||
return messagesLayoutDelegate.messageLabelInset(for: message, at: indexPath, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
/// Returns the padding around the `MessageContainerView` in a `MessageCollectionViewCell`
|
||||
/// for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
///
|
||||
/// - Note: The default implementation of this method retrieves its value from
|
||||
/// `messagePadding(for:at:in)` in `MessagesLayoutDelegate`.
|
||||
open func messageContainerPadding(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets {
|
||||
return messagesLayoutDelegate.messagePadding(for: message, at: indexPath, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
/// Returns the maximum width of the `MessageContainerView` in a `MessageCollectionViewCell`
|
||||
/// for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
open func messageContainerMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
let avatarWidth = _avatarSize(for: message, at: indexPath).width
|
||||
let messagePadding = _messageContainerPadding(for: message, at: indexPath)
|
||||
|
||||
switch message.data {
|
||||
case .text, .attributedText:
|
||||
let messageInsets = _messageLabelInsets(for: message, at: indexPath)
|
||||
return itemWidth - avatarWidth - messagePadding.horizontal - messageInsets.horizontal
|
||||
default:
|
||||
return itemWidth - avatarWidth - messagePadding.horizontal
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the size of the `MessageContainerView` in a `MessageCollectionViewCell`
|
||||
/// for the `MessageType` at a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` for the given `IndexPath`.
|
||||
/// - indexPath: The `IndexPath` for the given `MessageType`.
|
||||
open func messageContainerSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
let maxWidth = _messageContainerMaxWidth(for: message, at: indexPath)
|
||||
|
||||
var messageContainerSize: CGSize = .zero
|
||||
|
||||
switch message.data {
|
||||
case .text(let text):
|
||||
let messageLabelInsets = _messageLabelInsets(for: message, at: indexPath)
|
||||
messageContainerSize = labelSize(for: text, considering: maxWidth, and: messageLabelFont)
|
||||
messageContainerSize.width += messageLabelInsets.horizontal
|
||||
messageContainerSize.height += messageLabelInsets.vertical
|
||||
case .attributedText(let text):
|
||||
let messageLabelInsets = _messageLabelInsets(for: message, at: indexPath)
|
||||
messageContainerSize = labelSize(for: text, considering: maxWidth)
|
||||
messageContainerSize.width += messageLabelInsets.horizontal
|
||||
messageContainerSize.height += messageLabelInsets.vertical
|
||||
case .emoji(let text):
|
||||
let messageLabelInsets = _messageLabelInsets(for: message, at: indexPath)
|
||||
messageContainerSize = labelSize(for: text, considering: maxWidth, and: emojiLabelFont)
|
||||
messageContainerSize.width += messageLabelInsets.horizontal
|
||||
messageContainerSize.height += messageLabelInsets.vertical
|
||||
case .photo, .video:
|
||||
let width = messagesLayoutDelegate.widthForMedia(message: message, at: indexPath, with: maxWidth, in: messagesCollectionView)
|
||||
let height = messagesLayoutDelegate.heightForMedia(message: message, at: indexPath, with: maxWidth, in: messagesCollectionView)
|
||||
messageContainerSize = CGSize(width: width, height: height)
|
||||
case .location:
|
||||
let width = messagesLayoutDelegate.widthForLocation(message: message, at: indexPath, with: maxWidth, in: messagesCollectionView)
|
||||
let height = messagesLayoutDelegate.heightForLocation(message: message, at: indexPath, with: maxWidth, in: messagesCollectionView)
|
||||
messageContainerSize = CGSize(width: width, height: height)
|
||||
case .custom:
|
||||
fatalError(MessageKitError.customDataUnresolvedSize)
|
||||
}
|
||||
|
||||
return messageContainerSize
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
internal func _messageLabelInsets(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets {
|
||||
guard let cachedInsets = currentLayoutContext.messageLabelInsets else {
|
||||
let insets = messageLabelInsets(for: message, at: indexPath)
|
||||
currentLayoutContext.messageLabelInsets = insets
|
||||
return insets
|
||||
}
|
||||
return cachedInsets
|
||||
}
|
||||
|
||||
internal func _messageContainerPadding(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets {
|
||||
guard let cachedPadding = currentLayoutContext.messageContainerPadding else {
|
||||
let padding = messageContainerPadding(for: message, at: indexPath)
|
||||
currentLayoutContext.messageContainerPadding = padding
|
||||
return padding
|
||||
}
|
||||
return cachedPadding
|
||||
}
|
||||
|
||||
internal func _messageContainerMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
guard let cachedMaxWidth = currentLayoutContext.messageContainerMaxWidth else {
|
||||
let maxWidth = messageContainerMaxWidth(for: message, at: indexPath)
|
||||
currentLayoutContext.messageContainerMaxWidth = maxWidth
|
||||
return maxWidth
|
||||
}
|
||||
return cachedMaxWidth
|
||||
}
|
||||
|
||||
internal func _messageContainerSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
guard let cachedSize = currentLayoutContext.messageContainerSize else {
|
||||
let size = messageContainerSize(for: message, at: indexPath)
|
||||
currentLayoutContext.messageContainerSize = size
|
||||
return size
|
||||
}
|
||||
return cachedSize
|
||||
}
|
||||
}
|
||||
@@ -32,52 +32,39 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
||||
open override class var layoutAttributesClass: AnyClass {
|
||||
return MessagesCollectionViewLayoutAttributes.self
|
||||
}
|
||||
|
||||
/** The `MessagesCollectionView` that owns this layout object. */
|
||||
public var messagesCollectionView: MessagesCollectionView {
|
||||
guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
|
||||
fatalError(MessageKitError.layoutUsedOnForeignType)
|
||||
}
|
||||
return messagesCollectionView
|
||||
}
|
||||
|
||||
/** The `MessagesDataSource` for the layout's collection view. */
|
||||
public var messagesDataSource: MessagesDataSource {
|
||||
guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
return messagesDataSource
|
||||
}
|
||||
|
||||
/** The `MessagesLayoutDelegate` for the layout's collection view. */
|
||||
public var messagesLayoutDelegate: MessagesLayoutDelegate {
|
||||
guard let messagesLayoutDelegate = messagesCollectionView.messagesLayoutDelegate else {
|
||||
fatalError(MessageKitError.nilMessagesLayoutDelegate)
|
||||
}
|
||||
return messagesLayoutDelegate
|
||||
}
|
||||
|
||||
/// The width of an item in the `MessagesCollectionView`.
|
||||
internal var itemWidth: CGFloat {
|
||||
public var itemWidth: CGFloat {
|
||||
guard let collectionView = collectionView else { return 0 }
|
||||
return collectionView.frame.width - sectionInset.left - sectionInset.right
|
||||
}
|
||||
|
||||
/// The font to be used by `TextMessageCell` for `MessageData.text(String)` case.
|
||||
///
|
||||
/// Note: The default value of this property is `UIFont.preferredFont(forTextStyle: .body)`
|
||||
open var messageLabelFont: UIFont {
|
||||
didSet {
|
||||
emojiLabelFont = messageLabelFont.withSize(2 * messageLabelFont.pointSize)
|
||||
}
|
||||
}
|
||||
|
||||
/// The font to be used by `TextMessageCell` for `MessageData.emoji(String)` case.
|
||||
///
|
||||
/// Note: The default value of this property is 2x the `messageLabelFont`.
|
||||
internal var emojiLabelFont: UIFont
|
||||
|
||||
/// Determines the maximum number of `MessageCollectionViewCell` attributes to cache.
|
||||
///
|
||||
/// Note: The default value of this property is 500.
|
||||
open var attributesCacheMaxSize: Int = 500 {
|
||||
didSet {
|
||||
layoutContextCache.countLimit = attributesCacheMaxSize
|
||||
}
|
||||
}
|
||||
|
||||
typealias MessageID = NSString
|
||||
|
||||
/// The cache for `MessageCellLayoutContext`.
|
||||
/// The key is the `messageId` of the `MessageType`.
|
||||
fileprivate var layoutContextCache = NSCache<MessageID, MessageCellLayoutContext>()
|
||||
|
||||
/// The `MessageCellLayoutContext` for the current cell.
|
||||
internal var currentLayoutContext: MessageCellLayoutContext!
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
public override init() {
|
||||
|
||||
messageLabelFont = UIFont.preferredFont(forTextStyle: .body)
|
||||
emojiLabelFont = messageLabelFont.withSize(2 * messageLabelFont.pointSize)
|
||||
|
||||
super.init()
|
||||
|
||||
sectionInset = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
|
||||
@@ -92,40 +79,35 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Cache Invalidation
|
||||
// MARK: - Attributes
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
/// Removes the cached layout information for a given `MessageType`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` whose cached layout information is to be removed.
|
||||
public func removeCachedAttributes(for message: MessageType) {
|
||||
removeCachedAttributes(for: message.messageId)
|
||||
open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
guard let attributesArray = super.layoutAttributesForElements(in: rect) as? [MessagesCollectionViewLayoutAttributes] else {
|
||||
return nil
|
||||
}
|
||||
for attributes in attributesArray where attributes.representedElementCategory == .cell {
|
||||
let cellSizeCalculator = cellSizeCalculatorForItem(at: attributes.indexPath)
|
||||
cellSizeCalculator.configure(attributes: attributes)
|
||||
}
|
||||
return attributesArray
|
||||
}
|
||||
|
||||
/// Removes the cached layout information for a `MessageType` with a given `messageId`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - messageId: The id for the `MessageType` whose cached layout information is to be removed.
|
||||
public func removeCachedAttributes(for messageId: String) {
|
||||
layoutContextCache.removeObject(forKey: messageId as NSString)
|
||||
open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
guard let attributes = super.layoutAttributesForItem(at: indexPath) as? MessagesCollectionViewLayoutAttributes else {
|
||||
return nil
|
||||
}
|
||||
if attributes.representedElementCategory == .cell {
|
||||
let cellSizeCalculator = cellSizeCalculatorForItem(at: attributes.indexPath)
|
||||
cellSizeCalculator.configure(attributes: attributes)
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
/// Removes the cached layout information for all `MessageType`s.
|
||||
public func removeAllCachedAttributes() {
|
||||
layoutContextCache.removeAllObjects()
|
||||
}
|
||||
// MARK: - Layout Invalidation
|
||||
|
||||
open override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
|
||||
if collectionView?.bounds.width != newBounds.width {
|
||||
removeAllCachedAttributes()
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return collectionView?.bounds.width != newBounds.width
|
||||
}
|
||||
|
||||
open override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
|
||||
@@ -135,137 +117,42 @@ extension MessagesCollectionViewFlowLayout {
|
||||
return flowLayoutContext
|
||||
}
|
||||
|
||||
/// Invalidates the layout and removes all cached attributes on device orientation change
|
||||
@objc
|
||||
private func handleOrientationChange(_ notification: Notification) {
|
||||
removeAllCachedAttributes()
|
||||
invalidateLayout()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MessagesCollectionViewLayoutAttributes
|
||||
// MARK: - Cell Sizing
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
lazy open var textMessageSizeCalculator = TextMessageSizeCalculator(layout: self)
|
||||
lazy open var attributedTextMessageSizeCalculator = TextMessageSizeCalculator(layout: self)
|
||||
lazy open var emojiMessageSizeCalculator = TextMessageSizeCalculator(layout: self)
|
||||
lazy open var photoMessageSizeCalculator = MediaMessageSizeCalculator(layout: self)
|
||||
lazy open var videoMessageSizeCalculator = MediaMessageSizeCalculator(layout: self)
|
||||
lazy open var locationMessageSizeCalculator = LocationMessageSizeCalculator(layout: self)
|
||||
|
||||
open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
|
||||
guard let attributesArray = super.layoutAttributesForElements(in: rect) as? [MessagesCollectionViewLayoutAttributes] else { return nil }
|
||||
|
||||
attributesArray.forEach { attributes in
|
||||
if attributes.representedElementCategory == UICollectionElementCategory.cell {
|
||||
configure(attributes: attributes)
|
||||
}
|
||||
}
|
||||
|
||||
return attributesArray
|
||||
}
|
||||
|
||||
open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
|
||||
guard let attributes = super.layoutAttributesForItem(at: indexPath) as? MessagesCollectionViewLayoutAttributes else { return nil }
|
||||
|
||||
if attributes.representedElementCategory == UICollectionElementCategory.cell {
|
||||
configure(attributes: attributes)
|
||||
}
|
||||
|
||||
return attributes
|
||||
|
||||
}
|
||||
|
||||
private func configure(attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
|
||||
let indexPath = attributes.indexPath
|
||||
open func cellSizeCalculatorForItem(at indexPath: IndexPath) -> CellSizeCalculator {
|
||||
let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
let context = cellLayoutContext(for: message, at: indexPath)
|
||||
|
||||
attributes.avatarSize = context.avatarSize!
|
||||
attributes.avatarPosition = context.avatarPosition!
|
||||
|
||||
attributes.messageContainerPadding = context.messageContainerPadding!
|
||||
attributes.messageContainerSize = context.messageContainerSize!
|
||||
attributes.messageLabelInsets = context.messageLabelInsets!
|
||||
|
||||
attributes.topLabelAlignment = context.topLabelAlignment!
|
||||
attributes.topLabelSize = context.topLabelSize!
|
||||
|
||||
attributes.bottomLabelAlignment = context.bottomLabelAlignment!
|
||||
attributes.bottomLabelSize = context.bottomLabelSize!
|
||||
|
||||
switch message.data {
|
||||
case .emoji:
|
||||
attributes.messageLabelFont = emojiLabelFont
|
||||
case .text:
|
||||
attributes.messageLabelFont = messageLabelFont
|
||||
case .attributedText(let text):
|
||||
guard !text.string.isEmpty else { return }
|
||||
guard let font = text.attribute(.font, at: 0, effectiveRange: nil) as? UIFont else { return }
|
||||
attributes.messageLabelFont = font
|
||||
default:
|
||||
break
|
||||
return textMessageSizeCalculator
|
||||
case .attributedText:
|
||||
return attributedTextMessageSizeCalculator
|
||||
case .emoji:
|
||||
return emojiMessageSizeCalculator
|
||||
case .photo:
|
||||
return photoMessageSizeCalculator
|
||||
case .video:
|
||||
return videoMessageSizeCalculator
|
||||
case .location:
|
||||
return locationMessageSizeCalculator
|
||||
case .custom:
|
||||
fatalError("Must return a CellSizeCalculator for MessageData.custom(Any?)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MessageCellLayoutContext
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
internal func cellLayoutContext(for message: MessageType, at indexPath: IndexPath) -> MessageCellLayoutContext {
|
||||
guard let cachedContext = layoutContextCache.object(forKey: message.messageId as NSString) else {
|
||||
let newContext = newCellLayoutContext(for: message, at: indexPath)
|
||||
|
||||
if messagesLayoutDelegate.shouldCacheLayoutAttributes(for: message) {
|
||||
layoutContextCache.setObject(newContext, forKey: message.messageId as NSString)
|
||||
}
|
||||
return newContext
|
||||
}
|
||||
return cachedContext
|
||||
}
|
||||
|
||||
internal func newCellLayoutContext(for message: MessageType, at indexPath: IndexPath) -> MessageCellLayoutContext {
|
||||
currentLayoutContext = MessageCellLayoutContext()
|
||||
currentLayoutContext.avatarPosition = _avatarPosition(for: message, at: indexPath)
|
||||
currentLayoutContext.avatarSize = _avatarSize(for: message, at: indexPath)
|
||||
currentLayoutContext.messageContainerPadding = _messageContainerPadding(for: message, at: indexPath)
|
||||
currentLayoutContext.messageLabelInsets = _messageLabelInsets(for: message, at: indexPath)
|
||||
currentLayoutContext.messageContainerMaxWidth = _messageContainerMaxWidth(for: message, at: indexPath)
|
||||
currentLayoutContext.messageContainerSize = _messageContainerSize(for: message, at: indexPath)
|
||||
currentLayoutContext.topLabelAlignment = _cellTopLabelAlignment(for: message, at: indexPath)
|
||||
currentLayoutContext.topLabelMaxWidth = _cellTopLabelMaxWidth(for: message, at: indexPath)
|
||||
currentLayoutContext.topLabelSize = _cellTopLabelSize(for: message, at: indexPath)
|
||||
currentLayoutContext.bottomLabelAlignment = _cellBottomLabelAlignment(for: message, at: indexPath)
|
||||
currentLayoutContext.bottomLabelMaxWidth = _cellBottomLabelMaxWidth(for: message, at: indexPath)
|
||||
currentLayoutContext.bottomLabelSize = _cellBottomLabelSize(for: message, at: indexPath)
|
||||
currentLayoutContext.itemHeight = _cellContentHeight(for: message, at: indexPath)
|
||||
return currentLayoutContext
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
/// Convenience property for accessing the layout object's `MessagesCollectionView`.
|
||||
internal var messagesCollectionView: MessagesCollectionView {
|
||||
guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
|
||||
fatalError(MessageKitError.layoutUsedOnForeignType)
|
||||
}
|
||||
return messagesCollectionView
|
||||
}
|
||||
|
||||
/// Convenience property for unwrapping the `MessagesCollectionView`'s `MessagesDataSource`.
|
||||
internal var messagesDataSource: MessagesDataSource {
|
||||
guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
return messagesDataSource
|
||||
}
|
||||
|
||||
/// Convenience property for unwrapping the `MessagesCollectionView`'s `MessagesLayoutDelegate`.
|
||||
internal var messagesLayoutDelegate: MessagesLayoutDelegate {
|
||||
guard let messagesLayoutDelegate = messagesCollectionView.messagesLayoutDelegate else {
|
||||
fatalError(MessageKitError.nilMessagesLayoutDelegate)
|
||||
}
|
||||
return messagesLayoutDelegate
|
||||
open func sizeForItem(at indexPath: IndexPath) -> CGSize {
|
||||
let calculator = cellSizeCalculatorForItem(at: indexPath)
|
||||
return calculator.sizeForItem(at: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,19 +29,19 @@ open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttribu
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
var avatarSize: CGSize = .zero
|
||||
var avatarPosition = AvatarPosition(vertical: .cellBottom)
|
||||
public var avatarSize: CGSize = .zero
|
||||
public var avatarPosition = AvatarPosition(vertical: .cellBottom)
|
||||
|
||||
var messageContainerSize: CGSize = .zero
|
||||
var messageContainerPadding: UIEdgeInsets = .zero
|
||||
var messageLabelFont: UIFont = UIFont.preferredFont(forTextStyle: .body)
|
||||
var messageLabelInsets: UIEdgeInsets = .zero
|
||||
public var messageContainerSize: CGSize = .zero
|
||||
public var messageContainerPadding: UIEdgeInsets = .zero
|
||||
public var messageLabelFont: UIFont = UIFont.preferredFont(forTextStyle: .body)
|
||||
public var messageLabelInsets: UIEdgeInsets = .zero
|
||||
|
||||
var topLabelAlignment = LabelAlignment.cellCenter(.zero)
|
||||
var topLabelSize: CGSize = .zero
|
||||
public var topLabelAlignment = LabelAlignment(textAlignment: .center, textInsets: .zero)
|
||||
public var topLabelSize: CGSize = .zero
|
||||
|
||||
var bottomLabelAlignment = LabelAlignment.cellCenter(.zero)
|
||||
var bottomLabelSize: CGSize = .zero
|
||||
public var bottomLabelAlignment = LabelAlignment(textAlignment: .center, textInsets: .zero)
|
||||
public var bottomLabelSize: CGSize = .zero
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
@@ -63,14 +63,20 @@ open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttribu
|
||||
}
|
||||
|
||||
open override func isEqual(_ object: Any?) -> Bool {
|
||||
|
||||
// MARK: - LEAVE this as is
|
||||
// swiftlint:disable unused_optional_binding
|
||||
if let _ = object as? MessagesCollectionViewLayoutAttributes {
|
||||
return super.isEqual(object)
|
||||
if let attributes = object as? MessagesCollectionViewLayoutAttributes {
|
||||
return super.isEqual(object) && attributes.avatarSize == avatarSize
|
||||
&& attributes.avatarPosition == attributes.avatarPosition
|
||||
&& attributes.messageContainerSize == messageContainerSize
|
||||
&& attributes.messageContainerPadding == messageContainerPadding
|
||||
&& attributes.messageLabelFont == messageLabelFont
|
||||
&& attributes.messageLabelInsets == messageLabelInsets
|
||||
&& attributes.topLabelAlignment == topLabelAlignment
|
||||
&& attributes.topLabelSize == topLabelSize
|
||||
&& attributes.bottomLabelAlignment == bottomLabelAlignment
|
||||
&& attributes.bottomLabelSize == bottomLabelSize
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
// swiftlint:enable unused_optional_binding
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
open class TextMessageSizeCalculator: MessageSizeCalculator {
|
||||
|
||||
public var incomingMessageLabelInsets = UIEdgeInsets(top: 7, left: 18, bottom: 7, right: 14)
|
||||
public var outgoingMessageLabelInsets = UIEdgeInsets(top: 7, left: 14, bottom: 7, right: 18)
|
||||
|
||||
public var messageLabelFont = UIFont.preferredFont(forTextStyle: .body)
|
||||
|
||||
internal func messageLabelInsets(for message: MessageType) -> UIEdgeInsets {
|
||||
let dataSource = messagesLayout.messagesDataSource
|
||||
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
|
||||
return isFromCurrentSender ? outgoingMessageLabelInsets : incomingMessageLabelInsets
|
||||
}
|
||||
|
||||
open override func messageContainerMaxWidth(for message: MessageType) -> CGFloat {
|
||||
let maxWidth = super.messageContainerMaxWidth(for: message)
|
||||
let textInsets = messageLabelInsets(for: message)
|
||||
return maxWidth - textInsets.horizontal
|
||||
}
|
||||
|
||||
open override func messageContainerSize(for message: MessageType) -> CGSize {
|
||||
let maxWidth = messageContainerMaxWidth(for: message)
|
||||
|
||||
var messageContainerSize: CGSize
|
||||
let attributedText: NSAttributedString
|
||||
|
||||
switch message.data {
|
||||
case .attributedText(let text):
|
||||
attributedText = text
|
||||
case .text(let text), .emoji(let text):
|
||||
attributedText = NSAttributedString(string: text, attributes: [.font: messageLabelFont])
|
||||
default:
|
||||
fatalError("messageContainerSize received unhandled MessageDataType: \(message.data)")
|
||||
}
|
||||
|
||||
messageContainerSize = labelSize(for: attributedText, considering: maxWidth)
|
||||
|
||||
let messageInsets = messageLabelInsets(for: message)
|
||||
messageContainerSize.width += messageInsets.horizontal
|
||||
messageContainerSize.height += messageInsets.vertical
|
||||
|
||||
return messageContainerSize
|
||||
}
|
||||
|
||||
open override func configure(attributes: UICollectionViewLayoutAttributes) {
|
||||
super.configure(attributes: attributes)
|
||||
guard let attributes = attributes as? MessagesCollectionViewLayoutAttributes else { return }
|
||||
|
||||
let dataSource = messagesLayout.messagesDataSource
|
||||
let indexPath = attributes.indexPath
|
||||
let message = dataSource.messageForItem(at: indexPath, in: messagesLayout.messagesCollectionView)
|
||||
|
||||
attributes.messageLabelInsets = messageLabelInsets(for: message)
|
||||
attributes.messageLabelFont = messageLabelFont
|
||||
|
||||
switch message.data {
|
||||
case .attributedText(let text):
|
||||
guard !text.string.isEmpty else { return }
|
||||
guard let font = text.attribute(.font, at: 0, effectiveRange: nil) as? UIFont else { return }
|
||||
attributes.messageLabelFont = font
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,3 +82,13 @@ public struct AvatarPosition {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Equatable Conformance
|
||||
|
||||
extension AvatarPosition: Equatable {
|
||||
|
||||
public static func == (lhs: AvatarPosition, rhs: AvatarPosition) -> Bool {
|
||||
return lhs.vertical == rhs.vertical && lhs.horizontal == rhs.horizontal
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public enum DetectorType {
|
||||
//case hashtag
|
||||
//case custom
|
||||
|
||||
var textCheckingType: NSTextCheckingResult.CheckingType {
|
||||
internal var textCheckingType: NSTextCheckingResult.CheckingType {
|
||||
switch self {
|
||||
case .address: return .address
|
||||
case .date: return .date
|
||||
|
||||
@@ -24,38 +24,19 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
/// An enum represnting the horizontal alignment of a `MessageCollectionViewCell`'s top and bottom labels.
|
||||
public enum LabelAlignment {
|
||||
public struct LabelAlignment {
|
||||
|
||||
/// Aligns the label's trailing edge to the cell's trailing edge.
|
||||
/// The `UIEdgeInsets` associated value represents the offset from this position.
|
||||
case cellTrailing(UIEdgeInsets)
|
||||
|
||||
/// Aligns the label's leading edge to the cell's leading edge.
|
||||
/// The `UIEdgeInsets` associated value represents the offset from this position.
|
||||
case cellLeading(UIEdgeInsets)
|
||||
|
||||
/// Aligns the label's center to the cell's center.
|
||||
/// The `UIEdgeInsets` associated value represents the offset from this position.
|
||||
case cellCenter(UIEdgeInsets)
|
||||
|
||||
/// Aligns the label's trailing edge to the `MessageContainerView`'s trailing edge.
|
||||
/// The `UIEdgeInsets` associated value represents the offset from this position.
|
||||
case messageTrailing(UIEdgeInsets)
|
||||
|
||||
/// Aligns the label's leading edge to the `MessageContainerView`'s leading edge.
|
||||
/// The `UIEdgeInsets` associated value represents the offset from this position.
|
||||
case messageLeading(UIEdgeInsets)
|
||||
public var textAlignment: NSTextAlignment
|
||||
public var textInsets: UIEdgeInsets
|
||||
|
||||
/// Returns the `UIEdgeInsets` associated value for the `LabelAlignment` case.
|
||||
public var insets: UIEdgeInsets {
|
||||
switch self {
|
||||
case .cellTrailing(let insets): return insets
|
||||
case .cellLeading(let insets): return insets
|
||||
case .cellCenter(let insets): return insets
|
||||
case .messageTrailing(let insets): return insets
|
||||
case .messageLeading(let insets): return insets
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Equatable Conformance
|
||||
|
||||
extension LabelAlignment: Equatable {
|
||||
|
||||
public static func == (lhs: LabelAlignment, rhs: LabelAlignment) -> Bool {
|
||||
return lhs.textAlignment == rhs.textAlignment && lhs.textInsets == rhs.textInsets
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import class CoreLocation.CLLocation
|
||||
|
||||
/// An enum representing the kind of message and its underlying data.
|
||||
public enum MessageData {
|
||||
@@ -41,13 +40,13 @@ public enum MessageData {
|
||||
case attributedText(NSAttributedString)
|
||||
|
||||
/// A photo message.
|
||||
case photo(UIImage)
|
||||
case photo(MediaItem)
|
||||
|
||||
/// A video message.
|
||||
case video(file: URL, thumbnail: UIImage)
|
||||
case video(MediaItem)
|
||||
|
||||
/// A location message.
|
||||
case location(CLLocation)
|
||||
case location(LocationItem)
|
||||
|
||||
/// An emoji message.
|
||||
case emoji(String)
|
||||
@@ -55,7 +54,7 @@ public enum MessageData {
|
||||
/// A custom message.
|
||||
/// - Note: Using this case requires that you override the following methods and handle this case:
|
||||
/// - `collectionView(_:cellForItemAt indexPath: IndexPath) -> UICollectionViewCell`
|
||||
/// - `messageContainerSize(for message: MessageType, at indexPath: IndexPath) -> CGSize`
|
||||
/// - `cellSizeCalculatorForItem(at indexPath: IndexPath) -> CellSizeCalculator`
|
||||
case custom(Any?)
|
||||
|
||||
// MARK: - Not supported yet
|
||||
|
||||
@@ -22,17 +22,17 @@
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
enum MessageKitError {
|
||||
static let avatarPositionUnresolved = "AvatarPosition Horizontal.natural needs to be resolved."
|
||||
static let nilMessagesDataSource = "MessagesDataSource has not been set."
|
||||
static let nilMessagesDisplayDelegate = "MessagesDisplayDelegate has not been set."
|
||||
static let nilMessagesLayoutDelegate = "MessagesLayoutDelegate has not been set."
|
||||
static let notMessagesCollectionView = "The collectionView is not a MessagesCollectionView."
|
||||
static let layoutUsedOnForeignType = "MessagesCollectionViewFlowLayout is being used on a foreign type."
|
||||
static let unrecognizedSectionKind = "Received unrecognized element kind:"
|
||||
static let unrecognizedCheckingResult = "Received an unrecognized NSTextCheckingResult.CheckingType"
|
||||
static let couldNotLoadAssetsBundle = "MessageKit: Could not load the assets bundle"
|
||||
static let couldNotCreateAssetsPath = "MessageKit: Could not create path to the assets bundle."
|
||||
static let customDataUnresolvedCell = "Did not return a cell for MessageData.custom(Any)."
|
||||
static let customDataUnresolvedSize = "Did not return a size for MessageData.custom(Any)."
|
||||
internal enum MessageKitError {
|
||||
internal static let avatarPositionUnresolved = "AvatarPosition Horizontal.natural needs to be resolved."
|
||||
internal static let nilMessagesDataSource = "MessagesDataSource has not been set."
|
||||
internal static let nilMessagesDisplayDelegate = "MessagesDisplayDelegate has not been set."
|
||||
internal static let nilMessagesLayoutDelegate = "MessagesLayoutDelegate has not been set."
|
||||
internal static let notMessagesCollectionView = "The collectionView is not a MessagesCollectionView."
|
||||
internal static let layoutUsedOnForeignType = "MessagesCollectionViewFlowLayout is being used on a foreign type."
|
||||
internal static let unrecognizedSectionKind = "Received unrecognized element kind:"
|
||||
internal static let unrecognizedCheckingResult = "Received an unrecognized NSTextCheckingResult.CheckingType"
|
||||
internal static let couldNotLoadAssetsBundle = "MessageKit: Could not load the assets bundle"
|
||||
internal static let couldNotCreateAssetsPath = "MessageKit: Could not create path to the assets bundle."
|
||||
internal static let customDataUnresolvedCell = "Did not return a cell for MessageData.custom(Any)."
|
||||
internal static let customDataUnresolvedSize = "Did not return a size for MessageData.custom(Any)."
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public enum MessageStyle {
|
||||
case topRight
|
||||
case bottomRight
|
||||
|
||||
var imageOrientation: UIImageOrientation {
|
||||
internal var imageOrientation: UIImageOrientation {
|
||||
switch self {
|
||||
case .bottomRight: return .up
|
||||
case .bottomLeft: return .upMirrored
|
||||
@@ -52,7 +52,7 @@ public enum MessageStyle {
|
||||
case curved
|
||||
case pointedEdge
|
||||
|
||||
var imageNameSuffix: String {
|
||||
internal var imageNameSuffix: String {
|
||||
switch self {
|
||||
case .curved:
|
||||
return "_tail_v2"
|
||||
|
||||
@@ -24,18 +24,18 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class NSLayoutConstraintSet {
|
||||
internal class NSLayoutConstraintSet {
|
||||
|
||||
public var top: NSLayoutConstraint?
|
||||
public var bottom: NSLayoutConstraint?
|
||||
public var left: NSLayoutConstraint?
|
||||
public var right: NSLayoutConstraint?
|
||||
public var centerX: NSLayoutConstraint?
|
||||
public var centerY: NSLayoutConstraint?
|
||||
public var width: NSLayoutConstraint?
|
||||
public var height: NSLayoutConstraint?
|
||||
internal var top: NSLayoutConstraint?
|
||||
internal var bottom: NSLayoutConstraint?
|
||||
internal var left: NSLayoutConstraint?
|
||||
internal var right: NSLayoutConstraint?
|
||||
internal var centerX: NSLayoutConstraint?
|
||||
internal var centerY: NSLayoutConstraint?
|
||||
internal var width: NSLayoutConstraint?
|
||||
internal var height: NSLayoutConstraint?
|
||||
|
||||
public init(top: NSLayoutConstraint? = nil, bottom: NSLayoutConstraint? = nil,
|
||||
internal init(top: NSLayoutConstraint? = nil, bottom: NSLayoutConstraint? = nil,
|
||||
left: NSLayoutConstraint? = nil, right: NSLayoutConstraint? = nil,
|
||||
centerX: NSLayoutConstraint? = nil, centerY: NSLayoutConstraint? = nil,
|
||||
width: NSLayoutConstraint? = nil, height: NSLayoutConstraint? = nil) {
|
||||
@@ -59,7 +59,7 @@ public class NSLayoutConstraintSet {
|
||||
///
|
||||
/// - Returns: Self
|
||||
@discardableResult
|
||||
func activate() -> Self {
|
||||
internal func activate() -> Self {
|
||||
NSLayoutConstraint.activate(availableConstraints)
|
||||
return self
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public class NSLayoutConstraintSet {
|
||||
///
|
||||
/// - Returns: Self
|
||||
@discardableResult
|
||||
func deactivate() -> Self {
|
||||
internal func deactivate() -> Self {
|
||||
NSLayoutConstraint.deactivate(availableConstraints)
|
||||
return self
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import class CoreLocation.CLLocation
|
||||
|
||||
/// A protocol used to represent the data for a location message.
|
||||
public protocol LocationItem {
|
||||
|
||||
/// The location.
|
||||
var location: CLLocation { get set }
|
||||
|
||||
/// The size of the location item.
|
||||
var size: CGSize { get set }
|
||||
|
||||
}
|
||||
@@ -24,25 +24,19 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// An internal type used to store the attributes used to calculate
|
||||
/// the size of a `MessageCollectionViewCell`.
|
||||
final class MessageCellLayoutContext {
|
||||
/// A protocol used to represent the data for a media message.
|
||||
public protocol MediaItem {
|
||||
|
||||
var itemHeight: CGFloat?
|
||||
/// The url where the media is located.
|
||||
var url: URL? { get set }
|
||||
|
||||
var avatarSize: CGSize?
|
||||
var avatarPosition: AvatarPosition?
|
||||
/// The image.
|
||||
var image: UIImage? { get set }
|
||||
|
||||
var messageContainerSize: CGSize?
|
||||
var messageContainerMaxWidth: CGFloat?
|
||||
var messageContainerPadding: UIEdgeInsets?
|
||||
var messageLabelInsets: UIEdgeInsets?
|
||||
/// A placeholder image for when the image is obtained asychronously.
|
||||
var placeholderImage: UIImage { get set }
|
||||
|
||||
var bottomLabelAlignment: LabelAlignment?
|
||||
var bottomLabelSize: CGSize?
|
||||
var bottomLabelMaxWidth: CGFloat?
|
||||
/// The size of the media item.
|
||||
var size: CGSize { get set }
|
||||
|
||||
var topLabelAlignment: LabelAlignment?
|
||||
var topLabelSize: CGSize?
|
||||
var topLabelMaxWidth: CGFloat?
|
||||
}
|
||||
@@ -24,43 +24,44 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A protocol used by `MessageCollectionViewCell` subclasses to detect taps in the cell's contents.
|
||||
/// A protocol used by `MessageContentCell` subclasses to detect taps in the cell's subviews.
|
||||
public protocol MessageCellDelegate: MessageLabelDelegate {
|
||||
|
||||
/// Triggered when a touch occurs in the `MessageContainerView`.
|
||||
/// Triggered when a tap occurs in the `MessageContainerView`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - cell: The cell where the touch occurred.
|
||||
/// - cell: The cell where the tap occurred.
|
||||
///
|
||||
/// - Note:
|
||||
/// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
|
||||
/// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
|
||||
/// method `messageForItem(at:indexPath:messagesCollectionView)`.
|
||||
func didTapMessage(in cell: MessageCollectionViewCell)
|
||||
|
||||
/// Triggered when a touch occurs in the `AvatarView`.
|
||||
/// Triggered when a tap occurs in the `AvatarView`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - cell: The cell where the touch occurred.
|
||||
/// - cell: The cell where the tap occurred.
|
||||
///
|
||||
/// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
|
||||
/// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
|
||||
/// method `messageForItem(at:indexPath:messagesCollectionView)`.
|
||||
func didTapAvatar(in cell: MessageCollectionViewCell)
|
||||
|
||||
/// Triggered when a touch occurs in the cellBottomLabel.
|
||||
/// Triggered when a tap occurs in the cellBottomLabel.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - cell: The cell where the touch occurred.
|
||||
/// - cell: The cell where the tap occurred.
|
||||
///
|
||||
/// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
|
||||
/// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
|
||||
/// method `messageForItem(at:indexPath:messagesCollectionView)`.
|
||||
func didTapBottomLabel(in cell: MessageCollectionViewCell)
|
||||
|
||||
/// Triggered when a touch occurs in the cellTopLabel.
|
||||
/// Triggered when a tap occurs in the cellTopLabel.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - cell: The cell where the touch occurred.
|
||||
/// - cell: The cell tap the touch occurred.
|
||||
///
|
||||
/// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
|
||||
/// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
|
||||
|
||||
@@ -24,26 +24,27 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// MessageInputBarDelegate is a protocol that can recieve notifications from the MessageInputBar
|
||||
/// A protocol that can receive different event notifications from the MessageInputBar.
|
||||
public protocol MessageInputBarDelegate: AnyObject {
|
||||
|
||||
/// Called when the default send button has been selected
|
||||
/// Called when the default send button has been selected.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - inputBar: The MessageInputBar
|
||||
/// - text: The current text in the MessageInputBar's InputTextView
|
||||
/// - inputBar: The `MessageInputBar`.
|
||||
/// - text: The current text in the `InputTextView` of the `MessageInputBar`.
|
||||
func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String)
|
||||
|
||||
/// Called when the instrinsicContentSize of the MessageInputBar has changed. Can be used for adjusting content insets
|
||||
/// on other views to make sure the MessageInputBar does not cover up any other view
|
||||
/// Called when the instrinsicContentSize of the MessageInputBar has changed.
|
||||
/// Can be used for adjusting content insets on other views to make sure
|
||||
/// the MessageInputBar does not cover up any other view.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - inputBar: The MessageInputBar
|
||||
/// - size: The new instrinsicContentSize
|
||||
/// - inputBar: The `MessageInputBar`.
|
||||
/// - size: The new instrinsic content size.
|
||||
func messageInputBar(_ inputBar: MessageInputBar, didChangeIntrinsicContentTo size: CGSize)
|
||||
|
||||
/// Called when the MessageInputBar's InputTextView's text has changed. Useful for adding your own logic without the
|
||||
/// need of assigning a delegate or notification
|
||||
/// Called when the `MessageInputBar`'s `InputTextView`'s text has changed.
|
||||
/// Useful for adding your own logic without the need of assigning a delegate or notification.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - inputBar: The MessageInputBar
|
||||
|
||||
@@ -24,16 +24,37 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A protocol used to handle tap events on detected text.
|
||||
public protocol MessageLabelDelegate: AnyObject {
|
||||
|
||||
/// Triggered when a tap occurs on a detected address.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - addressComponents: The components of the selected address.
|
||||
func didSelectAddress(_ addressComponents: [String: String])
|
||||
|
||||
/// Triggered when a tap occurs on a detected date.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - date: The selected date.
|
||||
func didSelectDate(_ date: Date)
|
||||
|
||||
/// Triggered when a tap occurs on a detected phone number.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - phoneNumber: The selected phone number.
|
||||
func didSelectPhoneNumber(_ phoneNumber: String)
|
||||
|
||||
/// Triggered when a tap occurs on a detected URL.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - url: The selected URL.
|
||||
func didSelectURL(_ url: URL)
|
||||
|
||||
|
||||
/// Triggered when a tap occurs on detected transit information.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - transitInformation: The selected transit information.
|
||||
func didSelectTransitInformation(_ transitInformation: [String: String])
|
||||
|
||||
}
|
||||
|
||||
@@ -24,17 +24,14 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A standard protocol representing a message. Use this protocol to create
|
||||
/// your own message object to be used by MessageKit.
|
||||
/// A standard protocol representing a message.
|
||||
/// Use this protocol to create your own message object to be used by MessageKit.
|
||||
public protocol MessageType {
|
||||
|
||||
/// The sender of the message.
|
||||
var sender: Sender { get }
|
||||
|
||||
/// The unique identifier for the message.
|
||||
///
|
||||
/// NOTE: This value must be unique for all messages as it is
|
||||
/// used to cache layout information.
|
||||
var messageId: String { get }
|
||||
|
||||
/// The date the message was sent.
|
||||
|
||||
@@ -24,18 +24,21 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
/// An object that adopts the `MessagesDataSource` protocol is responsible for providing
|
||||
/// the data required by a `MessagesCollectionView`.
|
||||
public protocol MessagesDataSource: AnyObject {
|
||||
|
||||
/// The `Sender` of new messages in the `MessagesCollectionView`.
|
||||
func currentSender() -> Sender
|
||||
|
||||
/// A helper method to determine if a given message is from the current sender.
|
||||
/// A helper method to determine if a given message is from the current `Sender`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The message to check if it was sent by the current Sender.
|
||||
/// - message: The message to check if it was sent by the current `Sender`.
|
||||
///
|
||||
/// The default implementation of this method checks for equality between the message's `Sender`
|
||||
/// and the current Sender.
|
||||
/// - Note:
|
||||
/// The default implementation of this method checks for equality between
|
||||
/// the message's `Sender` and the current `Sender`.
|
||||
func isFromCurrentSender(message: MessageType) -> Bool
|
||||
|
||||
/// The message to be used for a `MessageCollectionViewCell` at the given `IndexPath`.
|
||||
@@ -45,11 +48,20 @@ public protocol MessagesDataSource: AnyObject {
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which the message will be displayed.
|
||||
func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType
|
||||
|
||||
/// The number of messages to be displayed in the `MessagesCollectionView`.
|
||||
/// The number of sections to be displayed in the `MessagesCollectionView`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which the messages will be displayed.
|
||||
func numberOfMessages(in messagesCollectionView: MessagesCollectionView) -> Int
|
||||
func numberOfSections(in messagesCollectionView: MessagesCollectionView) -> Int
|
||||
|
||||
/// The number of cells to be displayed in the `MessagesCollectionView`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - section: The number of the section in which the cells will be displayed.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which the messages will be displayed.
|
||||
/// - Note:
|
||||
/// The default implementation of this method returns 1. Putting each message in its own section.
|
||||
func numberOfItems(inSection section: Int, in messagesCollectionView: MessagesCollectionView) -> Int
|
||||
|
||||
/// The attributed text to be used for cell's top label.
|
||||
///
|
||||
@@ -79,6 +91,10 @@ public extension MessagesDataSource {
|
||||
return message.sender == currentSender()
|
||||
}
|
||||
|
||||
func numberOfItems(inSection section: Int, in messagesCollectionView: MessagesCollectionView) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
import Foundation
|
||||
import MapKit
|
||||
|
||||
/// A protocol used by the `MessagesViewController` to customize the appearance of a `MessageCollectionViewCell`.
|
||||
/// A protocol used by the `MessagesViewController` to customize the appearance of a `MessageContentCell`.
|
||||
public protocol MessagesDisplayDelegate: AnyObject {
|
||||
|
||||
// MARK: - All Messages
|
||||
@@ -37,7 +37,8 @@ public protocol MessagesDisplayDelegate: AnyObject {
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is `MessageStyle.bubble`.
|
||||
/// - Note:
|
||||
/// The default value returned by this method is `MessageStyle.bubble`.
|
||||
func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle
|
||||
|
||||
/// Specifies the background color of the `MessageContainerView`.
|
||||
@@ -47,44 +48,31 @@ public protocol MessagesDisplayDelegate: AnyObject {
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value is `UIColor.clear` for emoji messages. For all other `MessageData` cases, the color depends on the `Sender`:
|
||||
/// - Note:
|
||||
/// The default value is `UIColor.clear` for emoji messages.
|
||||
/// For all other `MessageData` cases, the color depends on the `Sender`.
|
||||
///
|
||||
/// Current Sender: Green
|
||||
/// Current sender: Green
|
||||
///
|
||||
/// All other Senders: Gray
|
||||
/// All other senders: Gray
|
||||
func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor
|
||||
|
||||
/// The section header to use for a given `MessageType`.
|
||||
/// The section header to use for a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed for this header.
|
||||
/// - indexPath: The `IndexPath` of the header.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this header will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is a `MessageDateHeaderView`.
|
||||
func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView
|
||||
func messageHeaderView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView
|
||||
|
||||
/// Used by the `MessageLayoutDelegate` method `headerViewSize(_:_:_:)` to determine if a header should be displayed.
|
||||
/// This method checks `MessageCollectionView`'s `showsDateHeaderAfterTimeInterval` property and returns true if
|
||||
/// the current messages sent date occurs after the specified time interval when compared to the previous message.
|
||||
/// The section footer to use for a given `IndexPath`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed for this header.
|
||||
/// - indexPath: The `IndexPath` of the header.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this header will be displayed.
|
||||
func shouldDisplayHeader(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Bool
|
||||
|
||||
/// The section footer to use for a given `MessageType`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed for this footer.
|
||||
/// - indexPath: The `IndexPath` of the footer.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this footer will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is a `MessageFooterView`.
|
||||
func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView
|
||||
func messageFooterView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView
|
||||
|
||||
/// Configure `AvatarView`‘s image.
|
||||
/// Used to configure the `AvatarView`‘s image in a `MessageContentCell` class.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - avatarView: The `AvatarView` of the cell.
|
||||
@@ -92,7 +80,8 @@ public protocol MessagesDisplayDelegate: AnyObject {
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default image configured by this method is `?`.
|
||||
/// - Note:
|
||||
/// The default image configured by this method is `?`.
|
||||
func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)
|
||||
|
||||
// MARK: - Text Messages
|
||||
@@ -100,15 +89,16 @@ public protocol MessagesDisplayDelegate: AnyObject {
|
||||
/// Specifies the color of the text for a `TextMessageCell`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: A `MessageType` with a `MessageData` case of `.text` or `.attributedText` to which the color will apply.
|
||||
/// - message: A `MessageType` with a `MessageData` case of `.text` to which the color will apply.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is determined by the messages `Sender`:
|
||||
/// - Note:
|
||||
/// The default value returned by this method is determined by the messages `Sender`.
|
||||
///
|
||||
/// Current Sender: UIColor.white
|
||||
/// Current sender: UIColor.white
|
||||
///
|
||||
/// All other Senders: UIColor.darkText
|
||||
/// All other senders: UIColor.darkText
|
||||
func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor
|
||||
|
||||
/// Specifies the `DetectorType`s to check for the `MessageType`'s text against.
|
||||
@@ -118,52 +108,59 @@ public protocol MessagesDisplayDelegate: AnyObject {
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is all available detector types.
|
||||
/// - Note:
|
||||
/// This method returns an empty array by default.
|
||||
func enabledDetectors(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> [DetectorType]
|
||||
|
||||
/// Specifies the attributes for a given `DetectorType`
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - detector: The `DetectorType` for the applied attributes.
|
||||
/// - message: A `MessageType` with a `MessageData` case of `.text` or `.attributedText`
|
||||
/// to which the detectors will apply.
|
||||
/// - message: A `MessageType` with a `MessageData` case of `.text` or `.attributedText` to which the detectors will apply.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
func detectorAttributes(for detector: DetectorType, and message: MessageType, at indexPath: IndexPath) -> [NSAttributedStringKey: Any]
|
||||
|
||||
// MARK: - Location Messages
|
||||
|
||||
/// Ask the delegate for a LocationMessageSnapshotOptions instance to customize the MapView on the given message
|
||||
/// Used to configure a `LocationMessageSnapshotOptions` instance to customize the map image on the given location message.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The location message to be customized
|
||||
/// - indexPath: Message's index path
|
||||
/// - messagesCollectionView: The collection view requesting the information
|
||||
/// - Returns: Your LocationMessageSnapshotOptions instance with the options to customize map style
|
||||
/// - message: A `MessageType` with a `MessageData` case of `.location`.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` requesting the information.
|
||||
/// - Returns: The LocationMessageSnapshotOptions instance with the options to customize map style.
|
||||
func snapshotOptionsForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LocationMessageSnapshotOptions
|
||||
|
||||
/// Ask the delegate for a custom MKAnnotationView to show on the given message.
|
||||
/// You can return nil if you don't want to show any annotation.
|
||||
///
|
||||
/// default: MKPinAnnotationView
|
||||
/// Used to configure the annoation view of the map image on the given location message.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The location message with the annotation to customize
|
||||
/// - indexPath: Message's index path
|
||||
/// - messageCollectionView: The collection view requesting the information
|
||||
/// - Returns: Your customized MKAnnotationView or nil to not show any.
|
||||
/// - message: A `MessageType` with a `MessageData` case of `.location`.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` requesting the information.
|
||||
/// - Returns: The `MKAnnotationView` to use as the annotation view.
|
||||
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.
|
||||
///
|
||||
/// default: nil
|
||||
/// 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 `UIImageView` to be animated.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The location message with the map to animate
|
||||
/// - indexPath: Message's index path
|
||||
/// - messagesCollectionView: The collection view requesting the information
|
||||
/// - Returns: Your customized animation block.
|
||||
/// - message: A `MessageType` with a `MessageData` case of `.location`.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` requesting the information.
|
||||
/// - Returns: The animation block to use to apply the location image.
|
||||
func animationBlockForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> ((UIImageView) -> Void)?
|
||||
|
||||
// MARK: - Media Messages
|
||||
|
||||
/// Used to configure the `UIImageView` of a `MediaMessageCell.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - imageView: The `UIImageView` of the cell.
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
func configureMediaMessageImageView(_ imageView: UIImageView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)
|
||||
|
||||
}
|
||||
|
||||
public extension MessagesDisplayDelegate {
|
||||
@@ -185,24 +182,12 @@ public extension MessagesDisplayDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView {
|
||||
let header = messagesCollectionView.dequeueReusableHeaderView(MessageDateHeaderView.self, for: indexPath)
|
||||
header.dateLabel.text = MessageKitDateFormatter.shared.string(from: message.sentDate)
|
||||
return header
|
||||
func messageHeaderView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView {
|
||||
return messagesCollectionView.dequeueReusableHeaderView(MessageReusableView.self, for: indexPath)
|
||||
}
|
||||
|
||||
func shouldDisplayHeader(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Bool {
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else { return false }
|
||||
if indexPath.section == 0 { return false }
|
||||
let previousSection = indexPath.section - 1
|
||||
let previousIndexPath = IndexPath(item: 0, section: previousSection)
|
||||
let previousMessage = dataSource.messageForItem(at: previousIndexPath, in: messagesCollectionView)
|
||||
let timeIntervalSinceLastMessage = message.sentDate.timeIntervalSince(previousMessage.sentDate)
|
||||
return timeIntervalSinceLastMessage >= messagesCollectionView.showsDateHeaderAfterTimeInterval
|
||||
}
|
||||
|
||||
func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView {
|
||||
return messagesCollectionView.dequeueReusableFooterView(MessageFooterView.self, for: indexPath)
|
||||
func messageFooterView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView {
|
||||
return messagesCollectionView.dequeueReusableFooterView(MessageReusableView.self, for: indexPath)
|
||||
}
|
||||
|
||||
func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) {
|
||||
@@ -240,4 +225,8 @@ public extension MessagesDisplayDelegate {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Media Message Defaults
|
||||
|
||||
func configureMediaMessageImageView(_ imageView: UIImageView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,255 +23,70 @@
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
/// A protocol used by the `MessagesCollectionViewFlowLayout` object to determine
|
||||
/// the size and layout of a `MessageCollectionViewCell` and its contents.
|
||||
public protocol MessagesLayoutDelegate: AnyObject {
|
||||
|
||||
// MARK: - All Messages
|
||||
|
||||
/// Specifies the padding around the `MessageContainerView` in a `MessageCollectionViewCell`.
|
||||
/// Specifies the size to use for a header view.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is determined by the messages `Sender`:
|
||||
///
|
||||
/// Current Sender: `UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 4)`
|
||||
///
|
||||
/// All other Senders: `UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 30)`
|
||||
func messagePadding(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets
|
||||
|
||||
/// Specifies the vertical and horizontal alignment for the `AvatarView` in a `MessageCollectionViewCell`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is an `AvatarPosition` with
|
||||
/// `Horizontal.natural` and `Vertical.messageBottom`.
|
||||
func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition
|
||||
|
||||
/// Specifies the horizontal alignment of a `MessageCollectionViewCell`'s top label.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is determined by the messages `Sender`:
|
||||
///
|
||||
/// Current Sender: .messageTrailing(.zero)
|
||||
///
|
||||
/// All other senders: .messageLeading(.zero)
|
||||
func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment
|
||||
|
||||
/// Specifies the horizontal alignment of a `MessageCollectionViewCell`'s bottom label.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is determined by the messages `Sender`:
|
||||
///
|
||||
/// Current Sender: .messageLeading(.zero)
|
||||
///
|
||||
/// All other senders: .messageTrailing(.zero)
|
||||
func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment
|
||||
|
||||
/// Specifies the size of the `AvatarView` in a `MessageCollectionViewCell`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is a size of `30 x 30`.
|
||||
func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize
|
||||
|
||||
/// Specifies the size to use for a `MessageHeaderView`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed for this header.
|
||||
/// - indexPath: The `IndexPath` of the header.
|
||||
/// - section: The section number of the header.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this header will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is the width of the `MessagesCollectionView` and a height of 12.
|
||||
func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize
|
||||
/// - Note:
|
||||
/// The default value returned by this method is a size of `GGSize.zero`.
|
||||
func headerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize
|
||||
|
||||
/// Specifies the size to use for a `MessageFooterView`.
|
||||
/// Specifies the size to use for a footer view.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed for this footer.
|
||||
/// - indexPath: The `IndexPath` of the footer.
|
||||
/// - section: The section number of the footer.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this footer will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is a size of `GGSize.zero`.
|
||||
func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize
|
||||
/// - Note:
|
||||
/// The default value returned by this method is a size of `GGSize.zero`.
|
||||
func footerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize
|
||||
|
||||
// MARK: - Text Messages
|
||||
|
||||
/// Specifies the insets for the text rect of the `MessageLabel` in a `TextMessageCell`.
|
||||
/// Specifies the height for the `MessageContentCell`'s top label.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: A `MessageType` with a `MessageData` case of `.text` or `.attributedText` to which these insets will apply.
|
||||
/// - message: The `MessageType` that will be displayed for this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is determined by the messages `Sender`:
|
||||
///
|
||||
/// Current Sender: `UIEdgeInsets(top: 7, left: 14, bottom: 7, right: 18)`
|
||||
///
|
||||
/// All other Senders: `UIEdgeInsets(top: 7, left: 18, bottom: 7, right: 14)`
|
||||
func messageLabelInset(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets
|
||||
/// - Note:
|
||||
/// The default value returned by this method is zero.
|
||||
func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat
|
||||
|
||||
// MARK: - Media Messages
|
||||
|
||||
/// Specifies the width for a `MessageContainerView`.
|
||||
/// Specifies the height for the `MessageContentCell`'s bottom label.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - message: The `MessageType` that will be displayed for this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - maxWidth: The max available width for the `MessageContainerView` respecting the cell's other content.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is the `maxWidth`.
|
||||
func widthForMedia(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat
|
||||
|
||||
/// Specifies the height for a `MessageContainerView`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - maxWidth: The max available width for the `MessageContainerView` respecting the cell's other content.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method uses `AVMakeRect(aspectRatio:insideRect:)` with a bounding
|
||||
/// rect using the `maxWidth` and `.greatestFiniteMagnitude` for the height.
|
||||
func heightForMedia(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat
|
||||
|
||||
// MARK: - Location Messages
|
||||
|
||||
/// Specifies the width for a `MessageContainerView`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - maxWidth: The max available width for the `MessageContainerView` respecting the cell's other content.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is the `maxWidth`.
|
||||
func widthForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat
|
||||
|
||||
/// Specifies the height for a `MessageContainerView`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - maxWidth: The max available width for the `MessageContainerView` respecting the cell's other content.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
func heightForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat
|
||||
|
||||
/// Specifies whether the layout attributes for a given `MessageType`
|
||||
/// should be cached by the `MessagesCollectionViewFlowLayout`.
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` whose attributes to cache.
|
||||
///
|
||||
/// The default value returned by this method is `false`.
|
||||
func shouldCacheLayoutAttributes(for message: MessageType) -> Bool
|
||||
/// - Note:
|
||||
/// The default value returned by this method is zero.
|
||||
func cellBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat
|
||||
|
||||
}
|
||||
|
||||
public extension MessagesLayoutDelegate {
|
||||
|
||||
// MARK: - All Messages Defaults
|
||||
|
||||
func messagePadding(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets {
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
if dataSource.isFromCurrentSender(message: message) {
|
||||
return UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 4)
|
||||
} else {
|
||||
return UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 30)
|
||||
}
|
||||
}
|
||||
|
||||
func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
return dataSource.isFromCurrentSender(message: message) ? .messageTrailing(.zero) : .messageLeading(.zero)
|
||||
}
|
||||
|
||||
func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
return dataSource.isFromCurrentSender(message: message) ? .messageLeading(.zero) : .messageTrailing(.zero)
|
||||
}
|
||||
|
||||
func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
|
||||
return CGSize(width: 30, height: 30)
|
||||
}
|
||||
|
||||
func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition {
|
||||
return AvatarPosition(horizontal: .natural, vertical: .messageBottom)
|
||||
}
|
||||
|
||||
func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
|
||||
guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
|
||||
fatalError(MessageKitError.nilMessagesDisplayDelegate)
|
||||
}
|
||||
let shouldDisplay = displayDelegate.shouldDisplayHeader(for: message, at: indexPath, in: messagesCollectionView)
|
||||
return shouldDisplay ? CGSize(width: messagesCollectionView.bounds.width, height: 12) : .zero
|
||||
}
|
||||
|
||||
func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
|
||||
func headerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize {
|
||||
return .zero
|
||||
}
|
||||
|
||||
func shouldCacheLayoutAttributes(for message: MessageType) -> Bool {
|
||||
return false
|
||||
func footerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize {
|
||||
return .zero
|
||||
}
|
||||
|
||||
// MARK: - Text Messages Defaults
|
||||
|
||||
func messageLabelInset(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets {
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
if dataSource.isFromCurrentSender(message: message) {
|
||||
return UIEdgeInsets(top: 7, left: 14, bottom: 7, right: 18)
|
||||
} else {
|
||||
return UIEdgeInsets(top: 7, left: 18, bottom: 7, right: 14)
|
||||
}
|
||||
func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
return 0
|
||||
}
|
||||
|
||||
// MARK: - Media Messages Defaults
|
||||
|
||||
func widthForMedia(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
return maxWidth
|
||||
}
|
||||
|
||||
func heightForMedia(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
switch message.data {
|
||||
case .photo(let image), .video(_, let image):
|
||||
let boundingRect = CGRect(origin: .zero, size: CGSize(width: maxWidth, height: .greatestFiniteMagnitude))
|
||||
return AVMakeRect(aspectRatio: image.size, insideRect: boundingRect).height
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Location Messages Defaults
|
||||
|
||||
func widthForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
return maxWidth
|
||||
func cellBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,98 +24,49 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Deprecated Protocols
|
||||
public extension MessagesLayoutDelegate {
|
||||
|
||||
@available(*, deprecated: 0.11.0, message: "LocationMessageDisplayDelegate has been deprecated in favor of MessagesDisplayDelegate")
|
||||
typealias LocationMessageDisplayDelegate = MessagesDisplayDelegate
|
||||
|
||||
@available(*, deprecated: 0.11.0, message: "TextMessageDisplayDelegate has been deprecated in favor of MessagesDisplayDelegate")
|
||||
typealias TextMessageDisplayDelegate = MessagesDisplayDelegate
|
||||
|
||||
@available(*, deprecated: 0.11.0, message: "LocationMessageLayoutDelegate has been deprecated in favor of MessagesDisplayDelegate")
|
||||
typealias LocationMessageLayoutDelegate = MessagesLayoutDelegate
|
||||
|
||||
@available(*, deprecated: 0.11.0, message: "MediaMessageLayoutDelegate has been deprecated in favor of MessagesDisplayDelegate")
|
||||
typealias MediaMessageLayoutDelegate = MessagesLayoutDelegate
|
||||
|
||||
// MARK: - AvatarAlignment
|
||||
|
||||
@available(*, deprecated: 0.11.0, message: "Removed in MessageKit 0.11.0. Please use AvatarPosition instead.")
|
||||
public enum AvatarAlignment {}
|
||||
|
||||
// MARK: - MessagesLayoutDelegate
|
||||
|
||||
extension MessagesLayoutDelegate {
|
||||
|
||||
/// Specifies the vertical alignment for the `AvatarView` in a `MessageCollectionViewCell`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is `AvatarAlignment.cellBottom`.
|
||||
@available(*, deprecated: 0.11.0, message: "Removed in MessageKit 0.11.0. Please use avatarPosition(for:at:in:) instead.")
|
||||
public func avatarAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarAlignment {
|
||||
fatalError("Please use avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition instead.")
|
||||
func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
|
||||
fatalError("avatarSize(for:at:in) has been removed in MessageKit 1.0.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - MessagesCollectionViewFlowLayout
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
/// A Boolean value that determines if the `AvatarView` is always on the leading
|
||||
/// side of a MessageCollectionViewCell.
|
||||
///
|
||||
/// Setting this property to `true` causes `avatarAlwaysTrailing` to be set to `false`.
|
||||
///
|
||||
/// The default value of this property is `false`.
|
||||
@available(*, deprecated: 0.11.0, message: "Removed in MessageKit 0.11.0. Please use the avatarPosition(for:at:in) delegate method.")
|
||||
public var avatarAlwaysLeading: Bool {
|
||||
fatalError("Fatal Error: avatarAlwaysLeading is no longer supported")
|
||||
func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition {
|
||||
fatalError("avatarPosition(for:at:in) has been removed in MessageKit 1.0.")
|
||||
}
|
||||
|
||||
/// A Boolean value that determines if the `AvatarView` is always on the trailing
|
||||
/// side of a `MessageCollectionViewCell`.
|
||||
///
|
||||
/// Setting this property to `true` causes `avatarAlwaysLeading` to be set to `false`.
|
||||
///
|
||||
/// The default value of this property is `false`.
|
||||
@available(*, deprecated: 0.11.0, message: "Removed in MessageKit 0.11.0. Please use the avatarPosition(for:at:in) delegate method instead.")
|
||||
public var avatarAlwaysTrailing: Bool {
|
||||
fatalError("Fatal Error: avatarAlwaysTrailing is no longer supported")
|
||||
|
||||
func messageLabelInset(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets {
|
||||
fatalError("messageLabelInset(for:at:in) has been removed in MessageKit 1.0")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - MessagesDataSource
|
||||
func messagePadding(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets {
|
||||
fatalError("messagePadding(for:at:in) has been removed in MessageKit 1.0.")
|
||||
}
|
||||
|
||||
extension MessagesDataSource {
|
||||
/// The `Avatar` information to be used by the `AvatarView`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is `Avatar()`.
|
||||
@available(*, deprecated: 0.12.1, message: "Removed in MessageKit 0.12.1.")
|
||||
func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar {
|
||||
fatalError("Fatal Error: avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) is no longer supported")
|
||||
func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
|
||||
fatalError("cellTopLabelAlignment(for:at:in) has been removed in MessageKit 1.0.")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MessagesViewController
|
||||
func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
|
||||
fatalError("cellBottomLabelAlignment(for:at:in) has been removed in MessageKit 1.0.")
|
||||
}
|
||||
|
||||
func widthForMedia(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
fatalError("widthForMedia(message:at:with:in) has been removed in MessageKit 1.0.")
|
||||
}
|
||||
|
||||
func heightForMedia(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
fatalError("heightForMedia(message:at:with:in) has been removed in MessageKit 1.0.")
|
||||
}
|
||||
|
||||
func widthForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
fatalError("widthForLocation(message:at:with:in) has been removed in MessageKit 1.0.")
|
||||
}
|
||||
|
||||
func heightForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
|
||||
fatalError("heightForLocation(message:at:with:in) has been removed in MessageKit 1.0.")
|
||||
}
|
||||
|
||||
extension MessagesViewController {
|
||||
/// A Boolean value that determines whether the `MessagesCollectionView` scrolls to the
|
||||
/// bottom on the view's first layout.
|
||||
///
|
||||
/// The default value of this property is `false`.
|
||||
@available(*, deprecated: 0.11.1, message: "Removed in MessageKit 0.11.1.")
|
||||
open var scrollsToBottomOnFirstLayout: Bool {
|
||||
fatalError("Fatal Error: scrollsToBottomOnFirstLayout is no longer supported")
|
||||
func shouldCacheLayoutAttributes(for message: MessageType) -> Bool {
|
||||
fatalError("shouldCacheLayoutAttributes(for:) has been removed in MessageKit 1.0.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +96,9 @@ open class AvatarView: UIImageView {
|
||||
|
||||
//// Text Drawing
|
||||
let textRect = calculateTextRect(outerViewWidth: width, outerViewHeight: height)
|
||||
let initialsText = NSAttributedString(string: initials, attributes: [.font: font])
|
||||
if adjustsFontSizeToFitWidth,
|
||||
initials.width(considering: textRect.height, and: font) > textRect.width {
|
||||
initialsText.width(considering: textRect.height) > textRect.width {
|
||||
let newFontSize = calculateFontSize(text: initials, font: font, width: textRect.width, height: textRect.height)
|
||||
font = placeholderFont.withSize(newFontSize)
|
||||
}
|
||||
@@ -119,7 +120,8 @@ open class AvatarView: UIImageView {
|
||||
Recursively find the biggest size to fit the text with a given width and height
|
||||
*/
|
||||
private func calculateFontSize(text: String, font: UIFont, width: CGFloat, height: CGFloat) -> CGFloat {
|
||||
if text.width(considering: height, and: font) > width {
|
||||
let attributedText = NSAttributedString(string: text, attributes: [.font: font])
|
||||
if attributedText.width(considering: height) > width {
|
||||
let newFont = font.withSize(font.pointSize - 1)
|
||||
if newFont.pointSize > minimumFontSize {
|
||||
return font.pointSize
|
||||
|
||||
@@ -25,14 +25,13 @@
|
||||
import UIKit
|
||||
import MapKit
|
||||
|
||||
open class LocationMessageCell: MessageCollectionViewCell {
|
||||
|
||||
open override class func reuseIdentifier() -> String { return "messagekit.cell.location" }
|
||||
|
||||
// MARK: - Properties
|
||||
/// A subclass of `MessageContentCell` used to display location messages.
|
||||
open class LocationMessageCell: MessageContentCell {
|
||||
|
||||
/// The activity indicator to be displayed while the map image is loading.
|
||||
open var activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
|
||||
|
||||
/// The image view holding the map image.
|
||||
open var imageView = UIImageView()
|
||||
|
||||
private weak var snapShotter: MKMapSnapshotter?
|
||||
@@ -45,6 +44,7 @@ open class LocationMessageCell: MessageCollectionViewCell {
|
||||
setupConstraints()
|
||||
}
|
||||
|
||||
/// Responsible for setting up the constraints of the cell's subviews.
|
||||
open func setupConstraints() {
|
||||
imageView.fillSuperview()
|
||||
activityIndicator.centerInSuperview()
|
||||
@@ -64,12 +64,12 @@ open class LocationMessageCell: MessageCollectionViewCell {
|
||||
let annotationView = displayDelegate.annotationViewForLocation(message: message, at: indexPath, in: messagesCollectionView)
|
||||
let animationBlock = displayDelegate.animationBlockForLocation(message: message, at: indexPath, in: messagesCollectionView)
|
||||
|
||||
guard case let .location(location) = message.data else { fatalError("") }
|
||||
guard case let .location(locationItem) = message.data else { fatalError("") }
|
||||
|
||||
activityIndicator.startAnimating()
|
||||
|
||||
let snapshotOptions = MKMapSnapshotOptions()
|
||||
snapshotOptions.region = MKCoordinateRegion(center: location.coordinate, span: options.span)
|
||||
snapshotOptions.region = MKCoordinateRegion(center: locationItem.location.coordinate, span: options.span)
|
||||
snapshotOptions.showsBuildings = options.showsBuildings
|
||||
snapshotOptions.showsPointsOfInterest = options.showsPointsOfInterest
|
||||
|
||||
@@ -93,7 +93,7 @@ open class LocationMessageCell: MessageCollectionViewCell {
|
||||
|
||||
snapshot.image.draw(at: .zero)
|
||||
|
||||
var point = snapshot.point(for: location.coordinate)
|
||||
var point = snapshot.point(for: locationItem.location.coordinate)
|
||||
//Move point to reflect annotation anchor
|
||||
point.x -= annotationView.bounds.size.width / 2
|
||||
point.y -= annotationView.bounds.size.height / 2
|
||||
|
||||
@@ -24,21 +24,25 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
open class MediaMessageCell: MessageCollectionViewCell {
|
||||
|
||||
open override class func reuseIdentifier() -> String { return "messagekit.cell.mediamessage" }
|
||||
|
||||
// MARK: - Properties
|
||||
/// A subclass of `MessageContentCell` used to display video and audio messages.
|
||||
open class MediaMessageCell: MessageContentCell {
|
||||
|
||||
/// The play button view to display on video messages.
|
||||
open lazy var playButtonView: PlayButtonView = {
|
||||
let playButtonView = PlayButtonView()
|
||||
return playButtonView
|
||||
}()
|
||||
|
||||
open var imageView = UIImageView()
|
||||
/// The image view display the media content.
|
||||
open var imageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
return imageView
|
||||
}()
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
/// Responsible for setting up the constraints of the cell's subviews.
|
||||
open func setupConstraints() {
|
||||
imageView.fillSuperview()
|
||||
playButtonView.centerInSuperview()
|
||||
@@ -54,15 +58,22 @@ open class MediaMessageCell: MessageCollectionViewCell {
|
||||
|
||||
open override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
|
||||
super.configure(with: message, at: indexPath, and: messagesCollectionView)
|
||||
|
||||
guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
|
||||
fatalError(MessageKitError.nilMessagesDisplayDelegate)
|
||||
}
|
||||
|
||||
switch message.data {
|
||||
case .photo(let image):
|
||||
imageView.image = image
|
||||
case .photo(let mediaItem):
|
||||
imageView.image = mediaItem.image ?? mediaItem.placeholderImage
|
||||
playButtonView.isHidden = true
|
||||
case .video(_, let image):
|
||||
imageView.image = image
|
||||
case .video(let mediaItem):
|
||||
imageView.image = mediaItem.image ?? mediaItem.placeholderImage
|
||||
playButtonView.isHidden = false
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
displayDelegate.configureMediaMessageImageView(imageView, for: message, at: indexPath, in: messagesCollectionView)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,236 +24,17 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
open class MessageCollectionViewCell: UICollectionViewCell, CollectionViewReusable {
|
||||
/// A subclass of `UICollectionViewCell` to be used inside of a `MessagesCollectionView`.
|
||||
open class MessageCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
open class func reuseIdentifier() -> String {
|
||||
return "messagekit.cell.base-cell"
|
||||
}
|
||||
|
||||
open var avatarView = AvatarView()
|
||||
|
||||
open var messageContainerView: MessageContainerView = {
|
||||
let containerView = MessageContainerView()
|
||||
containerView.clipsToBounds = true
|
||||
containerView.layer.masksToBounds = true
|
||||
return containerView
|
||||
}()
|
||||
|
||||
open var cellTopLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
open var cellBottomLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
open weak var delegate: MessageCellDelegate?
|
||||
// MARK: - Initializers
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
open func setupSubviews() {
|
||||
contentView.addSubview(messageContainerView)
|
||||
contentView.addSubview(avatarView)
|
||||
contentView.addSubview(cellTopLabel)
|
||||
contentView.addSubview(cellBottomLabel)
|
||||
}
|
||||
|
||||
open override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
cellTopLabel.text = nil
|
||||
cellTopLabel.attributedText = nil
|
||||
cellBottomLabel.text = nil
|
||||
cellBottomLabel.attributedText = nil
|
||||
}
|
||||
|
||||
// MARK: - Configuration
|
||||
|
||||
open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
|
||||
super.apply(layoutAttributes)
|
||||
guard let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes else { return }
|
||||
// Call this before other laying out other subviews
|
||||
layoutMessageContainerView(with: attributes)
|
||||
layoutAvatarView(with: attributes)
|
||||
layoutBottomLabel(with: attributes)
|
||||
layoutTopLabel(with: attributes)
|
||||
}
|
||||
|
||||
open func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
|
||||
fatalError(MessageKitError.nilMessagesDisplayDelegate)
|
||||
}
|
||||
|
||||
delegate = messagesCollectionView.messageCellDelegate
|
||||
|
||||
let messageColor = displayDelegate.backgroundColor(for: message, at: indexPath, in: messagesCollectionView)
|
||||
let messageStyle = displayDelegate.messageStyle(for: message, at: indexPath, in: messagesCollectionView)
|
||||
|
||||
displayDelegate.configureAvatarView(avatarView, for: message, at: indexPath, in: messagesCollectionView)
|
||||
|
||||
messageContainerView.backgroundColor = messageColor
|
||||
messageContainerView.style = messageStyle
|
||||
|
||||
let topText = dataSource.cellTopLabelAttributedText(for: message, at: indexPath)
|
||||
let bottomText = dataSource.cellBottomLabelAttributedText(for: message, at: indexPath)
|
||||
|
||||
cellTopLabel.attributedText = topText
|
||||
cellBottomLabel.attributedText = bottomText
|
||||
}
|
||||
|
||||
/// Handle tap gesture on contentView and its subviews like messageContainerView, cellTopLabel, cellBottomLabel, avatarView ....
|
||||
open func handleTapGesture(_ gesture: UIGestureRecognizer) {
|
||||
let touchLocation = gesture.location(in: self)
|
||||
|
||||
switch true {
|
||||
case messageContainerView.frame.contains(touchLocation) && !cellContentView(canHandle: convert(touchLocation, to: messageContainerView)):
|
||||
delegate?.didTapMessage(in: self)
|
||||
case avatarView.frame.contains(touchLocation):
|
||||
delegate?.didTapAvatar(in: self)
|
||||
case cellTopLabel.frame.contains(touchLocation):
|
||||
delegate?.didTapTopLabel(in: self)
|
||||
case cellBottomLabel.frame.contains(touchLocation):
|
||||
delegate?.didTapBottomLabel(in: self)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle long press gesture, return true when gestureRecognizer's touch point in `messageContainerView`'s frame
|
||||
open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
let touchPoint = gestureRecognizer.location(in: self)
|
||||
guard gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) else { return false }
|
||||
return messageContainerView.frame.contains(touchPoint)
|
||||
}
|
||||
|
||||
/// Handle `ContentView`'s tap gesture, return false when `ContentView` doesn't needs to handle gesture
|
||||
open func cellContentView(canHandle touchPoint: CGPoint) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Origin Calculations
|
||||
|
||||
/// Positions the cell's `AvatarView`.
|
||||
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
|
||||
open func layoutAvatarView(with attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
guard attributes.avatarSize != .zero else { return }
|
||||
|
||||
var origin: CGPoint = .zero
|
||||
|
||||
switch attributes.avatarPosition.horizontal {
|
||||
case .cellLeading:
|
||||
break
|
||||
case .cellTrailing:
|
||||
origin.x = attributes.frame.width - attributes.avatarSize.width
|
||||
case .natural:
|
||||
fatalError(MessageKitError.avatarPositionUnresolved)
|
||||
}
|
||||
|
||||
switch attributes.avatarPosition.vertical {
|
||||
case .cellTop:
|
||||
break
|
||||
case .cellBottom:
|
||||
origin.y = attributes.frame.height - attributes.avatarSize.height
|
||||
case .messageTop: // Needs messageContainerView frame to be set
|
||||
origin.y = messageContainerView.frame.minY
|
||||
case .messageBottom: // Needs messageContainerView frame to be set
|
||||
origin.y = messageContainerView.frame.maxY - attributes.avatarSize.height
|
||||
case .messageCenter: // Needs messageContainerView frame to be set
|
||||
origin.y = messageContainerView.frame.midY - (attributes.avatarSize.height/2)
|
||||
}
|
||||
|
||||
avatarView.frame = CGRect(origin: origin, size: attributes.avatarSize)
|
||||
}
|
||||
|
||||
/// Positions the cell's `MessageContainerView`.
|
||||
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
|
||||
open func layoutMessageContainerView(with attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
guard attributes.messageContainerSize != .zero else { return }
|
||||
|
||||
var origin: CGPoint = .zero
|
||||
origin.y = attributes.topLabelSize.height + attributes.messageContainerPadding.top + attributes.topLabelAlignment.insets.vertical
|
||||
|
||||
switch attributes.avatarPosition.horizontal {
|
||||
case .cellLeading:
|
||||
origin.x = attributes.avatarSize.width + attributes.messageContainerPadding.left
|
||||
case .cellTrailing:
|
||||
origin.x = attributes.frame.width - attributes.avatarSize.width - attributes.messageContainerSize.width - attributes.messageContainerPadding.right
|
||||
case .natural:
|
||||
fatalError(MessageKitError.avatarPositionUnresolved)
|
||||
}
|
||||
|
||||
messageContainerView.frame = CGRect(origin: origin, size: attributes.messageContainerSize)
|
||||
}
|
||||
|
||||
/// Positions the cell's top label.
|
||||
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
|
||||
open func layoutTopLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
guard attributes.topLabelSize != .zero else { return }
|
||||
|
||||
let topLabelAlignment = attributes.topLabelAlignment
|
||||
let topLabelPadding = topLabelAlignment.insets
|
||||
let topLabelSize = attributes.topLabelSize
|
||||
|
||||
var origin: CGPoint = .zero
|
||||
|
||||
origin.y = topLabelPadding.top
|
||||
|
||||
switch attributes.topLabelAlignment {
|
||||
case .cellLeading:
|
||||
origin.x = topLabelPadding.left
|
||||
case .cellCenter:
|
||||
origin.x = (attributes.frame.width/2) + topLabelPadding.left - topLabelPadding.right
|
||||
case .cellTrailing:
|
||||
origin.x = attributes.frame.width - topLabelSize.width - topLabelPadding.right
|
||||
case .messageLeading: // Needs messageContainerView frame to be set
|
||||
origin.x = messageContainerView.frame.minX + topLabelPadding.left
|
||||
case .messageTrailing: // Needs messageContainerView frame to be set
|
||||
origin.x = messageContainerView.frame.maxX - topLabelSize.width - topLabelPadding.right
|
||||
}
|
||||
|
||||
cellTopLabel.frame = CGRect(origin: origin, size: topLabelSize)
|
||||
}
|
||||
|
||||
/// Positions the cell's bottom label.
|
||||
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
|
||||
open func layoutBottomLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
guard attributes.bottomLabelSize != .zero else { return }
|
||||
|
||||
let bottomLabelAlignment = attributes.bottomLabelAlignment
|
||||
let bottomLabelPadding = bottomLabelAlignment.insets
|
||||
let bottomLabelSize = attributes.bottomLabelSize
|
||||
|
||||
var origin: CGPoint = .zero
|
||||
|
||||
origin.y = messageContainerView.frame.maxY + attributes.messageContainerPadding.bottom + bottomLabelPadding.top
|
||||
|
||||
switch bottomLabelAlignment {
|
||||
case .cellLeading:
|
||||
origin.x = bottomLabelPadding.left
|
||||
case .cellCenter:
|
||||
origin.x = (attributes.frame.width/2) + bottomLabelPadding.left - bottomLabelPadding.right
|
||||
case .cellTrailing:
|
||||
origin.x = attributes.frame.width - bottomLabelSize.width - bottomLabelPadding.right
|
||||
case .messageLeading:
|
||||
origin.x = messageContainerView.frame.minX + bottomLabelPadding.left
|
||||
case .messageTrailing:
|
||||
origin.x = messageContainerView.frame.maxX - bottomLabelSize.width - bottomLabelPadding.right
|
||||
}
|
||||
|
||||
cellBottomLabel.frame = CGRect(origin: origin, size: bottomLabelSize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
/// A subclass of `MessageCollectionViewCell` used to display text, media, and location messages.
|
||||
open class MessageContentCell: MessageCollectionViewCell {
|
||||
|
||||
/// The image view displaying the avatar.
|
||||
open var avatarView = AvatarView()
|
||||
|
||||
/// The container used for styling and holding the message's content view.
|
||||
open var messageContainerView: MessageContainerView = {
|
||||
let containerView = MessageContainerView()
|
||||
containerView.clipsToBounds = true
|
||||
containerView.layer.masksToBounds = true
|
||||
return containerView
|
||||
}()
|
||||
|
||||
/// The top label of the cell.
|
||||
open var cellTopLabel: InsetLabel = {
|
||||
let label = InsetLabel()
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
/// The bottom label of the cell.
|
||||
open var cellBottomLabel: InsetLabel = {
|
||||
let label = InsetLabel()
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
/// The `MessageCellDelegate` for the cell.
|
||||
open weak var delegate: MessageCellDelegate?
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
open func setupSubviews() {
|
||||
contentView.addSubview(messageContainerView)
|
||||
contentView.addSubview(avatarView)
|
||||
contentView.addSubview(cellTopLabel)
|
||||
contentView.addSubview(cellBottomLabel)
|
||||
}
|
||||
|
||||
open override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
cellTopLabel.text = nil
|
||||
cellTopLabel.attributedText = nil
|
||||
cellBottomLabel.text = nil
|
||||
cellBottomLabel.attributedText = nil
|
||||
}
|
||||
|
||||
// MARK: - Configuration
|
||||
|
||||
open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
|
||||
super.apply(layoutAttributes)
|
||||
guard let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes else { return }
|
||||
// Call this before other laying out other subviews
|
||||
layoutMessageContainerView(with: attributes)
|
||||
layoutAvatarView(with: attributes)
|
||||
layoutBottomLabel(with: attributes)
|
||||
layoutTopLabel(with: attributes)
|
||||
}
|
||||
|
||||
/// Used to configure the cell.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` this cell displays.
|
||||
/// - indexPath: The `IndexPath` for this cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell is contained.
|
||||
open func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError(MessageKitError.nilMessagesDataSource)
|
||||
}
|
||||
guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
|
||||
fatalError(MessageKitError.nilMessagesDisplayDelegate)
|
||||
}
|
||||
|
||||
delegate = messagesCollectionView.messageCellDelegate
|
||||
|
||||
let messageColor = displayDelegate.backgroundColor(for: message, at: indexPath, in: messagesCollectionView)
|
||||
let messageStyle = displayDelegate.messageStyle(for: message, at: indexPath, in: messagesCollectionView)
|
||||
|
||||
displayDelegate.configureAvatarView(avatarView, for: message, at: indexPath, in: messagesCollectionView)
|
||||
|
||||
messageContainerView.backgroundColor = messageColor
|
||||
messageContainerView.style = messageStyle
|
||||
|
||||
let topText = dataSource.cellTopLabelAttributedText(for: message, at: indexPath)
|
||||
let bottomText = dataSource.cellBottomLabelAttributedText(for: message, at: indexPath)
|
||||
|
||||
cellTopLabel.attributedText = topText
|
||||
cellBottomLabel.attributedText = bottomText
|
||||
}
|
||||
|
||||
/// Handle tap gesture on contentView and its subviews like messageContainerView, cellTopLabel, cellBottomLabel, avatarView ....
|
||||
open func handleTapGesture(_ gesture: UIGestureRecognizer) {
|
||||
let touchLocation = gesture.location(in: self)
|
||||
|
||||
switch true {
|
||||
case messageContainerView.frame.contains(touchLocation) && !cellContentView(canHandle: convert(touchLocation, to: messageContainerView)):
|
||||
delegate?.didTapMessage(in: self)
|
||||
case avatarView.frame.contains(touchLocation):
|
||||
delegate?.didTapAvatar(in: self)
|
||||
case cellTopLabel.frame.contains(touchLocation):
|
||||
delegate?.didTapTopLabel(in: self)
|
||||
case cellBottomLabel.frame.contains(touchLocation):
|
||||
delegate?.didTapBottomLabel(in: self)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle long press gesture, return true when gestureRecognizer's touch point in `messageContainerView`'s frame
|
||||
open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
let touchPoint = gestureRecognizer.location(in: self)
|
||||
guard gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) else { return false }
|
||||
return messageContainerView.frame.contains(touchPoint)
|
||||
}
|
||||
|
||||
/// Handle `ContentView`'s tap gesture, return false when `ContentView` doesn't needs to handle gesture
|
||||
open func cellContentView(canHandle touchPoint: CGPoint) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Origin Calculations
|
||||
|
||||
/// Positions the cell's `AvatarView`.
|
||||
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
|
||||
open func layoutAvatarView(with attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
guard attributes.avatarSize != .zero else { return }
|
||||
|
||||
var origin: CGPoint = .zero
|
||||
|
||||
switch attributes.avatarPosition.horizontal {
|
||||
case .cellLeading:
|
||||
break
|
||||
case .cellTrailing:
|
||||
origin.x = attributes.frame.width - attributes.avatarSize.width
|
||||
case .natural:
|
||||
fatalError(MessageKitError.avatarPositionUnresolved)
|
||||
}
|
||||
|
||||
switch attributes.avatarPosition.vertical {
|
||||
case .cellTop:
|
||||
break
|
||||
case .cellBottom:
|
||||
origin.y = attributes.frame.height - attributes.avatarSize.height
|
||||
case .messageTop: // Needs messageContainerView frame to be set
|
||||
origin.y = messageContainerView.frame.minY
|
||||
case .messageBottom: // Needs messageContainerView frame to be set
|
||||
origin.y = messageContainerView.frame.maxY - attributes.avatarSize.height
|
||||
case .messageCenter: // Needs messageContainerView frame to be set
|
||||
origin.y = messageContainerView.frame.midY - (attributes.avatarSize.height/2)
|
||||
}
|
||||
|
||||
avatarView.frame = CGRect(origin: origin, size: attributes.avatarSize)
|
||||
}
|
||||
|
||||
/// Positions the cell's `MessageContainerView`.
|
||||
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
|
||||
open func layoutMessageContainerView(with attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
guard attributes.messageContainerSize != .zero else { return }
|
||||
|
||||
var origin: CGPoint = .zero
|
||||
origin.y = attributes.topLabelSize.height + attributes.messageContainerPadding.top
|
||||
|
||||
switch attributes.avatarPosition.horizontal {
|
||||
case .cellLeading:
|
||||
origin.x = attributes.avatarSize.width + attributes.messageContainerPadding.left
|
||||
case .cellTrailing:
|
||||
origin.x = attributes.frame.width - attributes.avatarSize.width - attributes.messageContainerSize.width - attributes.messageContainerPadding.right
|
||||
case .natural:
|
||||
fatalError(MessageKitError.avatarPositionUnresolved)
|
||||
}
|
||||
|
||||
messageContainerView.frame = CGRect(origin: origin, size: attributes.messageContainerSize)
|
||||
}
|
||||
|
||||
/// Positions the cell's top label.
|
||||
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
|
||||
open func layoutTopLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
guard attributes.topLabelSize != .zero else { return }
|
||||
|
||||
cellTopLabel.textAlignment = attributes.topLabelAlignment.textAlignment
|
||||
cellTopLabel.textInsets = attributes.topLabelAlignment.textInsets
|
||||
|
||||
cellTopLabel.frame = CGRect(origin: .zero, size: attributes.topLabelSize)
|
||||
}
|
||||
|
||||
/// Positions the cell's bottom label.
|
||||
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
|
||||
open func layoutBottomLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
guard attributes.bottomLabelSize != .zero else { return }
|
||||
|
||||
cellBottomLabel.textAlignment = attributes.bottomLabelAlignment.textAlignment
|
||||
cellBottomLabel.textInsets = attributes.bottomLabelAlignment.textInsets
|
||||
|
||||
let y = messageContainerView.frame.maxY + attributes.messageContainerPadding.bottom
|
||||
let origin = CGPoint(x: 0, y: y)
|
||||
|
||||
cellBottomLabel.frame = CGRect(origin: origin, size: attributes.bottomLabelSize)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,18 +24,19 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
open class TextMessageCell: MessageCollectionViewCell {
|
||||
|
||||
open override class func reuseIdentifier() -> String { return "messagekit.cell.text" }
|
||||
/// A subclass of `MessageContentCell` used to display text messages.
|
||||
open class TextMessageCell: MessageContentCell {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The `MessageCellDelegate` for the cell.
|
||||
open override weak var delegate: MessageCellDelegate? {
|
||||
didSet {
|
||||
messageLabel.delegate = delegate
|
||||
}
|
||||
}
|
||||
|
||||
/// The label used to display the message's text.
|
||||
open var messageLabel = MessageLabel()
|
||||
|
||||
// MARK: - Methods
|
||||
@@ -44,7 +45,7 @@ open class TextMessageCell: MessageCollectionViewCell {
|
||||
super.apply(layoutAttributes)
|
||||
if let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes {
|
||||
messageLabel.textInsets = attributes.messageLabelInsets
|
||||
messageLabel.font = attributes.messageLabelFont
|
||||
messageLabel.messageLabelFont = attributes.messageLabelFont
|
||||
messageLabel.frame = messageContainerView.bounds
|
||||
}
|
||||
}
|
||||
@@ -79,18 +80,22 @@ open class TextMessageCell: MessageCollectionViewCell {
|
||||
switch message.data {
|
||||
case .text(let text), .emoji(let text):
|
||||
messageLabel.text = text
|
||||
messageLabel.textColor = textColor
|
||||
if let font = messageLabel.messageLabelFont {
|
||||
messageLabel.font = font
|
||||
}
|
||||
case .attributedText(let text):
|
||||
messageLabel.attributedText = text
|
||||
default:
|
||||
break
|
||||
}
|
||||
// Needs to be set after the attributedText because it takes precedence
|
||||
messageLabel.textColor = textColor
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle `ContentView`'s tap gesture, return false when `ContentView` don't needs to handle gesture
|
||||
/// Used to handle the cell's contentView's tap gesture.
|
||||
/// Return false when the contentView does not need to handle the gesture.
|
||||
open override func cellContentView(canHandle touchPoint: CGPoint) -> Bool {
|
||||
return messageLabel.handleGesture(touchPoint)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
open class MessageDateHeaderView: MessageHeaderView {
|
||||
open override class func reuseIdentifier() -> String { return "messagekit.header.date" }
|
||||
open class MessageDateHeaderView: MessageReusableView {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
|
||||
+6
-7
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
|
||||
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
|
||||
@@ -22,10 +22,9 @@
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
|
||||
open class MessageFooterView: UICollectionReusableView, CollectionViewReusable {
|
||||
open class func reuseIdentifier() -> String { return "messagekit.footer.base" }
|
||||
open class MessageReusableView: UICollectionReusableView {
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
@@ -46,7 +46,7 @@ open class InputStackView: UIStackView {
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
convenience init(axis: UILayoutConstraintAxis, spacing: CGFloat) {
|
||||
public convenience init(axis: UILayoutConstraintAxis, spacing: CGFloat) {
|
||||
self.init(frame: .zero)
|
||||
self.axis = axis
|
||||
self.spacing = spacing
|
||||
|
||||
+10
-12
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
|
||||
Copyright (c) 2017-2018 MessageKit
|
||||
|
||||
|
||||
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
|
||||
@@ -24,17 +24,15 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
open class MessageHeaderView: UICollectionReusableView, CollectionViewReusable {
|
||||
open class func reuseIdentifier() -> String { return "messagekit.header.base" }
|
||||
open class InsetLabel: UILabel {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
open var textInsets: UIEdgeInsets = .zero {
|
||||
didSet { setNeedsDisplay() }
|
||||
}
|
||||
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
open override func drawText(in rect: CGRect) {
|
||||
let insetRect = UIEdgeInsetsInsetRect(rect, textInsets)
|
||||
super.drawText(in: insetRect)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -112,6 +112,8 @@ open class MessageLabel: UILabel {
|
||||
if !isConfiguring { setNeedsDisplay() }
|
||||
}
|
||||
}
|
||||
|
||||
internal var messageLabelFont: UIFont?
|
||||
|
||||
private var attributesNeedUpdate = false
|
||||
|
||||
@@ -371,7 +373,7 @@ open class MessageLabel: UILabel {
|
||||
|
||||
}
|
||||
|
||||
func handleGesture(_ touchLocation: CGPoint) -> Bool {
|
||||
internal func handleGesture(_ touchLocation: CGPoint) -> Bool {
|
||||
|
||||
guard let index = stringIndex(at: touchLocation) else { return false }
|
||||
|
||||
|
||||
@@ -36,14 +36,10 @@ open class MessagesCollectionView: UICollectionView {
|
||||
|
||||
open weak var messageCellDelegate: MessageCellDelegate?
|
||||
|
||||
open var showsDateHeaderAfterTimeInterval: TimeInterval = 3600
|
||||
|
||||
private var indexPathForLastItem: IndexPath? {
|
||||
|
||||
let lastSection = numberOfSections - 1
|
||||
guard lastSection >= 0, numberOfItems(inSection: lastSection) > 0 else { return nil }
|
||||
return IndexPath(item: numberOfItems(inSection: lastSection) - 1, section: lastSection)
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Initializers
|
||||
@@ -64,7 +60,7 @@ open class MessagesCollectionView: UICollectionView {
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
func setupGestureRecognizers() {
|
||||
private func setupGestureRecognizers() {
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
|
||||
tapGesture.delaysTouchesBegan = true
|
||||
addGestureRecognizer(tapGesture)
|
||||
@@ -77,7 +73,7 @@ open class MessagesCollectionView: UICollectionView {
|
||||
let touchLocation = gesture.location(in: self)
|
||||
guard let indexPath = indexPathForItem(at: touchLocation) else { return }
|
||||
|
||||
let cell = cellForItem(at: indexPath) as? MessageCollectionViewCell
|
||||
let cell = cellForItem(at: indexPath) as? MessageContentCell
|
||||
cell?.handleTapGesture(gesture)
|
||||
}
|
||||
|
||||
@@ -106,4 +102,42 @@ open class MessagesCollectionView: UICollectionView {
|
||||
setContentOffset(newOffset, animated: false)
|
||||
}
|
||||
|
||||
/// Registers a particular cell using its reuse-identifier
|
||||
public func register<T: UICollectionViewCell>(_ cellClass: T.Type) {
|
||||
register(cellClass, forCellWithReuseIdentifier: String(describing: T.self))
|
||||
}
|
||||
|
||||
/// Registers a reusable view for a specific SectionKind
|
||||
public func register<T: UICollectionReusableView>(_ headerFooterClass: T.Type, forSupplementaryViewOfKind kind: String) {
|
||||
register(headerFooterClass,
|
||||
forSupplementaryViewOfKind: kind,
|
||||
withReuseIdentifier: String(describing: T.self))
|
||||
}
|
||||
|
||||
/// Generically dequeues a cell of the correct type allowing you to avoid scattering your code with guard-let-else-fatal
|
||||
public func dequeueReusableCell<T: UICollectionViewCell>(_ cellClass: T.Type, for indexPath: IndexPath) -> T {
|
||||
guard let cell = dequeueReusableCell(withReuseIdentifier: String(describing: T.self), for: indexPath) as? T else {
|
||||
fatalError("Unable to dequeue \(String(describing: cellClass)) with reuseId of \(String(describing: T.self))")
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
/// Generically dequeues a header of the correct type allowing you to avoid scattering your code with guard-let-else-fatal
|
||||
public func dequeueReusableHeaderView<T: UICollectionReusableView>(_ viewClass: T.Type, for indexPath: IndexPath) -> T {
|
||||
let view = dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: String(describing: T.self), for: indexPath)
|
||||
guard let viewType = view as? T else {
|
||||
fatalError("Unable to dequeue \(String(describing: viewClass)) with reuseId of \(String(describing: T.self))")
|
||||
}
|
||||
return viewType
|
||||
}
|
||||
|
||||
/// Generically dequeues a footer of the correct type allowing you to avoid scattering your code with guard-let-else-fatal
|
||||
public func dequeueReusableFooterView<T: UICollectionReusableView>(_ viewClass: T.Type, for indexPath: IndexPath) -> T {
|
||||
let view = dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionFooter, withReuseIdentifier: String(describing: T.self), for: indexPath)
|
||||
guard let viewType = view as? T else {
|
||||
fatalError("Unable to dequeue \(String(describing: viewClass)) with reuseId of \(String(describing: T.self))")
|
||||
}
|
||||
return viewType
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class MessagesViewControllerTests: XCTestCase {
|
||||
sut.messagesCollectionView.reloadData()
|
||||
|
||||
let count = sut.messagesCollectionView.numberOfSections
|
||||
let expectedCount = messagesDataSource.numberOfMessages(in: sut.messagesCollectionView)
|
||||
let expectedCount = messagesDataSource.numberOfSections(in: sut.messagesCollectionView)
|
||||
|
||||
XCTAssertEqual(count, expectedCount)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,34 @@
|
||||
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
@testable import MessageKit
|
||||
|
||||
struct MockLocationItem: LocationItem {
|
||||
|
||||
var location: CLLocation
|
||||
var size: CGSize
|
||||
|
||||
init(location: CLLocation) {
|
||||
self.location = location
|
||||
self.size = CGSize(width: 240, height: 240)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct MockMediaItem: MediaItem {
|
||||
|
||||
var url: URL?
|
||||
var image: UIImage?
|
||||
var placeholderImage: UIImage
|
||||
var size: CGSize
|
||||
|
||||
init(image: UIImage) {
|
||||
self.image = image
|
||||
self.size = CGSize(width: 240, height: 240)
|
||||
self.placeholderImage = UIImage()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct MockMessage: MessageType {
|
||||
|
||||
@@ -48,16 +76,18 @@ struct MockMessage: MessageType {
|
||||
}
|
||||
|
||||
init(image: UIImage, sender: Sender, messageId: String) {
|
||||
self.init(data: .photo(image), sender: sender, messageId: messageId)
|
||||
let mediaItem = MockMediaItem(image: image)
|
||||
self.init(data: .photo(mediaItem), sender: sender, messageId: messageId)
|
||||
}
|
||||
|
||||
init(thumbnail: UIImage, sender: Sender, messageId: String) {
|
||||
let url = URL(fileURLWithPath: "")
|
||||
self.init(data: .video(file: url, thumbnail: thumbnail), sender: sender, messageId: messageId)
|
||||
let mediaItem = MockMediaItem(image: thumbnail)
|
||||
self.init(data: .video(mediaItem), sender: sender, messageId: messageId)
|
||||
}
|
||||
|
||||
init(location: CLLocation, sender: Sender, messageId: String) {
|
||||
self.init(data: .location(location), sender: sender, messageId: messageId)
|
||||
let locationItem = MockLocationItem(location: location)
|
||||
self.init(data: .location(locationItem), sender: sender, messageId: messageId)
|
||||
}
|
||||
|
||||
init(emoji: String, sender: Sender, messageId: String) {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
@testable import MessageKit
|
||||
|
||||
class MockMessagesDataSource: MessagesDataSource {
|
||||
|
||||
@@ -34,7 +35,7 @@ class MockMessagesDataSource: MessagesDataSource {
|
||||
return senders[0]
|
||||
}
|
||||
|
||||
func numberOfMessages(in messagesCollectionView: MessagesCollectionView) -> Int {
|
||||
func numberOfSections(in messagesCollectionView: MessagesCollectionView) -> Int {
|
||||
return messages.count
|
||||
}
|
||||
|
||||
|
||||
@@ -111,69 +111,12 @@ class MessagesDisplayDelegateTests: XCTestCase {
|
||||
XCTAssertTrue(result)
|
||||
}
|
||||
|
||||
func testShouldDisplayHeaderWithoutDataSource_returnsFalseForDefault() {
|
||||
sut.messagesCollectionView.messagesDataSource = nil
|
||||
|
||||
XCTAssertFalse(sut.shouldDisplayHeader(for: sut.dataProvider.messages[0],
|
||||
at: IndexPath(item: 0, section: 0),
|
||||
in: sut.messagesCollectionView))
|
||||
}
|
||||
|
||||
func testShouldDisplayHeaderForFirstMessage_returnsFalseForDefault() {
|
||||
XCTAssertFalse(sut.shouldDisplayHeader(for: sut.dataProvider.messages[0],
|
||||
at: IndexPath(item: 0, section: 0),
|
||||
in: sut.messagesCollectionView))
|
||||
}
|
||||
|
||||
func testShouldDisplayHeaderForMessageWithTimeIntervalSinceLastMessageGreatherThanScheduled_returnsTrue() {
|
||||
var message = MockMessage(text: "Test", sender: sut.dataProvider.currentSender(), messageId: "003")
|
||||
let scheduledInterval = sut.messagesCollectionView.showsDateHeaderAfterTimeInterval
|
||||
message.sentDate = Date(timeIntervalSinceNow: scheduledInterval + 300)
|
||||
sut.dataProvider.messages.append(message)
|
||||
|
||||
XCTAssertTrue(sut.shouldDisplayHeader(for: sut.dataProvider.messages[2],
|
||||
at: IndexPath(item: 0, section: 1),
|
||||
in: sut.messagesCollectionView))
|
||||
}
|
||||
|
||||
func testShouldDisplayHeaderForMessageWithTimeIntervalSinceLastMessageEqualThanScheduled_returnsTrue() {
|
||||
var message = MockMessage(text: "Test", sender: sut.dataProvider.currentSender(), messageId: "003")
|
||||
let scheduledInterval = sut.messagesCollectionView.showsDateHeaderAfterTimeInterval
|
||||
message.sentDate = Date(timeIntervalSinceNow: scheduledInterval)
|
||||
sut.dataProvider.messages.append(message)
|
||||
|
||||
XCTAssertTrue(sut.shouldDisplayHeader(for: sut.dataProvider.messages[2],
|
||||
at: IndexPath(item: 0, section: 1),
|
||||
in: sut.messagesCollectionView))
|
||||
}
|
||||
|
||||
func testShouldDisplayHeaderForMessageWithTimeIntervalSinceLastMessageLessThanScheduled_returnsFalse() {
|
||||
var message = MockMessage(text: "Test", sender: sut.dataProvider.currentSender(), messageId: "003")
|
||||
let scheduledInterval = sut.messagesCollectionView.showsDateHeaderAfterTimeInterval
|
||||
message.sentDate = Date(timeIntervalSinceNow: scheduledInterval - 300)
|
||||
sut.dataProvider.messages.append(message)
|
||||
|
||||
XCTAssertFalse(sut.shouldDisplayHeader(for: sut.dataProvider.messages[2],
|
||||
at: IndexPath(item: 0, section: 1),
|
||||
in: sut.messagesCollectionView))
|
||||
}
|
||||
|
||||
func testMessageHeaderView_isNotNil() {
|
||||
let headerView = sut.messageHeaderView(for: sut.dataProvider.messages[1],
|
||||
at: IndexPath(item: 0, section: 1),
|
||||
in: sut.messagesCollectionView)
|
||||
|
||||
let indexPath = IndexPath(item: 0, section: 1)
|
||||
let headerView = sut.messageHeaderView(for: indexPath, in: sut.messagesCollectionView)
|
||||
XCTAssertNotNil(headerView)
|
||||
}
|
||||
|
||||
func testMessageFooterView_isNotNil() {
|
||||
let footerView = sut.messageFooterView(for: sut.dataProvider.messages[1],
|
||||
at: IndexPath(item: 0, section: 1),
|
||||
in: sut.messagesCollectionView)
|
||||
|
||||
XCTAssertNotNil(footerView)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TextMessageDisplayDelegateTests: XCTestCase {
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
import XCTest
|
||||
@testable import MessageKit
|
||||
|
||||
class MessageCollectionViewCellTests: XCTestCase {
|
||||
final class MessageContentCellTests: XCTestCase {
|
||||
|
||||
var cell: MessageCollectionViewCell!
|
||||
var cell: MessageContentCell!
|
||||
let frame = CGRect(x: 0, y: 0, width: 100, height: 100)
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
cell = MessageCollectionViewCell(frame: frame)
|
||||
cell = MessageContentCell(frame: frame)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -74,7 +74,7 @@ class MessageCollectionViewCellTests: XCTestCase {
|
||||
|
||||
}
|
||||
|
||||
extension MessageCollectionViewCellTests {
|
||||
extension MessageContentCellTests {
|
||||
|
||||
fileprivate class MockMessagesDisplayDelegate: MessagesDisplayDelegate {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user