Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,24 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa

### Changed

- BREAKING CHANGE: [cd4f75b](https://github.com/MessageKit/MessageKit/commit/cd4f75b561129fc25e6c4576000e5a92ccd81cad) by [@martinpucik](https://github.com/martinpucik)
- **Breaking change**: Dropped support for iOS 12 [2bd234b](https://github.com/MessageKit/MessageKit/commit/2bd234b1e878f392089f166d6868ce644d6c9e95) by [@martinpucik](https://github.com/martinpucik)
- **Breaking change**: Moved messageInputBar from inputAccessoryView to a subview in MessagesViewController [#1704](https://github.com/MessageKit/MessageKit/pull/1704) by [@martinpucik](https://github.com/martinpucik)
- **Breaking change**: Added an argument to `messageContainerMaxWidth` [cd4f75b](https://github.com/MessageKit/MessageKit/commit/cd4f75b561129fc25e6c4576000e5a92ccd81cad) by [@martinpucik](https://github.com/martinpucik)
```swift
MessageSizeCalculator.messageContainerMaxWidth(for message: MessageType) -> CGFloat
```
now has IndexPath argument
```swift
MessageSizeCalculator.messageContainerMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat
```
- BREAKING CHANGE: [cd4f75b](https://github.com/MessageKit/MessageKit/commit/cd4f75b561129fc25e6c4576000e5a92ccd81cad) by [@martinpucik](https://github.com/martinpucik)
- **Breaking change**: Added an argument to `messageContainerSize` [cd4f75b](https://github.com/MessageKit/MessageKit/commit/cd4f75b561129fc25e6c4576000e5a92ccd81cad) by [@martinpucik](https://github.com/martinpucik)
```swift
MessageSizeCalculator.messageContainerSize(for message: MessageType) -> CGSize
```
now has IndexPath argument
```swift
MessageSizeCalculator.messageContainerSize(for message: MessageType, at indexPath: IndexPath) -> CGSize
```
- BREAKING CHANGE: Dropped support for iOS 12 [2bd234b](https://github.com/MessageKit/MessageKit/commit/2bd234b1e878f392089f166d6868ce644d6c9e95) by [@martinpucik](https://github.com/martinpucik)

### Removed

Expand Down
6 changes: 4 additions & 2 deletions Example/Sources/View Controllers/ChatViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ class ChatViewController: MessagesViewController, MessagesDataSource {

override func viewDidLoad() {
super.viewDidLoad()

navigationItem.largeTitleDisplayMode = .never
navigationItem.title = "MessageKit"

configureMessageCollectionView()
configureMessageInputBar()
loadFirstMessages()
title = "MessageKit"

}

override func viewDidAppear(_ animated: Bool) {
Expand Down
5 changes: 2 additions & 3 deletions Example/Sources/View Controllers/LaunchViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ final internal class LaunchViewController: UITableViewController {
title = "MessageKit"
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.tintColor = .primaryColor
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.tableFooterView = UIView()
}
Expand Down Expand Up @@ -125,9 +126,7 @@ final internal class LaunchViewController: UITableViewController {
case .customLayout:
navigationController?.pushViewController(CustomLayoutExampleViewController(), animated: true)
case .swiftUI:
if #available(iOS 13, *) {
navigationController?.pushViewController(UIHostingController(rootView: SwiftUIExampleView()), animated: true)
}
navigationController?.pushViewController(UIHostingController(rootView: SwiftUIExampleView()), animated: true)
case .subview:
let viewController = MessageSubviewContainerViewController()
let detailViewController = UINavigationController(rootViewController: viewController)
Expand Down
8 changes: 1 addition & 7 deletions Example/Sources/Views/CameraInputBarAccessoryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,7 @@ extension CameraInputBarAccessoryViewDelegate {
return InputBarButtonItem()
.configure {
$0.spacing = .fixed(10)

if #available(iOS 13.0, *) {
$0.image = UIImage(systemName: "camera.fill")?.withRenderingMode(.alwaysTemplate)
} else {
$0.image = UIImage(named: named)?.withRenderingMode(.alwaysTemplate)
}

$0.image = UIImage(systemName: "camera.fill")?.withRenderingMode(.alwaysTemplate)
$0.setSize(CGSize(width: 30, height: 30), animated: false)
}.onSelected {
$0.tintColor = .systemBlue
Expand Down
4 changes: 0 additions & 4 deletions Example/Sources/Views/SwiftUI/MessagesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ final class MessageSwiftUIVC: MessagesViewController {
}
}

@available(iOS 13.0, *)
struct MessagesView: UIViewControllerRepresentable {

@State var initialized = false
Expand Down Expand Up @@ -89,7 +88,6 @@ struct MessagesView: UIViewControllerRepresentable {

}

@available(iOS 13.0, *)
extension MessagesView.Coordinator: MessagesDataSource {
func currentSender() -> SenderType {
return SampleData.shared.currentSender
Expand Down Expand Up @@ -122,7 +120,6 @@ extension MessagesView.Coordinator: MessagesDataSource {
}
}

@available(iOS 13.0, *)
extension MessagesView.Coordinator: InputBarAccessoryViewDelegate {
func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
let message = MockMessage(text: text, user: SampleData.shared.currentSender, messageId: UUID().uuidString, date: Date())
Expand All @@ -131,7 +128,6 @@ extension MessagesView.Coordinator: InputBarAccessoryViewDelegate {
}
}

@available(iOS 13.0, *)
extension MessagesView.Coordinator: MessagesLayoutDelegate, MessagesDisplayDelegate {
func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) {
let avatar = SampleData.shared.getAvatarFor(sender: message.sender)
Expand Down
2 changes: 0 additions & 2 deletions Example/Sources/Views/SwiftUI/SwiftUIExampleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import SwiftUI
import MessageKit

@available(iOS 13.0, *)
struct SwiftUIExampleView: View {

@State var messages: [MessageType] = SampleData.shared.getMessages(count: 20)
Expand All @@ -35,7 +34,6 @@ struct SwiftUIExampleView: View {

}

@available(iOS 13.0, *)
struct SwiftUIExampleView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIExampleView()
Expand Down
10 changes: 0 additions & 10 deletions MessageKit.xcworkspace/contents.xcworkspacedata

This file was deleted.

8 changes: 0 additions & 8 deletions MessageKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

This file was deleted.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ let package = Package(
.library(name: "MessageKit", targets: ["MessageKit"]),
],
dependencies: [
.package(url: "https://github.com/nathantannar4/InputBarAccessoryView", .upToNextMajor(from: "5.5.0"))
.package(url: "https://github.com/nathantannar4/InputBarAccessoryView", .upToNextMajor(from: "6.0.0"))
],
targets: [
.target(
Expand Down
140 changes: 55 additions & 85 deletions Sources/Controllers/MessagesViewController+Keyboard.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
MIT License

Copyright (c) 2017-2020 MessageKit
Copyright (c) 2017-2022 MessageKit

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -24,121 +24,91 @@

import Foundation
import UIKit
import Combine
import InputBarAccessoryView

internal extension MessagesViewController {

// MARK: - Register / Unregister Observers

// MARK: - Register Observers
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)
}

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)
}

// MARK: - Notification Handlers

@objc
private func handleTextViewDidBeginEditing(_ notification: Notification) {
if scrollsToLastItemOnKeyboardBeginsEditing || scrollsToLastItemOnKeyboardBeginsEditing {
guard
let inputTextView = notification.object as? InputTextView,
inputTextView === messageInputBar.inputTextView
else {
return
}
if scrollsToLastItemOnKeyboardBeginsEditing {
messagesCollectionView.scrollToLastItem()
} else {
messagesCollectionView.scrollToLastItem(animated: true)
keyboardManager.bind(inputAccessoryView: inputContainerView)
keyboardManager.bind(to: messagesCollectionView)

NotificationCenter.default
.publisher(for: UITextView.textDidBeginEditingNotification)
.subscribe(on: DispatchQueue.global())
.receive(on: DispatchQueue.main)
.sink { [weak self] notification in
self?.handleTextViewDidBeginEditing(notification)
}
}
.store(in: &disposeBag)

Publishers.MergeMany(
NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification),
NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification),
NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification),
NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)
)
.subscribe(on: DispatchQueue.global())
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] _ in
self?.updateMessageCollectionViewBottomInset()
})
.store(in: &disposeBag)
}

@objc
private func handleKeyboardDidChangeState(_ notification: Notification) {
guard !isMessagesControllerBeingDismissed else { return }

guard let keyboardStartFrameInScreenCoords = notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? CGRect else { return }
guard !keyboardStartFrameInScreenCoords.isEmpty || UIDevice.current.userInterfaceIdiom != .pad else {
// WORKAROUND for what seems to be a bug in iPad's keyboard handling in iOS 11: we receive an extra spurious frame change
// notification when undocking the keyboard, with a zero starting frame and an incorrect end frame. The workaround is to
// 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
// change notifications while the keyboard is hidden or undocked (undocked keyboard is considered hidden by those events).
// Unfortunately, we do care about the difference between hidden and undocked, because we have an input bar which is at the
// bottom when the keyboard is hidden, and is tied to the keyboard when it's undocked.
//
// If we follow what Apple recommends and ignore notifications while the keyboard is hidden/undocked, we get an extra inset
// at the bottom when the undocked keyboard is visible (the inset that tries to compensate for the missing input bar).
// (Alternatives like setting newBottomInset to 0 or to the height of the input bar don't work either.)
//
// 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)
// MARK: - Updating insets

let newBottomInset = requiredScrollViewBottomInset(forKeyboardFrame: keyboardEndFrame)
/// Updates bottom messagesCollectionView inset based on the position of inputContainerView
func updateMessageCollectionViewBottomInset() {
/// This is important to skip notifications from child modal controllers in iOS >= 13.0
guard self.presentedViewController == nil else { return }
let collectionViewHeight = messagesCollectionView.frame.height
let newBottomInset = collectionViewHeight - (inputContainerView.frame.minY - additionalBottomInset) - automaticallyAddedBottomInset
let differenceOfBottomInset = newBottomInset - messageCollectionViewBottomInset

UIView.performWithoutAnimation {
messageCollectionViewBottomInset = newBottomInset
guard differenceOfBottomInset != 0 else { return }
messagesCollectionView.contentInset.bottom = max(0, newBottomInset)
messagesCollectionView.verticalScrollIndicatorInsets.bottom = newBottomInset
}

if maintainPositionOnKeyboardFrameChanged && differenceOfBottomInset != 0 {
let contentOffset = CGPoint(x: messagesCollectionView.contentOffset.x, y: messagesCollectionView.contentOffset.y + differenceOfBottomInset)
// Changing contentOffset to bigger number than the contentSize will result in a jump of content
// https://github.com/MessageKit/MessageKit/issues/1486
guard contentOffset.y <= messagesCollectionView.contentSize.height else {
return
}
guard contentOffset.y <= messagesCollectionView.contentSize.height else { return }
messagesCollectionView.setContentOffset(contentOffset, animated: false)
}
}

// MARK: - Inset Computation

private func requiredScrollViewBottomInset(forKeyboardFrame keyboardFrame: CGRect) -> CGFloat {
// 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)
// MARK: - Private methods

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.
return max(0, additionalBottomInset - automaticallyAddedBottomInset)
private func handleTextViewDidBeginEditing(_ notification: Notification) {
guard scrollsToLastItemOnKeyboardBeginsEditing || scrollsToLastItemOnKeyboardBeginsEditing else { return }
guard
let inputTextView = notification.object as? InputTextView,
inputTextView === messageInputBar.inputTextView
else {
return
}
if scrollsToLastItemOnKeyboardBeginsEditing {
messagesCollectionView.scrollToLastItem()
} else {
return max(0, intersection.height + additionalBottomInset - automaticallyAddedBottomInset)
messagesCollectionView.scrollToLastItem(animated: true)
}
}

func requiredInitialScrollViewBottomInset() -> CGFloat {
let inputAccessoryViewHeight = inputAccessoryView?.frame.height ?? 0
return max(0, inputAccessoryViewHeight + additionalBottomInset - automaticallyAddedBottomInset)
}

/// UIScrollView can automatically add safe area insets to its contentInset,
/// which needs to be accounted for when setting the contentInset based on screen coordinates.
///
/// - Returns: The distance automatically added to contentInset.bottom, if any.
private var automaticallyAddedBottomInset: CGFloat {
return messagesCollectionView.adjustedContentInset.bottom - messagesCollectionView.contentInset.bottom
}

private var messageCollectionViewBottomInset: CGFloat {
return messagesCollectionView.contentInset.bottom
}
}
2 changes: 1 addition & 1 deletion Sources/Controllers/MessagesViewController+Menu.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
MIT License

Copyright (c) 2017-2019 MessageKit
Copyright (c) 2017-2022 MessageKit

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
Loading