Merge pull request #1250 from MessageKit/development

Prepare release 3.1.0-beta.1, migrate to Swift 5
This commit is contained in:
Austin Wright
2020-02-25 14:06:29 -05:00
committed by GitHub
54 changed files with 415 additions and 170 deletions
+2 -2
View File
@@ -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:
+1 -1
View File
@@ -1 +1 @@
4.2
5.0
-1
View File
@@ -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
+35
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
github "nathantannar4/InputBarAccessoryView" "4.2.1"
github "nathantannar4/InputBarAccessoryView" ~> 4.3.0
+1 -1
View File
@@ -1,2 +1,2 @@
github "Quick/Quick" ~> 2.0.0
github "Quick/Nimble" ~> 8.0.1
github "Quick/Nimble" ~> 8.0.0
+3 -3
View File
@@ -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"
+82
View File
@@ -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)
}
}
```
Binary file not shown.
+13 -13
View File
@@ -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;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
+2
View File
@@ -1,3 +1,5 @@
source 'https://cdn.cocoapods.org/'
platform :ios, '9.0'
target 'ChatExample' do
+10 -8
View File
@@ -1,13 +1,15 @@
PODS:
- InputBarAccessoryView (4.2.2)
- MessageKit (3.0.0-beta-swift4-2):
- InputBarAccessoryView
- InputBarAccessoryView (4.3.1):
- InputBarAccessoryView/Core (= 4.3.1)
- InputBarAccessoryView/Core (4.3.1)
- MessageKit (3.1.0-beta.1):
- InputBarAccessoryView (~> 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
-16
View File
@@ -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
+4 -4
View File
@@ -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..<count])
@@ -195,6 +195,10 @@ extension ChatViewController: MessageCellDelegate {
print("Message tapped")
}
func didTapImage(in cell: MessageCollectionViewCell) {
print("Image tapped")
}
func didTapCellTopLabel(in cell: MessageCollectionViewCell) {
print("Top cell label tapped")
}
+4 -4
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'MessageKit'
s.version = '3.0.0'
s.version = '3.1.0-beta.1'
s.license = { :type => "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
+7 -7
View File
@@ -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;
};
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
+17 -11
View File
@@ -1,8 +1,7 @@
<p>
<img src="https://raw.githubusercontent.com/MessageKit/MessageKit/master/Assets/mklogo.png" title="MessageKit logo">
</p>
<p>
<img src="https://raw.githubusercontent.com/MessageKit/MessageKit/master/Assets/TypingIndicator.png" title="MessageKit header">
<p align="center">
<img src="https://raw.githubusercontent.com/MessageKit/MessageKit/master/Assets/mklogo.png" title="MessageKit logo" width="400">
A community-driven replacement for JSQMessagesViewController https://messagekit.github.io
</p>
[![CircleCI](https://circleci.com/gh/MessageKit/MessageKit.svg?style=svg)](https://circleci.com/gh/MessageKit/MessageKit)
@@ -29,6 +28,10 @@
alt="Contributions Welcome">
</a>
<p align="center">
<img src="https://raw.githubusercontent.com/MessageKit/MessageKit/master/Assets/TypingIndicator.png" title="MessageKit header" width="400">
</p>
## 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)
@@ -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,
@@ -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)
}
@@ -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() {
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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)
}
@@ -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)
+85 -6
View File
@@ -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
}
}
}
@@ -24,8 +24,6 @@
import Foundation
// swiftlint:disable explicit_acl
internal extension UIEdgeInsets {
var vertical: CGFloat {
+1 -1
View File
@@ -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 ?? "")
+1 -3
View File
@@ -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 []
@@ -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)
@@ -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
+1 -1
View File
@@ -94,5 +94,5 @@ public extension AvatarPosition {
static func == (lhs: AvatarPosition, rhs: AvatarPosition) -> Bool {
return lhs.vertical == rhs.vertical && lhs.horizontal == rhs.horizontal
}
}
+2 -2
View File
@@ -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`
+3 -1
View File
@@ -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
}
}
+4 -4
View File
@@ -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)
}
@@ -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) {}
@@ -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] {
+2 -25
View File
@@ -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
// }
//}
+1 -1
View File
@@ -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
+12 -1
View File
@@ -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"
}
+2 -2
View File
@@ -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
}()
@@ -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
}
}
}
@@ -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)
}
+7 -2
View File
@@ -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)
}
+19 -4
View File
@@ -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)
}
}
+1 -1
View File
@@ -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 }
}
@@ -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
@@ -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],
@@ -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() {
+7 -5
View File
@@ -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() {
@@ -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)
}
}