【swift】吹き出しでLINEぽいメッセージアプリのUIを作ったのでメモ
はじめに

今回やりたかったのはこんなかんじの画面です。
iosで吹き出しのライブラリとか実装方法とか検索するとちょいちょい出てくるのですが、ちょっと難しかったりで結局自分で実装することにしました。
最初は吹き出しの画像を用意してそれを背景にしたUILabelとかで実装しようとしたのですが、なんかきれいにできず画像なしでつくることにしました。
UITableViewを使って、1メッセージを1行にするようなイメージです。
実装方法
あんまりスマートなやり方ではないのですが、こんなかんじでUIViewの中にUILabelをいれて吹き出しの部分は三角形の線を引いたUIViewを重ねてそれっぽくさせてみました。

ViewController.swift
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
{
var tableView: UITableView!
var tableData: [Dictionary<String,AnyObject>]!
var heightLeftCell: CustomLeftTableViewCell = CustomLeftTableViewCell()
var heightRightCell: CustomRightTableViewCell = CustomRightTableViewCell()
override func viewDidLoad()
{
super.viewDidLoad()
// データを初期化します
tableData = [
["name": "にゃんこ1", "type": 1, "image": "image1.jpg", "created": "今日", "message": "おはようございます\nねむいにゃん"],
["name": "にゃんこ2", "type": 2, "image": "image2.jpg", "created": "今日", "message": "おはよー\nさむくなってきたね"],
・・・
]
// VIEWをセットします
setView()
}
// VIEWをセットします
func setView()
{
let statusBarHeight: CGFloat = UIApplication.sharedApplication().statusBarFrame.height
let displayWidth = self.view.frame.width
let displayHeight = self.view.frame.height
tableView = UITableView(frame: CGRect(x:0, y:statusBarHeight, width:displayWidth, height:displayHeight - statusBarHeight))
tableView.registerClass(CustomLeftTableViewCell.self, forCellReuseIdentifier: "CustomLeftTableViewCell")
tableView.registerClass(CustomRightTableViewCell.self, forCellReuseIdentifier: "CustomRightTableViewCell")
tableView.delegate = self
tableView.dataSource = self
tableView.separatorColor = UIColor.clearColor()
self.view.addSubview(tableView)
}
// テーブルセルの高さをかえします
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
if 1 == self.tableData[indexPath.row]["type"] as! Int
{
return heightLeftCell.setData(tableView.frame.size.width - 20, data: self.tableData[indexPath.row])
}
else
{
return heightRightCell.setData(tableView.frame.size.width - 20, data: self.tableData[indexPath.row])
}
}
// テーブルセルにデータをセットします
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
if 1 == self.tableData[indexPath.row]["type"] as! Int
{
var cell = tableView.dequeueReusableCellWithIdentifier("CustomLeftTableViewCell", forIndexPath: indexPath) as! CustomLeftTableViewCell
cell.setData(tableView.frame.size.width - 20, data: self.tableData[indexPath.row])
return cell
}
else
{
var cell = tableView.dequeueReusableCellWithIdentifier("CustomRightTableViewCell", forIndexPath: indexPath) as! CustomRightTableViewCell
cell.setData(tableView.frame.size.width - 20, data: self.tableData[indexPath.row])
return cell
}
}
//・・・UITableViewに必要なメソッドとか
}CustomLeftTableViewCell.swift(1メッセージを1セルで)
class CustomLeftTableViewCell: UITableViewCell
{
var icon: UIImageView = UIImageView() // アイコン
var name: UILabel = UILabel() // 名前
var created: UILabel = UILabel() // 投稿日時
var arrow: CustomLeftArrow = CustomLeftArrow() // 吹き出しの突起の部分
var message: UILabel = UILabel() // 吹き出しの文字を表示している部分
var viewMessage: UIView = UIView() // 吹き出しの枠部分
required init(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.selectionStyle = UITableViewCellSelectionStyle.None
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?)
{
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = UITableViewCellSelectionStyle.None
self.viewMessage.layer.borderColor = UIColor.grayColor().CGColor
self.viewMessage.layer.borderWidth = 0.5
self.viewMessage.layer.cornerRadius = 10.0
self.viewMessage.backgroundColor = UIColor.yellowColor()
self.message.font = UIFont.systemFontOfSize(14)
self.message.numberOfLines = 0
self.message.backgroundColor = UIColor.greenColor()
self.viewMessage.addSubview(self.message)
self.contentView.addSubview(self.viewMessage)
self.arrow.backgroundColor = UIColor.whiteColor()
self.arrow.backgroundColor = UIColor.redColor()
self.addSubview(self.arrow)
self.icon.layer.borderColor = UIColor.blackColor().CGColor
self.icon.layer.borderWidth = 0.5
self.addSubview(self.icon)
self.name.font = UIFont.boldSystemFontOfSize(13)
self.name.textAlignment = NSTextAlignment.Left
self.addSubview(self.name)
self.created.font = UIFont.systemFontOfSize(13)
self.created.textAlignment = NSTextAlignment.Right
self.addSubview(self.created)
}
func setData(widthMax: CGFloat,data: Dictionary<String,AnyObject>) -> CGFloat
{
var marginLeft: CGFloat = 60
var marginRight: CGFloat = 0
var marginVertical: CGFloat = 30
// アイコン
var xIcon: CGFloat = 3
var yIcon: CGFloat = 3
var widthIcon: CGFloat = 48
var heightIcon: CGFloat = 48
self.icon.frame = CGRectMake(xIcon, yIcon, widthIcon, heightIcon)
self.icon.image = UIImage(named: (data["image"] as? String)!)
self.icon.layer.cornerRadius = self.icon.frame.size.width * 0.5
self.icon.clipsToBounds = true
// 名前
var xName: CGFloat = self.icon.frame.origin.x + self.icon.frame.size.width + 3
var yName: CGFloat = self.icon.frame.origin.y
var widthName: CGFloat = widthMax - (self.icon.frame.origin.x + self.icon.frame.size.width + 3)
var heightName: CGFloat = 30
self.name.text = data["name"] as? String
self.name.frame = CGRectMake(xName, yName, widthName, heightName)
// 投稿日時
var xCreated: CGFloat = self.icon.frame.origin.x + self.icon.frame.size.width + 3
var yCreated: CGFloat = self.icon.frame.origin.y
var widthCreated: CGFloat = widthMax - (self.icon.frame.origin.x + self.icon.frame.size.width + 10)
var heightCreated: CGFloat = 30
self.created.text = data["created"] as? String
self.created.frame = CGRectMake(xCreated, yCreated, widthCreated, heightCreated)
var paddingHorizon: CGFloat = 10
var paddingVertical: CGFloat = 10
var widthLabelMax: CGFloat = widthMax - (marginLeft + marginRight + paddingHorizon * 2)
var xMessageLabel: CGFloat = paddingHorizon
var yMessageLabel: CGFloat = paddingVertical
self.message.frame = CGRectMake(xMessageLabel, yMessageLabel, widthLabelMax, 0)
self.message.text = data["message"] as? String
self.message.sizeToFit()
var xMessageView: CGFloat = marginLeft
var yMessageView: CGFloat = marginVertical
var widthMessageView: CGFloat = self.message.frame.size.width + paddingHorizon * 2
var heightMessageView: CGFloat = self.message.frame.size.height + paddingVertical * 2
self.viewMessage.frame = CGRectMake(xMessageView, yMessageView, widthMessageView, heightMessageView)
var widthArrow: CGFloat = 10
var heightArrorw: CGFloat = 10
var xArrow: CGFloat = marginLeft - widthArrow + 1
var yArrow: CGFloat = self.viewMessage.frame.origin.y + heightArrorw
self.arrow.frame = CGRectMake(xArrow, yArrow, widthArrow, heightArrorw)
var height: CGFloat = self.viewMessage.frame.height + marginVertical * 2
return height
}
}CustomLeftArrow.swift(吹き出しの突起部分)
class CustomLeftArrow: UIView
{
override func drawRect(rect: CGRect)
{
var x: CGFloat = 0.0
var y: CGFloat = rect.size.height / 2
var x2: CGFloat = rect.size.width
var y2: CGFloat = 0.0
var x3: CGFloat = rect.size.width
var y3: CGFloat = rect.size.height
UIColor.blackColor().setStroke()
var line = UIBezierPath()
line.lineWidth = 0.3
line.moveToPoint(CGPointMake(x, y))
line.addLineToPoint(CGPointMake(x2, y2))
line.stroke()
var line2 = UIBezierPath()
line2.lineWidth = 0.3
line2.moveToPoint(CGPointMake(x3, y3))
line2.addLineToPoint(CGPointMake(x, y))
line2.stroke()
}
}とりあえず左側の部分だけになりますがこんなかんじで実装しました。
実装方法はスマートじゃないけど、なんとなくLINEぽくなってよかったにゃん。
以上です