I am building an app with SwiftUI and I wanted to use MessageKit, so I used a UIViewControllerRepresentable
to show a MessageKit view. However, when the keyboard is shown messages are able to scroll past the bottom of the view.
Here is a screenshot of the problem:
I can still scroll the messages back down, but trying to push the UICollectionView
to the bottom results in the same problem. This problem does not occur when the keyboard is hidden.
Here is a screenshot of the captured UI hierarchy with the navigation bar and root views hidden:
The collection view is still sized correctly but messages are able to scroll past the bottom and the scroll indicators are way up at the top.
Here is the root SwiftUI view with relevant code:
struct SingleChatView: SwiftUI.View {
@ObservedObject var chat: Chat
@State private var mkView: MessageKitView
init(chat: Chat) {
self.chat = chat
_mkView = State(initialValue: MessageKitView(chat: chat))
}
var body: some SwiftUI.View {
mkView
.navigationBarTitle(chat.name)
.uiKitOnAppear {
mkView.controller.becomeFirstResponder()
mkView.controller.messagesCollectionView.scrollToBottom(animated: true)
chat.clearNotifications()
// Set chat as active
ChatManager.shared.activeChat = chat
}
.onDisappear {
chat.setTypingStatus(to: false)
// Set chat as inactive
ChatManager.shared.activeChat = nil
}
}
}
// Different file
struct UIKitAppear: UIViewControllerRepresentable {
let action: () -> Void
func makeUIViewController(context: Context) -> UIAppearViewController {
let vc = UIAppearViewController()
vc.action = action
return vc
}
func updateUIViewController(_ controller: UIAppearViewController, context: Context) {
}
}
final class UIAppearViewController: UIViewController {
var action: () -> Void = {}
override func viewDidLoad() {
view.addSubview(UILabel())
}
override func viewDidAppear(_ animated: Bool) {
action()
}
}
public extension View {
func uiKitOnAppear(_ perform: @escaping () -> Void) -> some View {
self.background(UIKitAppear(action: perform))
}
}
Here is the root UIKit view controller:
struct MessageKitView: UIViewControllerRepresentable {
@ObservedObject var chat: Chat
@EnvironmentObject var userData: UserData
@State var controller = ChatViewController()
@State var refreshControl = UIRefreshControl()
init(chat: Chat) {
self.chat = chat
}
func makeUIViewController(context: Context) -> MessagesViewController {
return controller
}
func updateUIViewController(_ uiViewController: MessagesViewController, context: Context) {
uiViewController.messagesCollectionView.messagesDataSource = context.coordinator
uiViewController.messagesCollectionView.messagesLayoutDelegate = context.coordinator
uiViewController.messagesCollectionView.messagesDisplayDelegate = context.coordinator
uiViewController.messagesCollectionView.messageCellDelegate = context.coordinator
uiViewController.showMessageTimestampOnSwipeLeft = true
uiViewController.setTypingIndicatorViewHidden(chat.typers.isEmpty, animated: true)
// Refresh Control
refreshControl.addTarget(context.coordinator, action: #selector(context.coordinator.onRefresh), for: .valueChanged)
uiViewController.messagesCollectionView.refreshControl = refreshControl
uiViewController.messageInputBar.delegate = context.coordinator
uiViewController.messageInputBar.inputTextView.isImagePasteEnabled = false
//uiViewController.maintainPositionOnKeyboardFrameChanged = true
uiViewController.scrollsToBottomOnKeyboardBeginsEditing = true
//uiViewController.scrollsToLastItemOnKeyboardBeginsEditing = true
uiViewController.messagesCollectionView.reloadData()
//uiViewController.messagesCollectionView.scrollToLastItem(at: .bottom, animated: true)
//uiViewController.messagesCollectionView.scrollToBottom(animated: true)
// Removes avatar from outgoing messages
if let layout = uiViewController.messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout {
layout.textMessageSizeCalculator.outgoingAvatarSize = .zero
layout.textMessageSizeCalculator.outgoingMessageBottomLabelAlignment = LabelAlignment(textAlignment: .right, textInsets: .zero)
layout.emojiMessageSizeCalculator.outgoingAvatarSize = .zero
layout.emojiMessageSizeCalculator.outgoingMessageBottomLabelAlignment = LabelAlignment(textAlignment: .right, textInsets: .zero)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator {
let parent: MessageKitView
init(_ parent: MessageKitView) {
self.parent = parent
}
@objc func onRefresh() {
parent.chat.loadMessages(userData: parent.userData, withPromise: FailurePromise(success: {
self.parent.refreshControl.endRefreshing()
}, failure: { failure in
self.parent.refreshControl.endRefreshing()
print(failure)
}))
}
}
}
The commented out lines represent parameters I have tried to tweak. There are more extensions to Coordinator
but I will only include them upon request because I don't think they are relevant to my issue. What could I be doing wrong here and how should I try to fix it?