String Bounding Korrektes Berechnungsproblem, wenn der String '\ n' enthält
Ich entwickle eine WhatsApp-Funktion in unserer App. Nachrichten werden von JSON analysiert und dann als Textnachrichten (+ optionales Bild) in einer UITableView erstellt (jede Nachricht ist eine benutzerdefinierte Zelle). Die Nachrichtenblase wird mithilfe des Bezier-Pfads gezeichnet, basierend auf der Berechnung des Textrahmens mithilfe der boundingRect-Methode. Später werden UILabel und UIImage als Unteransichten einer UIStackview hinzugefügt, und sowohl die StackView- als auch die Nachrichtenblasenansicht sind auf eine Containeransicht beschränkt.
Manchmal, wenn der Text '\ n' enthält, wird das UILabel entweder abgeschnitten (mit '...') oder fließt unter die Nachrichtenblasenansicht, abhängig von der unteren Ankerpriorität der Stapelansicht (höher / niedriger als die inhaltliche Priorität des UILabels) ), aber andere Nachrichten, die Zeilenumbrüche enthalten, werden korrekt angezeigt. Ich vermute, dass die Rahmenberechnung der Zeichenfolge das '\ n' als 2 Zeichen anstelle einer neuen Zeile behandelt.
Als ich versuchte, denselben Code auf einem Spielplatz zu testen (mit einem einfacheren Layout, nur UILabel und Blasenansicht, keine Containeransichten, keine Tabellenansicht und keine Einschränkungen), schien alles gut zu funktionieren und die Blase würde sich erweitern, um sich an die hinzugefügten Zeilenumbrüche anzupassen.
Basierend auf diesem Thread habe ich versucht, den Code durch die sizeThatFits-Methode zu ersetzen, immer noch das gleiche Ergebnis. Schließlich zählte ich die Vorkommen von '\ n' innerhalb der Zeichenfolge und fügte dem Frame manuell Höhe hinzu, aber dies wirkt sich sowohl auf die schlechten als auch auf die guten Nachrichten aus, die inzwischen zusätzlichen Platz um sie herum haben.
Screenshots, relevanter Code und Konsolenprotokolle sind unten angefügt. Hoffentlich hilft es jemandem, dies herauszufinden.
Bearbeiten: Durch Ändern der Breite von messageView von UIScreen.main.bounds.width * 0.73 in UIScreen.main.bounds.width * 0.8 wurde das Problem behoben. Ich kann jedoch immer noch nicht herausfinden, warum es nur bestimmte Nachrichten betraf. Für weitere Informationen hierzu bin ich dankbar.
ChatMessageModel.swift
fileprivate func setText(_ label: ClickableUILabel, _ text: String, _ shouldLimitSize: Bool, _ shouldOpenLinks: Bool) {
...
// set text frame
let textFrameHeight: CGFloat = shouldLimitSize ? 40.0 : .greatestFiniteMagnitude
let constraintRect = CGSize(width: innerContentWidth, height: textFrameHeight)
let boundingBox = text.boundingRect(with: constraintRect,
options: .usesLineFragmentOrigin,
attributes: [.font: label.font!],
context: nil)
// width must have minimum value for short text to appear centered
let widthCeil = ceil(boundingBox.width)
let constraintWidthWithInset = constraintRect.width - 30
var height: CGFloat
if text.isEmpty {
height = 0
} else {
// min value of 40
height = max(ceil(boundingBox.height), 40) + 5
}
// ***** This part fixes bad messages but messes up good messages ****
// add extra height for newLine inside text
if let newLineCount = label.text?.countInstances(of: "\n"), newLineCount > 0 {
LOG("found \n")
height += CGFloat((newLineCount * 8))
}
label.frame.size = CGSize(width:max(widthCeil, constraintWidthWithInset),
height: height)
label.setContentHuggingPriority(UILayoutPriority(200), for: .horizontal)
}
fileprivate func setTextBubble(_ label: UILabel, _ image: String?, _ video: String?, _ shouldLimitSize: Bool) -> CustomRoundedCornerRectangle {
// configure bubble size
var contentHeight = CGFloat()
if imageDistribution! == .alongsideText {
contentHeight = max(label.frame.height, contentImageView.frame.height)
} else {
contentHeight = label.frame.height + contentImageView.frame.height + 20
}
// messages with no text on main feed should have smaller width
let width: CGFloat = shouldLimitSize && (label.text ?? "").isEmpty ? 150.0 : UIScreen.main.bounds.width * 0.73
let bubbleFrame = CGRect(x: 0, y: 0, width: width, height: contentHeight + 20)
let messageView = CustomRoundedCornerRectangle(frame: bubbleFrame)
messageView.heightAnchor.constraint(equalToConstant: bubbleFrame.size.height).isActive = true
messageView.widthAnchor.constraint(equalToConstant: bubbleFrame.size.width).isActive = true
messageView.translatesAutoresizingMaskIntoConstraints = false
self.messageViewFrame = bubbleFrame
return messageView
}
fileprivate func layoutSubviews(_ containerView: UIView, _ messageView: CustomRoundedCornerRectangle, _ timeLabel: UILabel, _ profileImageView: UIImageView, _ profileName: UILabel, _ label: UILabel, _ contentImageView: CustomImageView, _ imagePlacement: imagePlacement) {
// container view
containerView.addSubview(messageView)
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.autoSetDimension(.width, toSize: UIScreen.main.bounds.width * 0.8)
containerView.autoPinEdge(.bottom, to: .bottom, of: messageView)
messageView.autoPinEdge(.top, to: .top, of: containerView, withOffset: 23)
// time label
containerView.addSubview(timeLabel)
timeLabel.autoPinEdge(.bottom, to: .top, of: messageView)
timeLabel.autoPinEdge(.leading, to: .leading, of: containerView, withOffset: -2)
// profile image
containerView.addSubview(profileImageView)
profileImageView.autoPinEdge(.trailing, to: .trailing, of: containerView, withOffset: 15)
profileImageView.autoPinEdge(.top, to: .top, of: containerView, withOffset: 30)
messageView.autoPinEdge(.trailing, to: .leading, of: profileImageView, withOffset: 15)
// profile name
containerView.addSubview(profileName)
profileName.autoAlignAxis(.horizontal, toSameAxisOf: timeLabel)
profileName.autoPinEdge(.trailing, to: .trailing, of: messageView, withOffset: -2)
if isSameAuthor {
profileName.isHidden = true
profileImageView.isHidden = true
}
// content stack view
let contenStackView = UIStackView(forAutoLayout: ())
messageView.addSubview(contenStackView)
if imageDistribution! == .alongsideText {
contenStackView.axis = NSLayoutConstraint.Axis.horizontal
contenStackView.alignment = UIStackView.Alignment.center
} else {
contenStackView.axis = NSLayoutConstraint.Axis.vertical
contenStackView.alignment = UIStackView.Alignment.trailing
}
contenStackView.spacing = 5.0
contenStackView.autoPinEdge(.leading, to: .leading, of: messageView, withOffset: 15)
contenStackView.autoPinEdge(.trailing, to: .trailing, of: messageView, withOffset: -40)
contenStackView.autoPinEdge(.top, to: .top, of: messageView, withOffset: 10)
let bottomConstraint = contenStackView.bottomAnchor.constraint(equalTo: messageView.bottomAnchor, constant: -10)
bottomConstraint.priority = UILayoutPriority(800)
bottomConstraint.isActive = true
//Add Chat image and Message
contenStackView.addArrangedSubview(contentImageView)
if imagePlacement == .alongsideText || !label.text!.isEmpty { // do not insert empty labels if above text
contenStackView.addArrangedSubview(label)
}
}
CustromRoundedCorenerRectangle.swift
class CustomRoundedCornerRectangle: UIView {
lazy var shapeLayer = CAShapeLayer()
var frameToUse: CGRect?
override init(frame: CGRect) {
super.init(frame: frame)
setup(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup(frame: CGRect(x: 0, y: 0, width: 300, height: 100))
}
func setup(frame: CGRect) {
// keep frame for later use
frameToUse = frame
// create CAShapeLayer
// apply other properties related to the path
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.lineWidth = 1.0
shapeLayer.strokeColor = UIColor(red: 212/255, green: 212/255, blue: 212/255, alpha: 1.0).cgColor
shapeLayer.position = CGPoint(x: 0, y: 0)
// add the new layer to our custom view
self.layer.addSublayer(shapeLayer)
}
func updateBezierPath(frame: CGRect) {
let path = UIBezierPath()
let largeCornerRadius: CGFloat = 18
let smallCornerRadius: CGFloat = 10
let upperCornerSpacerRadius: CGFloat = 2
let imageToArcSpace: CGFloat = 5
var rect = frame
// bezier frame is smaller than messageView frame
rect.size.width -= 20
// move to starting point
path.move(to: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY))
// draw bottom left corner
path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
startAngle: .pi / 2, // straight down
endAngle: .pi, // straight left
clockwise: true)
// draw left line
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + smallCornerRadius))
// draw top left corner
path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.minY + smallCornerRadius), radius: smallCornerRadius,
startAngle: .pi, // straight left
endAngle: .pi / 2 * 3, // straight up
clockwise: true)
// draw top line
path.addLine(to: CGPoint(x: rect.maxX - largeCornerRadius, y: rect.minY))
// draw concave top right corner
// first arc
path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius, y: rect.minY + upperCornerSpacerRadius), radius: upperCornerSpacerRadius, startAngle: .pi / 2 * 3, // straight up
endAngle: .pi / 2, // straight left
clockwise: true)
// second arc
path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius + imageToArcSpace, y: rect.minY + largeCornerRadius + upperCornerSpacerRadius * 2 + imageToArcSpace), radius: largeCornerRadius + imageToArcSpace, startAngle: CGFloat(240.0).toRadians(), // up with offset
endAngle: .pi, // straight left
clockwise: false)
// draw right line
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - smallCornerRadius))
// draw bottom right corner
path.addArc(withCenter: CGPoint(x: rect.maxX - smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
startAngle: 0, // straight right
endAngle: .pi / 2, // straight down
clockwise: true)
// draw bottom line to close the shape
path.close()
shapeLayer.path = path.cgPath
}
}
extension CGFloat {
func toRadians() -> CGFloat {
return self * CGFloat(Double.pi) / 180.0
}
}
CustomChatTableViewCell.swift
class ChatMessageCell: UITableViewCell {
let horizontalInset: CGFloat = 30.0
let bottomInset: CGFloat = 10.0
var topInset: CGFloat = 5.0
var didSetupConstraints = false
var messageObject: ChatMessageModel?
weak var delegate: Notify?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// what we will call from our tableview method
func configure(with item: ChatItem?, previousItem: ChatItem?, delegate: Notify?) {
if let safeItem = item {
messageObject = ChatMessageModel().createMessage(chatItem: safeItem, previousItem: previousItem, shouldLimitSize: false,shouldAddMediaTap: true, imagePlacement: .aboveText, shouldOpenLinks: true)
messageObject?.delegate = delegate
let messageContainerView = messageObject?.containerView
contentView.addSubview(messageContainerView!)
contentView.backgroundColor = .clear
backgroundColor = .clear
selectionStyle = .none
// pin together messages from same author
if safeItem.user?.name == previousItem?.user?.name {
topInset = -10.0
} else {
topInset = 5.0
}
messageContainerView?.autoPinEdge(toSuperviewEdge: .top, withInset: topInset)
messageContainerView?.autoAlignAxis(.vertical, toSameAxisOf: contentView, withOffset: 0)
messageContainerView?.autoPinEdge(toSuperviewEdge: .bottom, withInset: bottomInset)
}
}
override func prepareForReuse() {
messageObject?.containerView.removeFromSuperview()
}
override func layoutSubviews() {
super.layoutSubviews()
// redraw message background
messageObject?.messageView?.updateBezierPath(frame: (messageObject!.messageView!.frameToUse!))
}
}
Protokoll der abgeschnittenen Nachricht:
(
"<NSLayoutConstraint:0x600000294960 Sport5.CustomRoundedCornerRectangle:0x7f9af3c9e990.height == 89 (active)>",
"<NSLayoutConstraint:0x6000002dc8c0 V:[Sport5.CustomRoundedCornerRectangle:0x7f9af3c9e990]-(0)-| (active, names: '|':UIView:0x7f9af3ce99a0 )>",
"<NSLayoutConstraint:0x6000002ddef0 V:|-(23)-[Sport5.CustomRoundedCornerRectangle:0x7f9af3c9e990] (active, names: '|':UIView:0x7f9af3ce99a0 )>",
"<NSLayoutConstraint:0x600000237890 V:|-(-10)-[UIView:0x7f9af3ce99a0] (active, names: '|':UITableViewCellContentView:0x7f9af3cdd730 )>",
"<NSLayoutConstraint:0x600000237610 UIView:0x7f9af3ce99a0.bottom == UITableViewCellContentView:0x7f9af3cdd730.bottom - 10 (active)>",
"<NSLayoutConstraint:0x600000203ca0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7f9af3cdd730.height == 108 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600000294960 Sport5.CustomRoundedCornerRectangle:0x7f9af3c9e990.height == 89 (active)>
Protokoll einer Nachricht mit Zeilenumbruch, die in Ordnung angezeigt wurde (es gibt ein Breitenproblem, aber ich glaube nicht, dass es etwas mit diesem Problem zu tun hat).
(
"<NSLayoutConstraint:0x600003de94a0 Sport5.CustomImageView:0x7fc7fd4c0540.width == 273.24 (active)>",
"<NSLayoutConstraint:0x600003deaf80 Sport5.CustomRoundedCornerRectangle:0x7fc7fd4e2730.width == 302.22 (active)>",
"<NSLayoutConstraint:0x600003d3fde0 H:|-(15)-[UIStackView:0x7fc7ff2d8430] (active, names: '|':Sport5.CustomRoundedCornerRectangle:0x7fc7fd4e2730 )>",
"<NSLayoutConstraint:0x600003d3fe30 UIStackView:0x7fc7ff2d8430.trailing == Sport5.CustomRoundedCornerRectangle:0x7fc7fd4e2730.trailing - 40 (active)>",
"<NSLayoutConstraint:0x600003de9d10 'UISV-canvas-connection' UIStackView:0x7fc7ff2d8430.leading == _UILayoutSpacer:0x60000219f660'UISV-alignment-spanner'.leading (active)>",
"<NSLayoutConstraint:0x600003deba20 'UISV-canvas-connection' H:[Sport5.CustomImageView:0x7fc7fd4c0540]-(0)-| (active, names: '|':UIStackView:0x7fc7ff2d8430 )>",
"<NSLayoutConstraint:0x600003dea8f0 'UISV-spanning-boundary' _UILayoutSpacer:0x60000219f660'UISV-alignment-spanner'.leading <= Sport5.CustomImageView:0x7fc7fd4c0540.leading (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600003de94a0 Sport5.CustomImageView:0x7fc7fd4c0540.width == 273.24 (active)>
Nachricht abschneiden
gute Nachricht, jetzt mit zusätzlichem Platz
Einschränkungen bei der Beschriftung von Nachrichten
schlechte Nachrichtenstapeleinschränkungen
Gute Einschränkungen für die Nachrichtenbezeichnung
Gute Einschränkungen für den Nachrichtenstapel
Antworten
Ich denke, Sie werden feststellen, dass es viel besser funktioniert, wenn Sie das automatische Layout die gesamte Größe übernehmen lassen. Sie müssen sich nicht auf die Berechnung der Größe von Textbegrenzungsrahmen verlassen.
Hier ist ein Beispiel mit einigen Beispieldaten:
und nach dem Scrollen, um einige Nachrichten ohne Inhaltsbilder anzuzeigen:
Der Code, den ich verwendet habe:
Beispielstruktur und Daten
struct MyMessageStruct {
var time: String = " "
var name: String = " "
var profileImageName: String = ""
var contentImageName: String = ""
var message: String = " "
}
class SampleData: NSObject {
let sampleStrings: [String] = [
"First message with short text.",
"Second message with longer text that should cause word wrapping in this cell.",
"Third message with some embedded newlines.\nThis line comes after a newline (\"\\n\"), so we can see if that works the way we want.",
"Message without content image.",
"Longer Message without content image.\n\nWith a pair of embedded newline (\"\\n\") characters giving us a \"blank line\" in the message text.",
"The sixth message, also without a content image."
]
lazy var sampleData: [MyMessageStruct] = [
MyMessageStruct(time: "08:36", name: "Bob", profileImageName: "pro1", contentImageName: "content1", message: sampleStrings[0]),
MyMessageStruct(time: "08:47", name: "Bob", profileImageName: "pro1", contentImageName: "content2", message: sampleStrings[1]),
MyMessageStruct(time: "08:59", name: "Joe", profileImageName: "pro2", contentImageName: "content3", message: sampleStrings[2]),
MyMessageStruct(time: "09:06", name: "Steve", profileImageName: "pro3", contentImageName: "", message: sampleStrings[3]),
MyMessageStruct(time: "09:21", name: "Bob", profileImageName: "pro1", contentImageName: "", message: sampleStrings[4]),
MyMessageStruct(time: "09:45", name: "Joe", profileImageName: "pro2", contentImageName: "", message: sampleStrings[5]),
]
}
Table View Controller
class ChatTableViewController: UITableViewController {
var myData: [MyMessageStruct] = SampleData().sampleData
override func viewDidLoad() {
super.viewDidLoad()
// register the cell
tableView.register(ChatMessageCell.self, forCellReuseIdentifier: "chatCell")
tableView.separatorStyle = .none
tableView.backgroundView = GrayGradientView()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "chatCell", for: indexPath) as! ChatMessageCell
// don't show the profile image if this message is from the same person
// as the previous message
var isSameAuthor = false
if indexPath.row > 0 {
if myData[indexPath.row].name == myData[indexPath.row - 1].name {
isSameAuthor = true
}
}
cell.fillData(myData[indexPath.row], isSameAuthor: isSameAuthor)
return cell
}
}
Zellklasse
Möglicherweise möchten Sie den Abstand anpassen, aber die Kommentare, die das Layout erläutern, sollten klar machen, wo Änderungen vorgenommen werden müssen.
class ChatMessageCell: UITableViewCell {
let timeLabel = UILabel()
let nameLabel = UILabel()
let profileImageView = RoundImageView()
let bubbleView = CustomRoundedCornerRectangle()
let stackView = UIStackView()
let contentImageView = UIImageView()
let messageLabel = UILabel()
var contentImageHeightConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
[timeLabel, nameLabel, profileImageView, bubbleView, stackView, contentImageView, messageLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
// MARK: add cell elements
contentView.addSubview(timeLabel)
contentView.addSubview(nameLabel)
contentView.addSubview(profileImageView)
contentView.addSubview(bubbleView)
bubbleView.addSubview(stackView)
stackView.addArrangedSubview(contentImageView)
stackView.addArrangedSubview(messageLabel)
// MARK: cell element constraints
// make constraints relative to the default cell margins
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
// timeLabel Top: 0 / Leading: 20
timeLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
timeLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
// nameLabel Top: 0 / Trailing: 30
nameLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
nameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -30.0),
// profile image
// Top: bubbleView.top + 6
profileImageView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 6.0),
// Trailing: 0 (to contentView margin)
profileImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
// Width: 50 / Height: 1:1 (to keep it square / round)
profileImageView.widthAnchor.constraint(equalToConstant: 50.0),
profileImageView.heightAnchor.constraint(equalTo: profileImageView.widthAnchor),
// bubbleView
// Top: timeLabel.bottom + 4
bubbleView.topAnchor.constraint(equalTo: timeLabel.bottomAnchor, constant: 4.0),
// Leading: timeLabel.leading + 16
bubbleView.leadingAnchor.constraint(equalTo: timeLabel.leadingAnchor, constant: 16.0),
// Trailing: profile image.leading - 4
bubbleView.trailingAnchor.constraint(equalTo: profileImageView.leadingAnchor, constant: -4.0),
// Bottom: contentView.bottom
bubbleView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
// stackView (to bubbleView)
// Top / Bottom: 12
stackView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 12.0),
stackView.bottomAnchor.constraint(equalTo: bubbleView.bottomAnchor, constant: -12.0),
// Leading / Trailing: 16
stackView.leadingAnchor.constraint(equalTo: bubbleView.leadingAnchor, constant: 16.0),
stackView.trailingAnchor.constraint(equalTo: bubbleView.trailingAnchor, constant: -16.0),
])
// contentImageView height ratio - will be changed based on the loaded image
// we need to set its Priority to less-than Required or we get auto-layout warnings when the cell is reused
contentImageHeightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: 2.0 / 3.0)
contentImageHeightConstraint.priority = .defaultHigh
contentImageHeightConstraint.isActive = true
// messageLabel minimum Height: 40
// we need to set its Priority to less-than Required or we get auto-layout warnings when the cell is reused
let c = messageLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0)
c.priority = .defaultHigh
c.isActive = true
// MARK: element properties
stackView.axis = .vertical
stackView.spacing = 6
// set label fonts and alignment here
timeLabel.font = UIFont.systemFont(ofSize: 14, weight: .regular)
nameLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold)
timeLabel.textColor = .gray
nameLabel.textColor = UIColor(red: 0.175, green: 0.36, blue: 0.72, alpha: 1.0)
// for now, I'm just setting the message label to right-aligned
// likely using RTL
messageLabel.textAlignment = .right
messageLabel.numberOfLines = 0
contentImageView.backgroundColor = .blue
contentImageView.contentMode = .scaleAspectFit
contentImageView.layer.cornerRadius = 8
contentImageView.layer.masksToBounds = true
profileImageView.contentMode = .scaleToFill
// MARK: cell background
backgroundColor = .clear
contentView.backgroundColor = .clear
}
func fillData(_ msg: MyMessageStruct, isSameAuthor: Bool) -> Void {
timeLabel.text = msg.time
nameLabel.text = msg.name
nameLabel.isHidden = isSameAuthor
profileImageView.isHidden = isSameAuthor
if !isSameAuthor {
if !msg.profileImageName.isEmpty {
if let img = UIImage(named: msg.profileImageName) {
profileImageView.image = img
}
}
}
if !msg.contentImageName.isEmpty {
contentImageView.isHidden = false
if let img = UIImage(named: msg.contentImageName) {
contentImageView.image = img
let ratio = img.size.height / img.size.width
contentImageHeightConstraint.isActive = false
contentImageHeightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: ratio)
contentImageHeightConstraint.priority = .defaultHigh
contentImageHeightConstraint.isActive = true
}
} else {
contentImageView.isHidden = true
}
messageLabel.text = msg.message
}
}
Zusätzliche Klassen
Für die "Chat-Blasenansicht", "Bildansicht mit abgerundeten Ecken" und "Hintergrundansicht mit Farbverlauf"
class CustomRoundedCornerRectangle: UIView {
lazy var shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
// apply properties related to the path
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.lineWidth = 1.0
shapeLayer.strokeColor = UIColor(red: 212/255, green: 212/255, blue: 212/255, alpha: 1.0).cgColor
shapeLayer.position = CGPoint(x: 0, y: 0)
// add the new layer to our custom view
//self.layer.addSublayer(shapeLayer)
self.layer.insertSublayer(shapeLayer, at: 0)
}
override func layoutSubviews() {
let path = UIBezierPath()
let largeCornerRadius: CGFloat = 18
let smallCornerRadius: CGFloat = 10
let upperCornerSpacerRadius: CGFloat = 2
let imageToArcSpace: CGFloat = 5
let rect = bounds
// move to starting point
path.move(to: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY))
// draw bottom left corner
path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
startAngle: .pi / 2, // straight down
endAngle: .pi, // straight left
clockwise: true)
// draw left line
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + smallCornerRadius))
// draw top left corner
path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.minY + smallCornerRadius), radius: smallCornerRadius,
startAngle: .pi, // straight left
endAngle: .pi / 2 * 3, // straight up
clockwise: true)
// draw top line
path.addLine(to: CGPoint(x: rect.maxX - largeCornerRadius, y: rect.minY))
// draw concave top right corner
// first arc
path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius, y: rect.minY + upperCornerSpacerRadius), radius: upperCornerSpacerRadius, startAngle: .pi / 2 * 3, // straight up
endAngle: .pi / 2, // straight left
clockwise: true)
// second arc
path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius + imageToArcSpace, y: rect.minY + largeCornerRadius + upperCornerSpacerRadius * 2 + imageToArcSpace), radius: largeCornerRadius + imageToArcSpace, startAngle: CGFloat(240.0).toRadians(), // up with offset
endAngle: .pi, // straight left
clockwise: false)
// draw right line
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - smallCornerRadius))
// draw bottom right corner
path.addArc(withCenter: CGPoint(x: rect.maxX - smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
startAngle: 0, // straight right
endAngle: .pi / 2, // straight down
clockwise: true)
// draw bottom line to close the shape
path.close()
shapeLayer.path = path.cgPath
}
}
extension CGFloat {
func toRadians() -> CGFloat {
return self * CGFloat(Double.pi) / 180.0
}
}
class RoundImageView: UIImageView {
override func layoutSubviews() {
layer.masksToBounds = true
layer.cornerRadius = bounds.size.height * 0.5
}
}
class GrayGradientView: UIView {
private var gradLayer: CAGradientLayer!
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
let myColors: [UIColor] = [
UIColor(white: 0.95, alpha: 1.0),
UIColor(white: 0.90, alpha: 1.0),
]
gradLayer = self.layer as? CAGradientLayer
// assign the colors (we're using map to convert UIColors to CGColors
gradLayer.colors = myColors.map({$0.cgColor})
// start at the top
gradLayer.startPoint = CGPoint(x: 0.25, y: 0.0)
// end at the bottom
gradLayer.endPoint = CGPoint(x: 0.75, y: 1.0)
}
}
Und Beispielbilder (zum Vergrößern anklicken):
content1.png content2.png content3.png
pro1.png pro2.png pro3.png