在Swift中绘制带有尾巴的消息气泡

消息传递应用程序中的一个常见主题是使消息气泡带有尾巴,这比将一堆块彼此叠放更好,并且这是区分谁发送了特定消息的简便方法。 尽管在您现在的iOS开发经验中,您可能会想知道如何创建这些自定义视图,所以在这篇文章中,我将向您展示如何制作自己的视图!

注意:对于本教程,我已经准备了一个在Github上可用的项目,该项目具有动态调整UITableViewCell设置的大小。 这是使用Xcode 8.3.3在Swift 3中编写的。

贝济耶救援之路

创建一个方形的消息单元并不难,即使是圆角的消息单元也不错,但是您必须改变通常创建单元的方式,以便在消息底部添加一条尾巴。 在下面,我将指导您完成一些步骤,不仅在消息单元下方绘制一条尾巴,还根据发送消息的人将其向左或向右绘制,类似于iMessage。

为了获得最终结果,我们需要使用UIBezierPath绘制消息气泡的形状,然后用所需的颜色填充它。 乍一看似乎令人望而生畏,但想起来就像在纸上画一个正方形一样。 您从给定点开始,思考(0,0),然后开始将第一条线绘制到下一个点(1,0)。 再过三边,您又回到起点,准备关闭形状并填充它。现在,我们只需要在代码中执行相同的操作即可!

绘制邮件正文

跳至MessageBubbleView类,让我们开始在draw(_ rect)编写代码。 (删除调用super.drawRect因为我们将提供自己的绘图代码。)现在,我们可以使用以下代码开始路径:

 让bezierPath = UIBezierPath() 
bezierPath.move(收件人:CGPoint(x:rect.minX,y:rect.minY))

move视为拿起铅笔并将其放置在给定点的代码。 这将是我们信息泡沫的起点。 现在我们要画顶线。

  bezierPath.addLine(至:CGPoint(x:rect.maxX,y:rect.minY)) 

这将在rect的最右边绘制一条线,即maxX 。 现在,我们可以遵循相同的逻辑来绘制其他三个边缘。

  bezierPath.addLine(至:CGPoint(x:rect.maxX,y:rect.maxY-10.0)) 
bezierPath.addLine(至:CGPoint(x:rect.minX,y:rect.maxY-10.0))
bezierPath.addLine(至:CGPoint(x:rect.minX,y:rect.minY))

现在您可能想知道为什么我没有一直画线一直到rect或maxY的底部边缘。 这是因为我为绘制尾巴留了10.0点的空间。 我鼓励调整这些值,并查看您自己的代码中的不同结果!

画尾巴

现在添加尾巴。 我们将在这里再次使用move功能。 就像您从rect的左上边缘(我们刚刚结束最后一行的地方)拿起铅笔并将其移动到尾巴的起点一样。 在此示例中,我将绘制一条指向右下方的尾巴,然后绘制一条平坦的边缘,该边缘再次向上并与我们的邮件正文相遇。

  bezierPath.move(至:CGPoint(x:rect.maxX-25.0,y:rect.maxY-10.0)) 
bezierPath.addLine(至:CGPoint(x:rect.maxX-10.0,y:rect.maxY))
bezierPath.addLine(至:CGPoint(x:rect.maxX-10.0,y:rect.maxY-10.0))

我们有自己的形状! 但是,如果您运行该代码,则会发现看不到它。 这是因为我们需要关闭路径并填写。

  UIColor.lightGray.setFill() 
bezierPath.fill()
bezierPath.close()

大! 我们将路径封闭并用浅灰色填充。 现在,我们的draw方法应如下所示:

 覆盖func draw(_ rect:CGRect){ 
让bezierPath = UIBezierPath()
  //绘制主体 
bezierPath.move(收件人:CGPoint(x:rect.minX,y:rect.minY))
bezierPath.addLine(至:CGPoint(x:rect.maxX,y:rect.minY))
bezierPath.addLine(至:CGPoint(x:rect.maxX,y:rect.maxY-10.0))
bezierPath.addLine(至:CGPoint(x:rect.minX,y:rect.maxY-10.0))
bezierPath.addLine(至:CGPoint(x:rect.minX,y:rect.minY))
  //画尾巴 
bezierPath.move(至:CGPoint(x:rect.maxX-25.0,y:rect.maxY-10.0))
bezierPath.addLine(至:CGPoint(x:rect.maxX-10.0,y:rect.maxY))
bezierPath.addLine(至:CGPoint(x:rect.maxX-10.0,y:rect.maxY-10.0))
  UIColor.lightGray.setFill() 
bezierPath.fill()
bezierPath.close()
}

生成并运行项目,您应该得到以下结果:

移尾

看起来不错! 现在,如果尾巴根据发送消息的人而移动,那就太好了。 为此,我们将在MessageBubbleView上设置一个变量,该变量用作确定尾部位置的标志。

  var currentUserIsSender = true { 
didSet {
setNeedsDisplay()
}
}

现在,我们有一个名为currentUserIsSender的公共属性,默认为true 。 使用Swift的didSet可以让我们通过调用setNeedsDisplay通知该视图,该属性更改后需要重绘自身。 现在,在我们的draw方法中,我们可以基于此值调整尾部。

 如果currentUserIsSender { 
bezierPath.move(至:CGPoint(x:rect.maxX-25.0,y:rect.maxY-10.0))
bezierPath.addLine(至:CGPoint(x:rect.maxX-10.0,y:rect.maxY))
bezierPath.addLine(至:CGPoint(x:rect.maxX-10.0,y:rect.maxY-10.0))
}其他{
//在左边画尾巴
}

在这里,我只是根据设置的标志将当前的尾部绘制代码移动到了if/else语句中。 现在,我们只需要添加代码以在左侧绘制尾巴即可。 所有需要做的是,而不是从maxX开始,我们从minX开始, minX所示:

  bezierPath.move(至:CGPoint(x:rect.minX + 25.0,y:rect.maxY-10.0)) 
bezierPath.addLine(至:CGPoint(x:rect.minX + 10.0,y:rect.maxY))
bezierPath.addLine(至:CGPoint(x:rect.minX + 10.0,y:rect.maxY-10.0))

现在,让我们添加一种更新currentUserIsSender标志的方法。 在MessageTableViewCellconfigure方法中添加一个新属性:

  func configure(withMessage消息:字符串,currentUserIsSender:布尔){ 
messageLabel.text =消息
messageContainerView.currentUserIsSender = currentUserIsSender
}

然后,我们可以添加一些逻辑来切换我们的cellForRow方法中的所有其他消息的标志:

 让currentUserIsSender = indexPath.row%2 == 0 
cell.configure(withMessage:消息,currentUserIsSender:currentUserIsSender)

好了,现在我们有了一条尾巴,可以根据发送消息的人来选择! 继续构建并运行项目以查看结果。

太棒了! 🎉

现在,您正在使用UIBezierPath运行! 您现在可以做的事情还很多,我鼓励您进一步尝试该项目! 将您的想法写在纸上,然后将其转换为代码,甚至更容易。

您可以尝试的一些想法是:

  • 根据发送者的不同来改变消息的颜色
  • 调整尾巴的形状和位置
  • 根据发送消息的人调整文本对齐方式

将来,我计划通过更多示例来扩展该项目。 在此之前,请试用当前项目,还可以通过从我的Github存储库中检出finished-project分支来找到完工项目。

如果您有任何疑问,请随时发表评论,我会尽力与您联系! 如果您喜欢这篇文章和/或觉得它很有用,请👏并分享!