diff --git a/.circleci/config.yml b/.circleci/config.yml
index e4c9ab21..69c219d4 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -8,11 +8,11 @@ jobs:
build-and-test:
environment:
- - DESTINATION: "platform=iOS Simulator,name=iPhone XS"
+ - DESTINATION: "platform=iOS Simulator,name=iPhone 11 Pro"
# Specify the Xcode version to use.
macos:
- xcode: "10.0.0"
+ xcode: "11.3.1"
# Define the steps required to build the project.
steps:
diff --git a/.swift-version b/.swift-version
index bf77d549..819e07a2 100644
--- a/.swift-version
+++ b/.swift-version
@@ -1 +1 @@
-4.2
+5.0
diff --git a/.swiftlint.yml b/.swiftlint.yml
index 8bf8a3da..5ebf9e77 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -12,7 +12,6 @@ custom_rules:
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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7aba7f2d..684b8da6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,41 @@
The changelog for `MessageKit`. Also see the [releases](https://github.com/MessageKit/MessageKit/releases) on GitHub.
+## 3.1.0
+
+### Fixed
+
+ - Set the proper notification to invalidate layout. MessageKit now relies on `UIApplication` orientation notification instead of `UIDevice`, which invalidates the layout only when it is needed. [#1126](https://github.com/MessageKit/MessageKit/pull/1126) by [@bguidolim](https://github.com/bguidolim)
+
+ - Fixed `requiredInitialScrollViewBottomInset` when `inputAccessoryView` is `nil` [#1218](https://github.com/MessageKit/MessageKit/pull/1218) by [@aabosh](https://github.com/aabosh)
+
+### Added
+
+- Add missing textAlignment and textInsets assignments to layoutCellTopLabel method in MessageContentCell. [#1117](https://github.com/MessageKit/MessageKit/pull/1117) by [@mdescalzo](https://github.com/mdescalzo)
+
+- Add support for styling NSLinkAttribute with existing urlAttributes in MessageLabel. [#1091](https://github.com/MessageKit/MessageKit/pull/1091) by [@marcetcheverry](https://github.com/marcetcheverry)
+
+- Add loading indicator to AudioMessageCell. [#1084](https://github.com/MessageKit/MessageKit/pull/1084) by [@marcetcheverry](https://github.com/marcetcheverry)
+
+- Lazily initialize the MessageInputBar on MessagesViewController. [#1092](https://github.com/MessageKit/MessageKit/pull/1092) by [@marcetcheverry](https://github.com/marcetcheverry)
+
+- Add support for Dark Mode [#1189](https://github.com/MessageKit/MessageKit/pull/1189) by [@Vlada31R](https://github.com/Vlada31R)
+
+- Add support for `scrollToLastItem` and `scrollsToLastItemOnKeyboardBeginsEditing` [#1247](https://github.com/MessageKit/MessageKit/pull/1247) by [@youuu](https://github.com/youuu)
+
+### Changed
+
+- **Breaking Change** Updated to Swift 5.0 [#1039](https://github.com/MessageKit/MessageKit/pull/1039) by [@nathantannar4](https://github.com/nathantannar4)
+
+- Fixes scrollToBottom method to properly handle calls made early in the view lifecycle. [#1110](https://github.com/MessageKit/MessageKit/pull/1110) by [@marcetcheverry](https://github.com/marcetcheverry)
+
+- Add missing cellTopLabelAlignment to MessageSizeCalculator. [#1113](https://github.com/MessageKit/MessageKit/pull/1113) by [@marcetcheverry](https://github.com/marcetcheverry)
+
+- **Breaking Change** `MessageInputBar`, and `MessageInputBarDelegate` have been obsoleted. Use`InputBarAccessoryView` and `InputBarAccessoryViewDelegate` respectively. This change was previously meant for 3.0.0 but was implemented erroneously. [#1201](https://github.com/MessageKit/MessageKit/pull/1201) by [@kinoroy](https://github.com/kinoroy)
+
+- Deprecated `SenderType.id` in favour of `SenderType.senderId`. This change was previously meant for 3.0.0 but
+ was implemented erroneously. [#1201](https://github.com/MessageKit/MessageKit/pull/1201) by [@kinoroy](https://github.com/kinoroy)
+
## 3.0.0
### Dependency Changes
diff --git a/Cartfile b/Cartfile
index 50814158..5e9a95f2 100644
--- a/Cartfile
+++ b/Cartfile
@@ -1 +1 @@
-github "nathantannar4/InputBarAccessoryView" "4.2.1"
+github "nathantannar4/InputBarAccessoryView" ~> 4.3.0
diff --git a/Cartfile.private b/Cartfile.private
index 70f82c40..29d169d6 100644
--- a/Cartfile.private
+++ b/Cartfile.private
@@ -1,2 +1,2 @@
github "Quick/Quick" ~> 2.0.0
-github "Quick/Nimble" ~> 8.0.1
+github "Quick/Nimble" ~> 8.0.0
diff --git a/Cartfile.resolved b/Cartfile.resolved
index de18a3b8..04ee5dc4 100644
--- a/Cartfile.resolved
+++ b/Cartfile.resolved
@@ -1,3 +1,3 @@
-github "Quick/Nimble" "v7.3.4"
-github "Quick/Quick" "v1.3.4"
-github "nathantannar4/InputBarAccessoryView" "4.2.2"
+github "Quick/Nimble" "v8.0.5"
+github "Quick/Quick" "v2.2.0"
+github "nathantannar4/InputBarAccessoryView" "4.3.2"
diff --git a/Documentation/CUSTOM_CELLS.md b/Documentation/CUSTOM_CELLS.md
new file mode 100644
index 00000000..bdff909a
--- /dev/null
+++ b/Documentation/CUSTOM_CELLS.md
@@ -0,0 +1,82 @@
+# MessageKit Custom Cell Guide(s)
+
+- [How can I add a custom cell?](#how-can-i-add-a-custom-cell)
+
+## How can I add a custom cell?
+
+**Note:** If you choose to use the `.custom` kind you are responsible for all of the cell's layout. You can design the cell in code or Interface Builder. Any `UICollectionViewCell` can be returned for custom cells which means any of the styling you provide from the `MessageDisplayDelegate` will not affect your custom cell, even if you subclass your cell from `MessageContentCell`.
+
+**Creating a custom cell involves four parts:**
+1. Build a cell in Interface Builder or code that inherits from `UICollectionViewCell`
+2. Set the size of your cell. Subclass `MessageSizeCalculator` if you want your cell to have the default MessageKit layout design. Subclass `CellSizeCalculator` if you want to further customize your own cell design. The implementation of this class will allow your custom cell to automatically size itself within the `messagesCollectionView`.
+3. Add your custom cell size to the collection view flow layout. Subclass `MessagesCollectionViewFlowLayout`, and use the custom message size calculator from step 2, above.
+4. Register your custom cell and reference your custom collection view flow layout.
+
+
+### Example:
+
+Let's take a look at what it takes to create a custom cell that displays a red block for your custom message.
+
+- **1. Custom Cell**: We create a cell that inherits from `UICollectionViewCell`
+**MyCustomCell.swift**
+```swift
+open class MyCustomCell: UICollectionViewCell {
+ open func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
+ self.contentView.backgroundColor = UIColor.red
+ }
+}
+```
+
+- **2. MessageSizeCalculator**: We set the size of our cell by creating a class that inherits from `MessageSizeCalculator`
+**CustomMessageSizeCalculator.swift**
+```swift
+open class CustomMessageSizeCalculator: MessageSizeCalculator {
+ open override func messageContainerSize(for message: MessageType) -> CGSize {
+ // Customize this function implementation to size your content appropriately. This example simply returns a constant size
+ // Refer to the default MessageKit cell implementations, and the Example App to see how to size a custom cell dynamically
+ return CGSize(width: 300, height: 130)
+ }
+}
+```
+
+- **3. MessageFlowLayout**: We add our custom message size calculator to our collection view layout by creating a class that inherits from `MessagesCollectionViewFlowLayout`
+**MyCustomMessagesFlowLayout.swift**
+```swift
+open class MyCustomMessagesFlowLayout: MessagesCollectionViewFlowLayout {
+ lazy open var customMessageSizeCalculator = CustomMessageSizeCalculator(layout: self)
+
+ override open func cellSizeCalculatorForItem(at indexPath: IndexPath) -> CellSizeCalculator {
+ let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+ if case .custom = message.kind {
+ return customMessageSizeCalculator
+ }
+ return super.cellSizeCalculatorForItem(at: indexPath);
+ }
+}
+```
+- **4. Implementation**: We register our custom cell and reference our newly created `MyCustomMessagesFlowLayout.swift`
+**ConversationViewController.swift**
+```swift
+internal class ConversationViewController: MessagesViewController {
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ messagesCollectionView = MessagesCollectionView(frame: .zero, collectionViewLayout: MyCustomMessagesFlowLayout())
+ messagesCollectionView.register(MyCustomCell.self)
+ //...
+ }
+//...
+ override open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
+ fatalError("Ouch. nil data source for messages")
+ }
+
+ let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+ if case .custom = message.kind {
+ let cell = messagesCollectionView.dequeueReusableCell(MyCustomCell.self, for: indexPath)
+ cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+ return cell
+ }
+ return super.collectionView(collectionView, cellForItemAt: indexPath)
+ }
+}
+```
diff --git a/Example/.Podfile.un~ b/Example/.Podfile.un~
deleted file mode 100644
index 8e635e87..00000000
Binary files a/Example/.Podfile.un~ and /dev/null differ
diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj
index 86d703d4..89a85340 100644
--- a/Example/ChatExample.xcodeproj/project.pbxproj
+++ b/Example/ChatExample.xcodeproj/project.pbxproj
@@ -371,7 +371,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0730;
- LastUpgradeCheck = 0930;
+ LastUpgradeCheck = 1020;
ORGANIZATIONNAME = MessageKit;
TargetAttributes = {
882B5E321CF7D4B900B6E160 = {
@@ -391,7 +391,7 @@
};
buildConfigurationList = 882B5E2E1CF7D4B900B6E160 /* Build configuration list for PBXProject "ChatExample" */;
compatibilityVersion = "Xcode 3.2";
- developmentRegion = English;
+ developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -523,7 +523,7 @@
);
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";
+ shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
@@ -603,6 +603,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -651,7 +652,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 4.0;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -660,6 +661,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -700,7 +702,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
- SWIFT_VERSION = 4.0;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@@ -710,14 +712,13 @@
isa = XCBuildConfiguration;
baseConfigurationReference = A830E27DBE0B66B89C5D2EB8 /* Pods-ChatExample.debug.xcconfig */;
buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.messagekit.ChatExample;
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 4.2;
+ SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -725,7 +726,6 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9E0D67CD75BA7EB323FD391B /* Pods-ChatExample.release.xcconfig */;
buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
@@ -733,7 +733,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.messagekit.ChatExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 4.2;
+ SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -746,7 +746,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.hexedbits.ChatExampleTests;
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 4.2;
+ SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChatExample.app/ChatExample";
};
name = Debug;
@@ -761,7 +761,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.hexedbits.ChatExampleTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 4.2;
+ SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChatExample.app/ChatExample";
};
name = Release;
@@ -774,7 +774,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.hexedbits.ChatExampleUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 4.2;
+ SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = ChatExample;
};
name = Debug;
@@ -788,7 +788,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.hexedbits.ChatExampleUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 4.2;
+ SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = ChatExample;
};
name = Release;
diff --git a/Example/ChatExample.xcodeproj/xcshareddata/xcschemes/ChatExample.xcscheme b/Example/ChatExample.xcodeproj/xcshareddata/xcschemes/ChatExample.xcscheme
index e34206a1..31f17b89 100644
--- a/Example/ChatExample.xcodeproj/xcshareddata/xcschemes/ChatExample.xcscheme
+++ b/Example/ChatExample.xcodeproj/xcshareddata/xcschemes/ChatExample.xcscheme
@@ -1,6 +1,6 @@
4.3.0)
DEPENDENCIES:
- MessageKit (from `../`)
SPEC REPOS:
- https://github.com/cocoapods/specs.git:
+ trunk:
- InputBarAccessoryView
EXTERNAL SOURCES:
@@ -15,9 +17,9 @@ EXTERNAL SOURCES:
:path: "../"
SPEC CHECKSUMS:
- InputBarAccessoryView: 2b937602598e2fab3149f37f51dd7ad795653812
- MessageKit: feb3ca4fa02a14f3391781323ced1da84a0aa73c
+ InputBarAccessoryView: 58a348be7ea2736c7eec60e5c315511c2dbb39fd
+ MessageKit: 6b809a162328346e0727dbcd040b5f9fd5f5ba15
-PODFILE CHECKSUM: cecdb7bc8129cf99f66de9f68eea3256fec30c3d
+PODFILE CHECKSUM: 90bd12ca685503630d7fe716609f4974660ed5ee
-COCOAPODS: 1.6.1
+COCOAPODS: 1.8.4
diff --git a/Example/Podfile~ b/Example/Podfile~
deleted file mode 100644
index bf44d23b..00000000
--- a/Example/Podfile~
+++ /dev/null
@@ -1,16 +0,0 @@
-platform :ios, '9.0'
-
-target 'ChatExample' do
- use_frameworks!
- pod 'MessageKit', :path => '../'
- pod 'MessageInputBar', :git => 'https://github.com/MessageKit/MessageInputBar.git', :branch => 'master'
-
-target 'ChatExampleTests' do
- inherit! :search_paths
-end
-
-target 'ChatExampleUITests' do
- inherit! :search_paths
-end
-
-end
diff --git a/Example/Sources/Data Generation/Lorem.swift b/Example/Sources/Data Generation/Lorem.swift
index 47597730..b368af71 100755
--- a/Example/Sources/Data Generation/Lorem.swift
+++ b/Example/Sources/Data Generation/Lorem.swift
@@ -222,7 +222,7 @@ public extension Array {
/**
Shuffle the array in-place using the Fisher-Yates algorithm.
*/
- public mutating func shuffle() {
+ mutating func shuffle() {
for i in 0..<(count - 1) {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
if j != i {
@@ -237,7 +237,7 @@ public extension Array {
- returns: Returns a shuffled version of the array.
*/
- public func shuffled() -> [Element] {
+ func shuffled() -> [Element] {
var list = self
list.shuffle()
@@ -249,7 +249,7 @@ public extension Array {
- returns: Returns a random element from the array or `nil` if the
array is empty.
*/
- public func random() -> Element? {
+ func random() -> Element? {
return (count > 0) ? self.shuffled()[0] : nil
}
@@ -257,7 +257,7 @@ public extension Array {
Return a random subset of `cnt` elements from the array.
- returns: Returns a random subset of `cnt` elements from the array.
*/
- public func random(_ count: Int = 1) -> [Element] {
+ func random(_ count: Int = 1) -> [Element] {
let result = shuffled()
return (count > result.count) ? result : Array(result[0.. "MIT", :file => "LICENSE.md" }
s.summary = 'An elegant messages UI library for iOS.'
@@ -12,16 +12,16 @@ Pod::Spec.new do |s|
s.source_files = 'Sources/**/*.swift'
s.pod_target_xcconfig = {
- "SWIFT_VERSION" => "4.2",
+ "SWIFT_VERSION" => "5.0",
}
- s.swift_version = '4.2'
+ s.swift_version = '5.0'
s.ios.deployment_target = '9.0'
s.ios.resource_bundle = { 'MessageKitAssets' => 'Assets/MessageKitAssets.bundle/Images' }
s.requires_arc = true
- s.dependency 'InputBarAccessoryView', '~> 4.2.2'
+ s.dependency 'InputBarAccessoryView', '~> 4.3.0'
end
diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj
index 4b6ecfac..72509f16 100644
--- a/MessageKit.xcodeproj/project.pbxproj
+++ b/MessageKit.xcodeproj/project.pbxproj
@@ -563,7 +563,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0730;
- LastUpgradeCheck = 1010;
+ LastUpgradeCheck = 1020;
ORGANIZATIONNAME = MessageKit;
TargetAttributes = {
88916B211CF0DF2F00469F91 = {
@@ -577,11 +577,11 @@
};
buildConfigurationList = 88916B1C1CF0DF2F00469F91 /* Build configuration list for PBXProject "MessageKit" */;
compatibilityVersion = "Xcode 3.2";
- developmentRegion = English;
+ developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
- English,
en,
+ Base,
);
mainGroup = 88916B181CF0DF2F00469F91;
productRefGroup = 88916B231CF0DF2F00469F91 /* Products */;
@@ -788,7 +788,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 4.2;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -841,7 +841,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
- SWIFT_VERSION = 4.2;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
@@ -871,7 +871,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 4.2;
+ SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -897,7 +897,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 4.2;
+ SWIFT_VERSION = 5.0;
};
name = Release;
};
diff --git a/MessageKit.xcodeproj/xcshareddata/xcschemes/MessageKit.xcscheme b/MessageKit.xcodeproj/xcshareddata/xcschemes/MessageKit.xcscheme
index edb000ac..1f6e5d93 100644
--- a/MessageKit.xcodeproj/xcshareddata/xcschemes/MessageKit.xcscheme
+++ b/MessageKit.xcodeproj/xcshareddata/xcschemes/MessageKit.xcscheme
@@ -1,6 +1,6 @@
-
-
-
-
+
+
+
+ A community-driven replacement for JSQMessagesViewController https://messagekit.github.io
[](https://circleci.com/gh/MessageKit/MessageKit)
@@ -29,6 +28,10 @@
alt="Contributions Welcome">
+
+
+
+
## Goals
- Provide a :rotating_light:safe:rotating_light: environment for others to learn and grow through Open Source.
@@ -43,11 +46,14 @@ See [VISION.md](https://github.com/MessageKit/MessageKit/blob/master/VISION.md)
## Installation
### [CocoaPods](https://cocoapods.org/) **Recommended**
````ruby
-# Swift 4.2
+# Swift 5.0
pod 'MessageKit'
````
-
-> If you are already using Swift 5, use the `3.0.0-swift5` branch until the offical release is made
+> For Swift 4.2 use version 3.0.0
+````ruby
+# Swift 4.2
+pod 'MessageKit', '~> 3.0.0'
+````
### [Carthage](https://github.com/Carthage/Carthage)
@@ -61,9 +67,8 @@ github "MessageKit/MessageKit"
## Requirements
-- **iOS9** or later
-- **Swift 4.2** or later
-
+- **iOS 9** or later
+- **Swift 5.0** or later
## Getting Started
@@ -120,6 +125,7 @@ public enum MessageKind {
```
If you choose to use the `.custom` kind you are responsible for all of the cells layout. Any `UICollectionViewCell` can be returned for custom cells which means any of the styling you provide from the `MessageDisplayDelegate` will not effect your custom cell. Even if you subclass your cell from `MessageContentCell`.
+[Read more about custom cells](https://github.com/MessageKit/MessageKit/blob/master/Documentation/CUSTOM_CELLS.md)
[Read more about the cases on the Quick Start guide.](https://github.com/MessageKit/MessageKit/blob/master/Documentation/QuickStart.md#messagekind)
diff --git a/Sources/Controllers/MessagesViewController+Keyboard.swift b/Sources/Controllers/MessagesViewController+Keyboard.swift
index c3b96310..2abbd030 100644
--- a/Sources/Controllers/MessagesViewController+Keyboard.swift
+++ b/Sources/Controllers/MessagesViewController+Keyboard.swift
@@ -29,13 +29,13 @@ internal extension MessagesViewController {
// MARK: - Register / Unregister Observers
- internal func addKeyboardObservers() {
+ func addKeyboardObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.handleKeyboardDidChangeState(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.handleTextViewDidBeginEditing(_:)), name: UITextView.textDidBeginEditingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.adjustScrollViewTopInset), name: UIDevice.orientationDidChangeNotification, object: nil)
}
- internal func removeKeyboardObservers() {
+ func removeKeyboardObservers() {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UITextView.textDidBeginEditingNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
@@ -45,9 +45,15 @@ internal extension MessagesViewController {
@objc
private func handleTextViewDidBeginEditing(_ notification: Notification) {
- if scrollsToBottomOnKeyboardBeginsEditing {
- guard let inputTextView = notification.object as? InputTextView, inputTextView === messageInputBar.inputTextView else { return }
- messagesCollectionView.scrollToBottom(animated: true)
+ if scrollsToLastItemOnKeyboardBeginsEditing || scrollsToBottomOnKeyboardBeginsEditing {
+ guard let inputTextView = notification.object as? InputTextView,
+ inputTextView === messageInputBar.inputTextView else { return }
+
+ if scrollsToLastItemOnKeyboardBeginsEditing {
+ messagesCollectionView.scrollToLastItem()
+ } else {
+ messagesCollectionView.scrollToBottom(animated: true)
+ }
}
}
@@ -62,12 +68,12 @@ internal extension MessagesViewController {
// ignore this notification.
return
}
-
+
guard self.presentedViewController == nil else {
// This is important to skip notifications from child modal controllers in iOS >= 13.0
return
}
-
+
// Note that the check above does not exclude all notifications from an undocked keyboard, only the weird ones.
//
// We've tried following Apple's recommended approach of tracking UIKeyboardWillShow / UIKeyboardDidHide and ignoring frame
@@ -82,25 +88,25 @@ internal extension MessagesViewController {
// We could make it work by adding extra checks for the state of the keyboard and compensating accordingly, but it seems easier
// to simply check whether the current keyboard frame, whatever it is (even when undocked), covers the bottom of the collection
// view.
-
+
guard let keyboardEndFrameInScreenCoords = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
let keyboardEndFrame = view.convert(keyboardEndFrameInScreenCoords, from: view.window)
-
+
let newBottomInset = requiredScrollViewBottomInset(forKeyboardFrame: keyboardEndFrame)
let differenceOfBottomInset = newBottomInset - messageCollectionViewBottomInset
-
+
if maintainPositionOnKeyboardFrameChanged && differenceOfBottomInset != 0 {
let contentOffset = CGPoint(x: messagesCollectionView.contentOffset.x, y: messagesCollectionView.contentOffset.y + differenceOfBottomInset)
messagesCollectionView.setContentOffset(contentOffset, animated: false)
}
-
+
messageCollectionViewBottomInset = newBottomInset
}
// MARK: - Inset Computation
@objc
- internal func adjustScrollViewTopInset() {
+ func adjustScrollViewTopInset() {
if #available(iOS 11.0, *) {
// No need to add to the top contentInset
} else {
@@ -116,7 +122,7 @@ internal extension MessagesViewController {
// we only need to adjust for the part of the keyboard that covers (i.e. intersects) our collection view;
// see https://developer.apple.com/videos/play/wwdc2017/242/ for more details
let intersection = messagesCollectionView.frame.intersection(keyboardFrame)
-
+
if intersection.isNull || (messagesCollectionView.frame.maxY - intersection.maxY) > 0.001 {
// The keyboard is hidden, is a hardware one, or is undocked and does not cover the bottom of the collection view.
// Note: intersection.maxY may be less than messagesCollectionView.frame.maxY when dealing with undocked keyboards.
@@ -126,9 +132,9 @@ internal extension MessagesViewController {
}
}
- internal func requiredInitialScrollViewBottomInset() -> CGFloat {
- guard let inputAccessoryView = inputAccessoryView else { return 0 }
- return max(0, inputAccessoryView.frame.height + additionalBottomInset - automaticallyAddedBottomInset)
+ func requiredInitialScrollViewBottomInset() -> CGFloat {
+ let inputAccessoryViewHeight = inputAccessoryView?.frame.height ?? 0
+ return max(0, inputAccessoryViewHeight + additionalBottomInset - automaticallyAddedBottomInset)
}
/// iOS 11's UIScrollView can automatically add safe area insets to its contentInset,
diff --git a/Sources/Controllers/MessagesViewController+Menu.swift b/Sources/Controllers/MessagesViewController+Menu.swift
index fefe53ee..9a02f20b 100644
--- a/Sources/Controllers/MessagesViewController+Menu.swift
+++ b/Sources/Controllers/MessagesViewController+Menu.swift
@@ -28,11 +28,11 @@ internal extension MessagesViewController {
// MARK: - Register / Unregister Observers
- internal func addMenuControllerObservers() {
+ func addMenuControllerObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.menuControllerWillShow(_:)), name: UIMenuController.willShowMenuNotification, object: nil)
}
- internal func removeMenuControllerObservers() {
+ func removeMenuControllerObservers() {
NotificationCenter.default.removeObserver(self, name: UIMenuController.willShowMenuNotification, object: nil)
}
diff --git a/Sources/Controllers/MessagesViewController.swift b/Sources/Controllers/MessagesViewController.swift
index 0e154c4f..e74b0183 100644
--- a/Sources/Controllers/MessagesViewController.swift
+++ b/Sources/Controllers/MessagesViewController.swift
@@ -34,12 +34,20 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
open var messagesCollectionView = MessagesCollectionView()
/// The `InputBarAccessoryView` used as the `inputAccessoryView` in the view controller.
- open var messageInputBar = InputBarAccessoryView()
+ open lazy var messageInputBar = InputBarAccessoryView()
+
+ /// A Boolean value that determines whether the `MessagesCollectionView` scrolls to the
+ /// last item whenever the `InputTextView` begins editing.
+ ///
+ /// The default value of this property is `false`.
+ /// NOTE: This is related to `scrollToLastItem` whereas the below flag is related to `scrollToBottom` - check each function for differences
+ open var scrollsToLastItemOnKeyboardBeginsEditing: Bool = false
/// A Boolean value that determines whether the `MessagesCollectionView` scrolls to the
/// bottom whenever the `InputTextView` begins editing.
///
/// The default value of this property is `false`.
+ /// NOTE: This is related to `scrollToBottom` whereas the above flag is related to `scrollToLastItem` - check each function for differences
open var scrollsToBottomOnKeyboardBeginsEditing: Bool = false
/// A Boolean value that determines whether the `MessagesCollectionView`
@@ -146,9 +154,10 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
private func setupDefaults() {
extendedLayoutIncludesOpaqueBars = true
automaticallyAdjustsScrollViewInsets = false
- view.backgroundColor = .white
+ view.backgroundColor = .backgroundColor
messagesCollectionView.keyboardDismissMode = .interactive
messagesCollectionView.alwaysBounceVertical = true
+ messagesCollectionView.backgroundColor = .backgroundColor
}
private func setupDelegates() {
diff --git a/Sources/Extensions/Bundle+Extensions.swift b/Sources/Extensions/Bundle+Extensions.swift
index 2cca400d..ea13d297 100644
--- a/Sources/Extensions/Bundle+Extensions.swift
+++ b/Sources/Extensions/Bundle+Extensions.swift
@@ -26,7 +26,7 @@ import Foundation
internal extension Bundle {
- static func messageKitAssetBundle() -> Bundle { // swiftlint:disable:this explicit_acl
+ static func messageKitAssetBundle() -> Bundle {
let podBundle = Bundle(for: MessagesViewController.self)
guard let resourceBundleUrl = podBundle.url(forResource: "MessageKitAssets", withExtension: "bundle") else {
diff --git a/Sources/Extensions/CGRect+Extensions.swift b/Sources/Extensions/CGRect+Extensions.swift
index b3cadbd1..f5112a3d 100644
--- a/Sources/Extensions/CGRect+Extensions.swift
+++ b/Sources/Extensions/CGRect+Extensions.swift
@@ -26,7 +26,7 @@ import Foundation
internal extension CGRect {
- init(_ x: CGFloat, _ y: CGFloat, _ w: CGFloat, _ h: CGFloat) { // swiftlint:disable:this explicit_acl
+ init(_ x: CGFloat, _ y: CGFloat, _ w: CGFloat, _ h: CGFloat) {
self.init(x: x, y: y, width: w, height: h)
}
diff --git a/Sources/Extensions/NSAttributedString+Extensions.swift b/Sources/Extensions/NSAttributedString+Extensions.swift
index 4025dac0..e0fe3760 100644
--- a/Sources/Extensions/NSAttributedString+Extensions.swift
+++ b/Sources/Extensions/NSAttributedString+Extensions.swift
@@ -26,7 +26,7 @@ import Foundation
internal extension NSAttributedString {
- func width(considering height: CGFloat) -> CGFloat { // swiftlint:disable:this explicit_acl
+ func width(considering height: CGFloat) -> CGFloat {
let constraintBox = CGSize(width: .greatestFiniteMagnitude, height: height)
let rect = self.boundingRect(with: constraintBox, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
diff --git a/Sources/Extensions/UIColor+Extensions.swift b/Sources/Extensions/UIColor+Extensions.swift
index 3c09bbcb..57f74578 100644
--- a/Sources/Extensions/UIColor+Extensions.swift
+++ b/Sources/Extensions/UIColor+Extensions.swift
@@ -24,18 +24,97 @@
import Foundation
-// swiftlint:disable explicit_acl
+internal extension UIColor {
+
+ static var incomingGray: UIColor {
+ if #available(iOS 13, *) {
+ return UIColor.systemGray5
+ } else {
+ return UIColor(red: 230/255, green: 230/255, blue: 235/255, alpha: 1.0)
+ }
+ }
+
+ static var outgoingGreen: UIColor {
+ if #available(iOS 13, *) {
+ return UIColor.systemGreen
+ } else {
+ return UIColor(red: 69/255, green: 214/255, blue: 93/255, alpha: 1.0)
+ }
+ }
+
+ static var inputBarGray: UIColor {
+ if #available(iOS 13, *) {
+ return UIColor.systemGray2
+ } else {
+ return UIColor(red: 247/255, green: 247/255, blue: 247/255, alpha: 1.0)
+ }
+ }
+
+ static var playButtonLightGray: UIColor {
+ if #available(iOS 13, *) {
+ return UIColor.systemGray6
+ } else {
+ return UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1.0)
+ }
+ }
+
+ static var sendButtonBlue: UIColor {
+ if #available(iOS 13, *) {
+ return UIColor.systemBlue
+ } else {
+ return UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0)
+ }
+ }
+}
internal extension UIColor {
- static let incomingGray = UIColor(red: 230/255, green: 230/255, blue: 235/255, alpha: 1.0)
+ static var backgroundColor: UIColor {
+ if #available(iOS 13, *) {
+ return systemBackground
+ } else {
+ return white
+ }
+ }
- static let outgoingGreen = UIColor(red: 69/255, green: 214/255, blue: 93/255, alpha: 1.0)
+ static var labelColor: UIColor {
+ if #available(iOS 13, *) {
+ return label
+ } else {
+ return black
+ }
+ }
- static let inputBarGray = UIColor(red: 247/255, green: 247/255, blue: 247/255, alpha: 1.0)
+ static var placeholderTextColor: UIColor {
+ if #available(iOS 13, *) {
+ return placeholderText
+ } else {
+ return .darkGray
+ }
+ }
- static let playButtonLightGray = UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1.0)
+ static var grayColor: UIColor {
+ if #available(iOS 13, *) {
+ return .systemGray
+ } else {
+ return gray
+ }
+ }
- static let sendButtonBlue = UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0)
+ static var darkTextColor: UIColor {
+ if #available(iOS 13, *) {
+ return .systemGray
+ } else {
+ return darkText
+ }
+ }
+ static var lightGrayColor: UIColor {
+ if #available(iOS 13, *) {
+ return .systemGray5
+ } else {
+ return .lightGray
+ }
+ }
}
+
diff --git a/Sources/Extensions/UIEdgeInsets+Extensions.swift b/Sources/Extensions/UIEdgeInsets+Extensions.swift
index a3fc6153..bd938be8 100644
--- a/Sources/Extensions/UIEdgeInsets+Extensions.swift
+++ b/Sources/Extensions/UIEdgeInsets+Extensions.swift
@@ -24,8 +24,6 @@
import Foundation
-// swiftlint:disable explicit_acl
-
internal extension UIEdgeInsets {
var vertical: CGFloat {
diff --git a/Sources/Extensions/UIImage+Extension.swift b/Sources/Extensions/UIImage+Extension.swift
index 157fd0f4..5195a1e7 100644
--- a/Sources/Extensions/UIImage+Extension.swift
+++ b/Sources/Extensions/UIImage+Extension.swift
@@ -33,7 +33,7 @@ import UIKit
/// This extension provide a way to access image resources with in framework
public extension UIImage {
- public class func messageKitImageWith(type: ImageType) -> UIImage? {
+ class func messageKitImageWith(type: ImageType) -> UIImage? {
let assetBundle = Bundle.messageKitAssetBundle()
let imagePath = assetBundle.path(forResource: type.rawValue, ofType: "png", inDirectory: "Images")
let image = UIImage(contentsOfFile: imagePath ?? "")
diff --git a/Sources/Extensions/UIView+Extensions.swift b/Sources/Extensions/UIView+Extensions.swift
index ec01877f..42bbbbd9 100644
--- a/Sources/Extensions/UIView+Extensions.swift
+++ b/Sources/Extensions/UIView+Extensions.swift
@@ -24,8 +24,6 @@
import UIKit
-// swiftlint:disable explicit_acl
-
internal extension UIView {
func fillSuperview() {
@@ -67,7 +65,7 @@ internal extension UIView {
}
@discardableResult
- internal func addConstraints(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, centerY: NSLayoutYAxisAnchor? = nil, centerX: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, centerYConstant: CGFloat = 0, centerXConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] {
+ func addConstraints(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, centerY: NSLayoutYAxisAnchor? = nil, centerX: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, centerYConstant: CGFloat = 0, centerXConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] {
if self.superview == nil {
return []
diff --git a/Sources/Layout/MessageSizeCalculator.swift b/Sources/Layout/MessageSizeCalculator.swift
index 4cca7e1c..2b95457f 100644
--- a/Sources/Layout/MessageSizeCalculator.swift
+++ b/Sources/Layout/MessageSizeCalculator.swift
@@ -78,6 +78,7 @@ open class MessageSizeCalculator: CellSizeCalculator {
attributes.messageContainerPadding = messageContainerPadding(for: message)
attributes.messageContainerSize = messageContainerSize(for: message)
attributes.cellTopLabelSize = cellTopLabelSize(for: message, at: indexPath)
+ attributes.cellTopLabelAlignment = cellTopLabelAlignment(for: message)
attributes.cellBottomLabelSize = cellBottomLabelSize(for: message, at: indexPath)
attributes.cellBottomLabelAlignment = cellBottomLabelAlignment(for: message)
attributes.messageTopLabelSize = messageTopLabelSize(for: message, at: indexPath)
diff --git a/Sources/Layout/MessagesCollectionViewFlowLayout.swift b/Sources/Layout/MessagesCollectionViewFlowLayout.swift
index b5e76738..95ac736d 100644
--- a/Sources/Layout/MessagesCollectionViewFlowLayout.swift
+++ b/Sources/Layout/MessagesCollectionViewFlowLayout.swift
@@ -89,7 +89,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
}
private func setupObserver() {
- NotificationCenter.default.addObserver(self, selector: #selector(MessagesCollectionViewFlowLayout.handleOrientationChange(_:)), name: UIDevice.orientationDidChangeNotification, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(MessagesCollectionViewFlowLayout.handleOrientationChange(_:)), name: UIApplication.didChangeStatusBarOrientationNotification, object: nil)
}
// MARK: - Typing Indicator API
diff --git a/Sources/Models/AvatarPosition.swift b/Sources/Models/AvatarPosition.swift
index 60219d1d..c62c99fe 100644
--- a/Sources/Models/AvatarPosition.swift
+++ b/Sources/Models/AvatarPosition.swift
@@ -94,5 +94,5 @@ public extension AvatarPosition {
static func == (lhs: AvatarPosition, rhs: AvatarPosition) -> Bool {
return lhs.vertical == rhs.vertical && lhs.horizontal == rhs.horizontal
}
-
+
}
diff --git a/Sources/Models/DetectorType.swift b/Sources/Models/DetectorType.swift
index d2b602a8..1b711a77 100644
--- a/Sources/Models/DetectorType.swift
+++ b/Sources/Models/DetectorType.swift
@@ -58,8 +58,8 @@ public enum DetectorType: Hashable {
}
///The hashValue of the `DetectorType` so we can conform to `Hashable` and be sorted.
- public func hash(into: inout Hasher) {
- into.combine(toInt())
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(toInt())
}
/// Return an 'Int' value for each `DetectorType` type so `DetectorType` can conform to `Hashable`
diff --git a/Sources/Models/HorizontalEdgeInsets.swift b/Sources/Models/HorizontalEdgeInsets.swift
index a1bf6740..91461fa9 100644
--- a/Sources/Models/HorizontalEdgeInsets.swift
+++ b/Sources/Models/HorizontalEdgeInsets.swift
@@ -40,6 +40,8 @@ public struct HorizontalEdgeInsets: Equatable {
}
}
+// MARK: Equatable Conformance
+
public extension HorizontalEdgeInsets {
static func == (lhs: HorizontalEdgeInsets, rhs: HorizontalEdgeInsets) -> Bool {
@@ -49,7 +51,7 @@ public extension HorizontalEdgeInsets {
internal extension HorizontalEdgeInsets {
- internal var horizontal: CGFloat {
+ var horizontal: CGFloat {
return left + right
}
}
diff --git a/Sources/Models/Sender.swift b/Sources/Models/Sender.swift
index 28468785..c44b8be1 100644
--- a/Sources/Models/Sender.swift
+++ b/Sources/Models/Sender.swift
@@ -25,17 +25,17 @@
import Foundation
/// An object that groups the metadata of a messages sender.
-@available(*, deprecated: 3.0.0, message: "`Sender` has been replaced with the `SenderType` protocol in 3.0.0")
+@available(*, deprecated, message: "`Sender` has been replaced with the `SenderType` protocol in 3.0.0")
public struct Sender: SenderType {
- /// MARK: - Properties
+ // MARK: - Properties
/// The unique String identifier for the sender.
///
/// Note: This value must be unique across all senders.
public let senderId: String
- @available(*, deprecated: 3.0.0, message: "`id` has been renamed `senderId` as defined in the `SenderType` protocol")
+ @available(*, deprecated, renamed: "senderId", message: "`id` has been renamed `senderId` as defined in the `SenderType` protocol")
public var id: String {
return senderId
}
@@ -50,7 +50,7 @@ public struct Sender: SenderType {
self.displayName = displayName
}
- @available(*, deprecated: 3.0.0, message: "`id` has been renamed `senderId` as defined in the `SenderType` protocol")
+ @available(*, deprecated, message: "`id` has been renamed `senderId` as defined in the `SenderType` protocol")
public init(id: String, displayName: String) {
self.init(senderId: id, displayName: displayName)
}
diff --git a/Sources/Protocols/MessageCellDelegate.swift b/Sources/Protocols/MessageCellDelegate.swift
index dd07f5d6..db2d7118 100644
--- a/Sources/Protocols/MessageCellDelegate.swift
+++ b/Sources/Protocols/MessageCellDelegate.swift
@@ -109,6 +109,16 @@ public protocol MessageCellDelegate: MessageLabelDelegate {
/// method `messageForItem(at:indexPath:messagesCollectionView)`.
func didTapAccessoryView(in cell: MessageCollectionViewCell)
+ /// Triggered when a tap occurs on the image.
+ ///
+ /// - Parameters:
+ /// - cell: The image where 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`
+ /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+ func didTapImage(in cell: MessageCollectionViewCell)
+
/// Triggered when a tap occurs on the play button from audio cell.
///
/// - Parameters:
@@ -164,6 +174,8 @@ public extension MessageCellDelegate {
func didTapCellBottomLabel(in cell: MessageCollectionViewCell) {}
func didTapMessageTopLabel(in cell: MessageCollectionViewCell) {}
+
+ func didTapImage(in cell: MessageCollectionViewCell) {}
func didTapPlayButton(in cell: AudioMessageCell) {}
diff --git a/Sources/Protocols/MessagesDisplayDelegate.swift b/Sources/Protocols/MessagesDisplayDelegate.swift
index 29d43e80..70b72fc5 100644
--- a/Sources/Protocols/MessagesDisplayDelegate.swift
+++ b/Sources/Protocols/MessagesDisplayDelegate.swift
@@ -229,7 +229,7 @@ public extension MessagesDisplayDelegate {
case .emoji:
return .clear
default:
- guard let dataSource = messagesCollectionView.messagesDataSource else { return .white }
+ guard let dataSource = messagesCollectionView.messagesDataSource else { return .backgroundColor }
return dataSource.isFromCurrentSender(message: message) ? .outgoingGreen : .incomingGray
}
}
@@ -254,7 +254,7 @@ public extension MessagesDisplayDelegate {
guard let dataSource = messagesCollectionView.messagesDataSource else {
fatalError(MessageKitError.nilMessagesDataSource)
}
- return dataSource.isFromCurrentSender(message: message) ? .white : .darkText
+ return dataSource.isFromCurrentSender(message: message) ? .backgroundColor : .labelColor
}
func enabledDetectors(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> [DetectorType] {
diff --git a/Sources/Supporting/MessageInputBar.swift b/Sources/Supporting/MessageInputBar.swift
index 575edd50..7c6f7dbf 100644
--- a/Sources/Supporting/MessageInputBar.swift
+++ b/Sources/Supporting/MessageInputBar.swift
@@ -25,31 +25,8 @@
import UIKit
import InputBarAccessoryView
-@available(*, obsoleted: 3.0.0, renamed: "InputBarAccessoryView")
+@available(*, unavailable, renamed: "InputBarAccessoryView")
public typealias MessageInputBar = InputBarAccessoryView
-@available(*, obsoleted: 3.0.0, renamed: "InputBarAccessoryViewDelegate")
+@available(*, unavailable, renamed: "InputBarAccessoryViewDelegate")
public typealias MessageInputBarDelegate = InputBarAccessoryViewDelegate
-
-//public extension MessageInputBarDelegate {
-//
-// @available(*, obsoleted: 3.0.0, message: "`MessageInputBar` has been replaced with `InputBarAccessoryView` in 3.0.0. Use `inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String)` instead.")
-// func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) {
-// }
-//
-// @available(*, obsoleted: 3.0.0, message: "`MessageInputBar` has been replaced with `InputBarAccessoryView` in 3.0.0. Use `inputBar(_ inputBar: InputBarAccessoryView, textViewTextDidChangeTo text: String)` instead.")
-// func messageInputBar(_ inputBar: MessageInputBar, textViewTextDidChangeTo text: String) {
-// }
-//
-// @available(*, obsoleted: 3.0.0, message: "`MessageInputBar` has been replaced with `InputBarAccessoryView` in 3.0.0. Use `inputBar(_ inputBar: InputBarAccessoryView, didChangeIntrinsicContentTo size: CGSize)` instead.")
-// func messageInputBar(_ inputBar: MessageInputBar, didChangeIntrinsicContentTo size: CGSize) {
-// }
-//}
-//
-//extension InputBarButtonItem {
-//
-// @available(*, renamed: "inputBarAccessoryView")
-// public var messageInputBar: MessageInputBar? {
-// return inputBarAccessoryView
-// }
-//}
diff --git a/Sources/Views/AvatarView.swift b/Sources/Views/AvatarView.swift
index 6563143e..31a7d363 100644
--- a/Sources/Views/AvatarView.swift
+++ b/Sources/Views/AvatarView.swift
@@ -161,7 +161,7 @@ open class AvatarView: UIImageView {
// MARK: - Internal methods
internal func prepareView() {
- backgroundColor = .gray
+ backgroundColor = .grayColor
contentMode = .scaleAspectFill
layer.masksToBounds = true
clipsToBounds = true
diff --git a/Sources/Views/Cells/AudioMessageCell.swift b/Sources/Views/Cells/AudioMessageCell.swift
index a9251ce5..b08d7ad5 100644
--- a/Sources/Views/Cells/AudioMessageCell.swift
+++ b/Sources/Views/Cells/AudioMessageCell.swift
@@ -47,18 +47,26 @@ open class AudioMessageCell: MessageContentCell {
return durationLabel
}()
+ public lazy var activityIndicatorView: UIActivityIndicatorView = {
+ let activityIndicatorView = UIActivityIndicatorView(style: .gray)
+ activityIndicatorView.hidesWhenStopped = true
+ activityIndicatorView.isHidden = true
+ return activityIndicatorView
+ }()
+
public lazy var progressView: UIProgressView = {
let progressView = UIProgressView(progressViewStyle: .default)
progressView.progress = 0.0
return progressView
}()
-
+
// MARK: - Methods
/// Responsible for setting up the constraints of the cell's subviews.
open func setupConstraints() {
playButton.constraint(equalTo: CGSize(width: 25, height: 25))
playButton.addConstraints(left: messageContainerView.leftAnchor, centerY: messageContainerView.centerYAnchor, leftConstant: 5)
+ activityIndicatorView.addConstraints(centerY: playButton.centerYAnchor, centerX: playButton.centerXAnchor)
durationLabel.addConstraints(right: messageContainerView.rightAnchor, centerY: messageContainerView.centerYAnchor, rightConstant: 15)
progressView.addConstraints(left: playButton.rightAnchor, right: durationLabel.leftAnchor, centerY: messageContainerView.centerYAnchor, leftConstant: 5, rightConstant: 5)
}
@@ -66,6 +74,7 @@ open class AudioMessageCell: MessageContentCell {
open override func setupSubviews() {
super.setupSubviews()
messageContainerView.addSubview(playButton)
+ messageContainerView.addSubview(activityIndicatorView)
messageContainerView.addSubview(durationLabel)
messageContainerView.addSubview(progressView)
setupConstraints()
@@ -75,6 +84,8 @@ open class AudioMessageCell: MessageContentCell {
super.prepareForReuse()
progressView.progress = 0
playButton.isSelected = false
+ activityIndicatorView.stopAnimating()
+ playButton.isHidden = false
durationLabel.text = "0:00"
}
diff --git a/Sources/Views/Cells/ContactMessageCell.swift b/Sources/Views/Cells/ContactMessageCell.swift
index d7f50087..2273a834 100644
--- a/Sources/Views/Cells/ContactMessageCell.swift
+++ b/Sources/Views/Cells/ContactMessageCell.swift
@@ -34,7 +34,7 @@ open class ContactMessageCell: MessageContentCell {
/// The view container that holds contact initials
public lazy var initialsContainerView: UIView = {
let initialsContainer = UIView(frame: CGRect.zero)
- initialsContainer.backgroundColor = .white
+ initialsContainer.backgroundColor = .backgroundColor
return initialsContainer
}()
@@ -42,7 +42,7 @@ open class ContactMessageCell: MessageContentCell {
public lazy var initialsLabel: UILabel = {
let initialsLabel = UILabel(frame: CGRect.zero)
initialsLabel.textAlignment = .center
- initialsLabel.textColor = .darkText
+ initialsLabel.textColor = .labelColor
initialsLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
return initialsLabel
}()
diff --git a/Sources/Views/Cells/MediaMessageCell.swift b/Sources/Views/Cells/MediaMessageCell.swift
index d7e1967b..6363e989 100644
--- a/Sources/Views/Cells/MediaMessageCell.swift
+++ b/Sources/Views/Cells/MediaMessageCell.swift
@@ -81,4 +81,17 @@ open class MediaMessageCell: MessageContentCell {
displayDelegate.configureMediaMessageImageView(imageView, for: message, at: indexPath, in: messagesCollectionView)
}
+
+ /// Handle tap gesture on contentView and its subviews.
+ open override func handleTapGesture(_ gesture: UIGestureRecognizer) {
+ let touchLocation = gesture.location(in: self)
+
+ switch true {
+ case imageView.frame.contains(touchLocation):
+ delegate?.didTapImage(in: self)
+ default:
+ break
+ }
+ }
+
}
diff --git a/Sources/Views/Cells/MessageContentCell.swift b/Sources/Views/Cells/MessageContentCell.swift
index 9fdfefeb..629cc272 100644
--- a/Sources/Views/Cells/MessageContentCell.swift
+++ b/Sources/Views/Cells/MessageContentCell.swift
@@ -267,6 +267,9 @@ open class MessageContentCell: MessageCollectionViewCell {
/// Positions the cell's top label.
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
open func layoutCellTopLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
+ cellTopLabel.textAlignment = attributes.cellTopLabelAlignment.textAlignment
+ cellTopLabel.textInsets = attributes.cellTopLabelAlignment.textInsets
+
cellTopLabel.frame = CGRect(origin: .zero, size: attributes.cellTopLabelSize)
}
diff --git a/Sources/Views/MessageLabel.swift b/Sources/Views/MessageLabel.swift
index 3ea89490..8ae68e86 100644
--- a/Sources/Views/MessageLabel.swift
+++ b/Sources/Views/MessageLabel.swift
@@ -126,9 +126,9 @@ open class MessageLabel: UILabel {
public static var defaultAttributes: [NSAttributedString.Key: Any] = {
return [
- NSAttributedString.Key.foregroundColor: UIColor.darkText,
+ NSAttributedString.Key.foregroundColor: UIColor.darkTextColor,
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
- NSAttributedString.Key.underlineColor: UIColor.darkText
+ NSAttributedString.Key.underlineColor: UIColor.darkTextColor
]
}()
@@ -273,6 +273,11 @@ open class MessageLabel: UILabel {
guard let rangeTuples = rangesForDetectors[detector] else { continue }
for (range, _) in rangeTuples {
+ // This will enable us to attribute it with our own styles, since `UILabel` does not provide link attribute overrides like `UITextView` does
+ if detector.textCheckingType == .link {
+ mutableAttributedString.removeAttribute(NSAttributedString.Key.link, range: range)
+ }
+
let attributes = detectorAttributes(for: detector)
mutableAttributedString.addAttributes(attributes, range: range)
}
diff --git a/Sources/Views/MessagesCollectionView.swift b/Sources/Views/MessagesCollectionView.swift
index 22e75474..05515be2 100644
--- a/Sources/Views/MessagesCollectionView.swift
+++ b/Sources/Views/MessagesCollectionView.swift
@@ -57,7 +57,7 @@ open class MessagesCollectionView: UICollectionView {
public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
- backgroundColor = .white
+ backgroundColor = .backgroundColor
registerReusableViews()
setupGestureRecognizers()
}
@@ -100,10 +100,25 @@ open class MessagesCollectionView: UICollectionView {
cell?.handleTapGesture(gesture)
}
+ // NOTE: It's possible for small content size this wouldn't work - https://github.com/MessageKit/MessageKit/issues/725
+ public func scrollToLastItem(at pos: UICollectionView.ScrollPosition = .bottom, animated: Bool = true) {
+ guard numberOfSections > 0 else { return }
+
+ let lastSection = numberOfSections - 1
+ let lastItemIndex = numberOfItems(inSection: lastSection) - 1
+
+ guard lastItemIndex >= 0 else { return }
+
+ let indexPath = IndexPath(row: lastItemIndex, section: lastSection)
+ scrollToItem(at: indexPath, at: pos, animated: animated)
+ }
+
+ // NOTE: This method seems to cause crash in certain cases - https://github.com/MessageKit/MessageKit/issues/725
+ // Could try using `scrollToLastItem` above
public func scrollToBottom(animated: Bool = false) {
- let collectionViewContentHeight = collectionViewLayout.collectionViewContentSize.height
-
- performBatchUpdates(nil) { _ in
+ performBatchUpdates(nil) { [weak self] _ in
+ guard let self = self else { return }
+ let collectionViewContentHeight = self.collectionViewLayout.collectionViewContentSize.height
self.scrollRectToVisible(CGRect(0.0, collectionViewContentHeight - 1.0, 1.0, 1.0), animated: animated)
}
}
diff --git a/Sources/Views/TypingIndicator.swift b/Sources/Views/TypingIndicator.swift
index 60872b37..ff007c3d 100644
--- a/Sources/Views/TypingIndicator.swift
+++ b/Sources/Views/TypingIndicator.swift
@@ -33,7 +33,7 @@ open class TypingIndicator: UIView {
public var bounceOffset: CGFloat = 2.5
/// A convenience accessor for the `backgroundColor` of each dot
- open var dotColor: UIColor = UIColor.lightGray {
+ open var dotColor: UIColor = UIColor.lightGrayColor {
didSet {
dots.forEach { $0.backgroundColor = dotColor }
}
diff --git a/Tests/ControllersTest/MessagesViewControllerSpec.swift b/Tests/ControllersTest/MessagesViewControllerSpec.swift
index 77c4de4c..5717fb32 100644
--- a/Tests/ControllersTest/MessagesViewControllerSpec.swift
+++ b/Tests/ControllersTest/MessagesViewControllerSpec.swift
@@ -70,7 +70,7 @@ final class MessagesViewControllerSpec: QuickSpec {
expect(controller.extendedLayoutIncludesOpaqueBars).to(beTrue())
}
it("sets the background color to be white") {
- expect(controller.view.backgroundColor).to(be(UIColor.white))
+ expect(controller.view.backgroundColor).to(be(UIColor.backgroundColor))
}
it("sets keyboardDismissMode to .interactive") {
let dismissMode = controller.messagesCollectionView.keyboardDismissMode
diff --git a/Tests/ControllersTest/MessagesViewControllerTests.swift b/Tests/ControllersTest/MessagesViewControllerTests.swift
index 640bbacd..c5f9f67e 100644
--- a/Tests/ControllersTest/MessagesViewControllerTests.swift
+++ b/Tests/ControllersTest/MessagesViewControllerTests.swift
@@ -105,7 +105,7 @@ class MessagesViewControllerTests: XCTestCase {
func testCellForItemWithAttributedTextData_returnsTextMessageCell() {
let messagesDataSource = MockMessagesDataSource()
sut.messagesCollectionView.messagesDataSource = messagesDataSource
- let attributes = [NSAttributedString.Key.foregroundColor: UIColor.black]
+ let attributes = [NSAttributedString.Key.foregroundColor: UIColor.labelColor]
let attriutedString = NSAttributedString(string: "Test", attributes: attributes)
messagesDataSource.messages.append(MockMessage(attributedText: attriutedString,
user: messagesDataSource.senders[0],
diff --git a/Tests/ProtocolsTests/MessagesDisplayDelegateTests.swift b/Tests/ProtocolsTests/MessagesDisplayDelegateTests.swift
index 6cbfb632..717d154e 100644
--- a/Tests/ProtocolsTests/MessagesDisplayDelegateTests.swift
+++ b/Tests/ProtocolsTests/MessagesDisplayDelegateTests.swift
@@ -71,7 +71,7 @@ class MessagesDisplayDelegateTests: XCTestCase {
at: IndexPath(item: 0, section: 0),
in: sut.messagesCollectionView)
- XCTAssertEqual(backgroundColor, .white)
+ XCTAssertEqual(backgroundColor, .backgroundColor)
}
func testBackgroundColorForMessageWithEmoji_returnsClearForDefault() {
@@ -143,7 +143,7 @@ class TextMessageDisplayDelegateTests: XCTestCase {
at: IndexPath(item: 0, section: 0),
in: sut.messagesCollectionView)
- XCTAssertEqual(textColor, .white)
+ XCTAssertEqual(textColor, .backgroundColor)
}
func testTextColorFromYou_returnsDarkTextForDefault() {
@@ -151,7 +151,7 @@ class TextMessageDisplayDelegateTests: XCTestCase {
at: IndexPath(item: 0, section: 0),
in: sut.messagesCollectionView)
- XCTAssertEqual(textColor, .darkText)
+ XCTAssertEqual(textColor, .labelColor)
}
func testTextColorWithoutDataSource_returnsDarkTextForDefault() {
@@ -161,7 +161,7 @@ class TextMessageDisplayDelegateTests: XCTestCase {
at: IndexPath(item: 0, section: 0),
in: sut.messagesCollectionView)
- XCTAssertEqual(textColor, .darkText)
+ XCTAssertEqual(textColor, .labelColor)
}
func testEnableDetectors_returnsEmptyForDefault() {
diff --git a/Tests/ViewsTests/AvatarViewTests.swift b/Tests/ViewsTests/AvatarViewTests.swift
index e919f141..602632c7 100644
--- a/Tests/ViewsTests/AvatarViewTests.swift
+++ b/Tests/ViewsTests/AvatarViewTests.swift
@@ -42,7 +42,9 @@ class AvatarViewTests: XCTestCase {
func testNoParams() {
XCTAssertEqual(avatarView.layer.cornerRadius, 15.0)
- XCTAssertEqual(avatarView.backgroundColor, UIColor.gray)
+ // For certain dynamic colors, need to compare cgColor in XCTest
+ // https://stackoverflow.com/questions/58065340/how-to-compare-two-uidynamicprovidercolor
+ XCTAssertEqual(avatarView.backgroundColor!.cgColor, UIColor.grayColor.cgColor)
}
func testWithImage() {
@@ -50,7 +52,7 @@ class AvatarViewTests: XCTestCase {
avatarView.set(avatar: avatar)
XCTAssertEqual(avatar.initials, "?")
XCTAssertEqual(avatarView.layer.cornerRadius, 15.0)
- XCTAssertEqual(avatarView.backgroundColor, UIColor.gray)
+ XCTAssertEqual(avatarView.backgroundColor!.cgColor, UIColor.grayColor.cgColor)
}
func testInitialsOnly() {
@@ -59,13 +61,13 @@ class AvatarViewTests: XCTestCase {
XCTAssertEqual(avatarView.initials, avatar.initials)
XCTAssertEqual(avatar.initials, "DL")
XCTAssertEqual(avatarView.layer.cornerRadius, 15.0)
- XCTAssertEqual(avatarView.backgroundColor, UIColor.gray)
+ XCTAssertEqual(avatarView.backgroundColor!.cgColor, UIColor.grayColor.cgColor)
}
func testSetBackground() {
- XCTAssertEqual(avatarView.backgroundColor, UIColor.gray)
+ XCTAssertEqual(avatarView.backgroundColor!.cgColor, UIColor.grayColor.cgColor)
avatarView.backgroundColor = UIColor.red
- XCTAssertEqual(avatarView.backgroundColor, UIColor.red)
+ XCTAssertEqual(avatarView.backgroundColor!, UIColor.red)
}
func testGetImage() {
diff --git a/Tests/ViewsTests/MessagesCollectionViewTests.swift b/Tests/ViewsTests/MessagesCollectionViewTests.swift
index a42fa54d..31d515f0 100644
--- a/Tests/ViewsTests/MessagesCollectionViewTests.swift
+++ b/Tests/ViewsTests/MessagesCollectionViewTests.swift
@@ -44,7 +44,7 @@ class MessagesCollectionViewTests: XCTestCase {
func testInit() {
XCTAssertEqual(messagesCollectionView.frame, rect)
XCTAssertEqual(messagesCollectionView.collectionViewLayout, layout)
- XCTAssertEqual(messagesCollectionView.backgroundColor, .white)
+ XCTAssertEqual(messagesCollectionView.backgroundColor, .backgroundColor)
}
}