A few weeks ago I published the sixth major release of
my our messages UI library for iOS. This release closes the door on a major milestone for this project, so I wanted to take the time to highlight its significance, discuss its new features, and examine its design. Of course, this would not have been possible without our amazing open-source community and the contributors to this project.
JSQMessagesViewController: An elegant messages UI library for iOS
A brief history
It all began with Hemoglobe, an app for the bleeding disorder community. I built this app with Michael Schultz almost two years ago, and one of the main features was… private user messages. I searched on GitHub and CocoaControls for an existing open-source framework. What I found was a lot of great attempts and partially completed projects. However, one abandoned project stuck out and gave me some great ideas to get started.
CocoaPods was just entering its third year back then and was not very mainstream. I knew very little about it until an issue was opened that requested CocoaPod support and I received my first pull-request to add a podspec.
After the app launched, I extracted the messages UI into a separate component and published version 1.0 on GitHub. I figured this was the least I could do, given how much the abandoned projects had helped me even though they were only partially completed. I figured there were probably other developers out there searching for a “finished” messages UI framework I like was before.
The component was originally implemented with a
UITableView, until version 5.0 which saw a complete rewrite of the library that opted for the more flexible
UICollectionView instead. And here we are today, 49 releases later at version 6.1.0, with over 20 apps using this library and 3,000 stars on GitHub. I never thought that this library would get so much attention. It’s been such a pleasure developing this component out in the open and collaborating with each of the contributors.
The anatomy of a cell
The flagship feature of 6.0 is the most requested feature to date — media messages. In order to display anything other than text in the message bubbles, some major changes were required in the core library. With these new changes it is now possible to display any arbitrary
UIView in a message bubble. The messages view (picture above) is backed by subclasses of
UICollectionViewFlowLayout. Each message is represented as a cell in the collection view and has a number of customizable subviews and properties, which are outlined in the following diagram. The labels for the subviews in the diagram are the names of the actual subview properties for a cell. See the JSQMessagesCollectionViewCell documentation for further details.
Anatomy of a cell
There are two basic types of cells, incoming and outgoing. Each cell has three labels for message metadata — the sender, the date, and delivery status. Each of these labels is optional, and you can hide or show any combination of them. Next, there are two top-level container views, one for the message content and one for the avatar. The
messageBubbleContainerView holds the
messageBubbleImageView and either a
avatarContainerView holds the
avatarImageView. Avatars are also optional. Finally, there’s a customizable margin (
messageBubbleLeftRightMargin) on the side of the cell that is opposite the avatar. For outgoing messages, this is the left-side margin. For incoming messages, this is the right-side margin. You can modify this margin using the property on the flow layout object. Each of these subviews is customizable via the usual UIKit APIs. Now that we understand the basics about the views, it’s time to examine the model behind them.
The model: speaking to protocols
One of the most challenging aspects of developing a framework is making assumptions — you should always try to make as few as possible. This is much easier said than done, and I’ve made plenty of embarassing mistakes. Frameworks and libraries are constantly evolving as new use cases and edge cases emerge. It is vitally important to be modular, flexible, and extensible.
Designing frameworks is difficult. In fact, I believe that they are so difficult, that they can only be designed by building examples and generalizing the example code into the framework code.
So how do we implement the model in Model-View-Controller when we have no idea what the model will be? Every developer that wants to use this library will have a unique model. Class names will be different, variable names will be different, the persistence layer (Core Data or otherwise) will be different. We unify these differences with Protocols (or Interfaces). This is the L in SOLID, and it is one of the most powerful design tools that you have.
There are 4 data protocols for the model layer:
JSQMessageData: defines methods for providing message data (text or media) and metadata (sender, date, etc.)
JSQMessageMediaData: defines methods for representing media as a
JSQMessageAvatarImageDataSource: defines methods for providing avatar images
JSQMessageBubbleImageDataSource: defines methods for providing message bubble images
Together, these protocols specify the comprehensive interface through which the
JSQMessagesViewController framework communicates with your data. This means it does not matter how your data is structured or defined — you simply need to conform to the protocols by implementing their methods. This is very flexible and gives you the freedom to implement your model however you like.
Finally, I want to mention that the library does provide concrete model classes for those who want to use them. This allows you to get started using this library more quickly, and also serves as an example for how to implement these protocols in your custom classes should you choose to do so.
Media messages revealed
Earlier I mentioned that the new media message API allows you to display any arbitrary
UIView in a message bubble. This means that your media message can be anything and you only need to implement the
JSQMessageMediaData protocol that defines how to display your specific media. For example, media data could be a CLLocation, or a UIImage, or an ABRecordRef. The 6.0 release provides 3 concrete media types:
JSQVideoMediaItem. I think these are the most common kind of media that users want to send and that developers want to support. They should cover about 80 percent of use cases.
Provided media message types
The media message API was built with extensibility in mind. If the provided media items do not suit your needs, you can extend them to do so. And, with very little effort you can define your own custom media items.
An alternative approach to this API would have been to define the different message or media types as an Enum. And a few contributors have suggested or asked about this. Hopefully, you can see how limiting this would have been, especially regarding the library-provided media types. Using an enum would litter the code base with
switch statements — a code smell — and require special handling for each kind of media. The only option for extensibilty would have been providing a “custom” enum case, such as
JSQMediaTypeCustom, which would defer all the work to the consumer of the API. This special enum case further increases complexity. And more importantly, this solution cannot address the situation where a developer wants to add more than one custom media type. By using a protocol, we avoid these pitfalls and every single media type is handled in the exact same way by the library.
I think this library is in a very good state. It’s stable and extensbile, but there’s still plenty of work to do moving forward. There are plenty of feature requests and some bugs to address. And as mentioned above, frameworks are constantly evolving. I can’t wait to see what this framework looks like in another year. I would love to rewrite it in Swift eventually. We’ll see what happens.
Thanks for contributing
Needless to say, this project would not be where it is today without the enthusiasm and support of our awesome community. The initial release of this library was so limited, and I never expected that I would still be developing it years later. It is only because of the interest, kindness, and encouragement from the community that I’ve continued working on this. (Well, programming is also pretty fun for me too.)
Developing this library, and doing it openly, has taught me so much and given me so many opportunities. I’ve improved my programming skills, I’ve met dozens of great developers, I discovered CocoaPods, I’ve learned how to effectively manage a large project with dozens of individual contributors, and the list continues. And I continue to learn. Open-source has given me all of these amazing things. I could not be more grateful for that. And I’m very excited to continue supporting and maintaining this library.
Thank you for this opportunity to learn, share, and collaborate.