-
Notifications
You must be signed in to change notification settings - Fork 20
iOS Example Structure
ChatViewController is developed with main functions about: View Controller and ChatBarView. The core doesn't contain functions related about layout message. Because we understand that each chat application has different layout message. So that we didn't leave this part of the core.
With iOS Example project they want to bring you a specific chat application, which is based on ChatViewController. The layout of our message is based on Facebook Messenger.
Below we will explain in detail how we layout. It is an example for you to easily apply ChatViewController to your application.
MessageCell is designed by using StackView. We aim to hide the components as easily as possible. Why we need hide the components?
- We want to hide
Status StackView (Status Label)for Incoming message. - We want to hide
Avatar Container View (Avatar ImageView)for Outgoing message.
fileprivate func layoutForIncomingMessage() {
statusStackView.isHidden = true
statusSpaceView.isHidden = false
avatarContainerView.isHidden = false
}
fileprivate func layoutForOutgoingMessage() {
statusStackView.isHidden = true
statusSpaceView.isHidden = true
avatarContainerView.isHidden = true
}We don't want use separated UI for each Incoming and Outgoing message. So that we update tranform of each components inside message cell.
func tranformUI(_ isOutgoingMessage: Bool) {
if isOutgoingMessage {
layoutForOutgoingMessage()
contentTranform = CGAffineTransform(scaleX: -1, y: 1)
statusLabel.textAlignment = .right
} else {
layoutForIncomingMessage()
contentTranform = CGAffineTransform.identity
statusLabel.textAlignment = .left
}
contentView.transform = contentTranform
statusLabel.transform = contentTranform
avatarImageView.transform = contentTranform
}MessageCell is abstract class. With RoundedView is designed as Container View. It holds each UI we want to display. With MessageTextCell we add messageLabel into it, with MessageImageCell we add attachImageView to show image. And to show right direction for incoming and outgoing message you just have to override tranformUI function.
We use UIBezierPath to draw Rectangle with corner. Then mask RoundedView by a CAShapeLayer
fileprivate func roundViewWithStyle( _ style: RoundedViewType) {
layoutIfNeeded()
let bounds = roundedView.bounds
let roundRadius: (tl: CGFloat, tr: CGFloat, bl: CGFloat, br: CGFloat) = getRoundRadiusForStyle(style)
let path = UIBezierPath(roundedRect: bounds,
topLeftRadius: roundRadius.tl,
topRightRadius: roundRadius.tr,
bottomLeftRadius: roundRadius.bl,
bottomRightRadius: roundRadius.br)
path.lineJoinStyle = .round
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = path.cgPath
roundedView.layer.mask = maskLayer
}To display text message we added an UILabel messageLabel inside RoundedView
As you see: We only add leadingAnchor, topAnchor and bottomAnchor for OuterStackView. So that to limit width of content. Here is width of messageLabel you have to set widthAnchor for messageLabel
You can set widthAnchor constant by specific value or depend on screen size width.
To display Image message we added an UIImageView attachImageView inside RoundedView. We want to display the image with the aspect ratio that corresponds to the actual size of the image. So that we calculate image size we want to display then set widthAnchor and heightAnchor for attachImageView.
/// Display the image with the aspect ratio that corresponds to the actual size of the image.
fileprivate func updateImage(width: CGFloat, height: CGFloat) {
let proportionalSize: (width: CGFloat, height: CGFloat) = calculateProportionalSize(width: width, height: height)
widthAnchorImageView.constant = proportionalSize.width
heightAnchorImageView.constant = proportionalSize.height
}
/// Calculate proportional size with same aspect ratio
fileprivate func calculateProportionalSize(width: CGFloat, height: CGFloat) -> (CGFloat, CGFloat) {
var imageResizeHeight: CGFloat
var imageResizeWidth = maxContentWidth
let imageRatio = height / width
if width > height {
imageResizeHeight = CGFloat(Int(imageResizeWidth * imageRatio))
} else {
imageResizeHeight = imageResizeWidth
imageResizeWidth = CGFloat(Int(imageResizeWidth / imageRatio))
}
return (imageResizeWidth, imageResizeHeight)
}