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

はじめに

f:id:yoppy0066:20150919003617p:plain

今回やりたかったのはこんなかんじの画面です。

iosで吹き出しのライブラリとか実装方法とか検索するとちょいちょい出てくるのですが、ちょっと難しかったりで結局自分で実装することにしました。

最初は吹き出しの画像を用意してそれを背景にしたUILabelとかで実装しようとしたのですが、なんかきれいにできず画像なしでつくることにしました。

UITableViewを使って、1メッセージを1行にするようなイメージです。

実装方法

あんまりスマートなやり方ではないのですが、こんなかんじでUIViewの中にUILabelをいれて吹き出しの部分は三角形の線を引いたUIViewを重ねてそれっぽくさせてみました。
f:id:yoppy0066:20150919004327p:plain

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ぽくなってよかったにゃん。

以上です