【Swift3】ハート型レーティング作った【Xcode8】


# デモ
UIButtonと画像を使って以下のようなハート型のレーティングを作りました。

![demo.gif](https://qiita-image-store.s3.amazonaws.com/0/135478/7f7add5f-6d51-d31e-a13a-74d229696e7b.gif)


# 素材の準備
2種類のハート型の画像ファイルを準備します。
自作するのが面倒くさいのでアイコンフォントをPNGに変換できるサイト[fa2png.io](http://fa2png.io/)を使います。
アイコンの形、大きさを統一させたいのでFontAwesomeの「fa-heart」と「fa-heart-o」を使います。
以下のようにフォント名や色、サイズを指定して「Generate」ボタンを押下後、「Download」ボタンを押下して画像ファイルをダウンロード。
![heart1.PNG](https://qiita-image-store.s3.amazonaws.com/0/135478/5dcba89c-8483-e9e7-3bce-4fccd6357072.png)

そして出来上がった画像がこちら。
![heartEmpty.png](https://qiita-image-store.s3.amazonaws.com/0/135478/b3081db4-4376-a6e0-0987-8edf1dacb0d0.png)
heartEmpty.png
![heartFill.png](https://qiita-image-store.s3.amazonaws.com/0/135478/b0204187-411d-df84-92aa-eb16027e91b9.png)
heartFill.png
# Xcodeで実装

### 1. Single View Applicationで適当にプロジェクトを作成します。
今回はHeartRatingという名前で作成しました。

### 2. 先程作った画像をドラッグ&ドロップしてプロジェクトに追加します。
![1.png](https://qiita-image-store.s3.amazonaws.com/0/135478/7693453f-cba0-ab93-e5e2-c19fea457a22.png)

### 3. UIButtonを配置します。
![2.png](https://qiita-image-store.s3.amazonaws.com/0/135478/99cbb964-7d09-302e-b11a-f5dd1ae94446.png)

### 4. UIButtonの文字を削除して画像（heartEmpty）を設定します。
![3.png](https://qiita-image-store.s3.amazonaws.com/0/135478/6998627b-cb97-9816-e5bf-4cab7717dd18.png)

### 5. これと同じものを全部で5つ作ります。AutoLayoutも設定してキレイに並べます。
![4.png](https://qiita-image-store.s3.amazonaws.com/0/135478/7f5101e8-4cd6-0079-15c1-7b912b8cf4ca.png)

### 6. レーティングの数字を表示させるLableを配置し、初期値を0にしておきます。
![スクリーンショット 2016-10-08 9.22.50.png](https://qiita-image-store.s3.amazonaws.com/0/135478/cc6ab471-6e2a-b2d3-ed7a-b6172d3ebe9e.png)

### 7. 先程作ったボタンとラベルをIBOutletで接続します。
あと画像のインスタンス生成とレーティングの数字を入れる変数も用意しておきます。

```ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var heart1: UIButton!
    @IBOutlet weak var heart2: UIButton!
    @IBOutlet weak var heart3: UIButton!
    @IBOutlet weak var heart4: UIButton!
    @IBOutlet weak var heart5: UIButton!
    @IBOutlet weak var rateLabel: UILabel!
    
    var rate: Int = 0
    
    let heartFill:UIImage = UIImage(named:"heartFill")!
    let heartEmpty:UIImage = UIImage(named:"heartEmpty")!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
```
### 8. さらにボタンをIBActionで接続します。

```ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var heart1: UIButton!
    @IBOutlet weak var heart2: UIButton!
    @IBOutlet weak var heart3: UIButton!
    @IBOutlet weak var heart4: UIButton!
    @IBOutlet weak var heart5: UIButton!
    @IBOutlet weak var rateLabel: UILabel!
    
    var rate: Int = 0
    
    let heartFill:UIImage = UIImage(named:"heartFill")!
    let heartEmpty:UIImage = UIImage(named:"heartEmpty")!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func heart1Tapped(_ sender: AnyObject) {
    }
    
    @IBAction func heart2Tapped(_ sender: AnyObject) {
    }
    
    @IBAction func heart3Tapped(_ sender: AnyObject) {
    }
    
    @IBAction func heart4Tapped(_ sender: AnyObject) {
    }
    
    @IBAction func heart5Tapped(_ sender: AnyObject) {
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
```

### 9. 各ボタンをタップした時に数字と画像が変わるように設定し、完成

```ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var heart1: UIButton!
    @IBOutlet weak var heart2: UIButton!
    @IBOutlet weak var heart3: UIButton!
    @IBOutlet weak var heart4: UIButton!
    @IBOutlet weak var heart5: UIButton!
    @IBOutlet weak var rateLabel: UILabel!
    
    var rate: Int = 0
    
    let heartFill:UIImage = UIImage(named:"heartFill")!
    let heartEmpty:UIImage = UIImage(named:"heartEmpty")!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func heart1Tapped(_ sender: AnyObject) {
        switch rate {            
        case 1:
            rate = 0
            
            heart1.setImage(heartEmpty, for: UIControlState())
            heart2.setImage(heartEmpty, for: UIControlState())
            heart3.setImage(heartEmpty, for: UIControlState())
            heart4.setImage(heartEmpty, for: UIControlState())
            heart5.setImage(heartEmpty, for: UIControlState())
        default:
            rate = 1
            
            heart1.setImage(heartFill, for: UIControlState())
            heart2.setImage(heartEmpty, for: UIControlState())
            heart3.setImage(heartEmpty, for: UIControlState())
            heart4.setImage(heartEmpty, for: UIControlState())
            heart5.setImage(heartEmpty, for: UIControlState())
        }
        rateLabel.text = String(rate)
    }
    
    @IBAction func heart2Tapped(_ sender: AnyObject) {
        rate = 2
        
        heart1.setImage(heartFill, for: UIControlState())
        heart2.setImage(heartFill, for: UIControlState())
        heart3.setImage(heartEmpty, for: UIControlState())
        heart4.setImage(heartEmpty, for: UIControlState())
        heart5.setImage(heartEmpty, for: UIControlState())
        
        rateLabel.text = String(rate)
    }
    
    @IBAction func heart3Tapped(_ sender: AnyObject) {
        rate = 3
        
        heart1.setImage(heartFill, for: UIControlState())
        heart2.setImage(heartFill, for: UIControlState())
        heart3.setImage(heartFill, for: UIControlState())
        heart4.setImage(heartEmpty, for: UIControlState())
        heart5.setImage(heartEmpty, for: UIControlState())
        
        rateLabel.text = String(rate)
    }
    
    @IBAction func heart4Tapped(_ sender: AnyObject) {
        rate = 4
        
        heart1.setImage(heartFill, for: UIControlState())
        heart2.setImage(heartFill, for: UIControlState())
        heart3.setImage(heartFill, for: UIControlState())
        heart4.setImage(heartFill, for: UIControlState())
        heart5.setImage(heartEmpty, for: UIControlState())
        
        rateLabel.text = String(rate)
    }
    
    @IBAction func heart5Tapped(_ sender: AnyObject) {
        rate = 5
        
        heart1.setImage(heartFill, for: UIControlState())
        heart2.setImage(heartFill, for: UIControlState())
        heart3.setImage(heartFill, for: UIControlState())
        heart4.setImage(heartFill, for: UIControlState())
        heart5.setImage(heartFill, for: UIControlState())
        
        rateLabel.text = String(rate)
    }
}
```

# ※10/8 追記
コメントでアドバイスを頂きましたので修正いたしました。
ViewDidLoad内でnormalとselected時の画像を設定。
rateにwillsetを設定して、タップ時にボタンのstateを切り替える。

コードがだいぶ短くなり、スッキリしました。ありがとうございます。

```ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var heart1: UIButton!
    @IBOutlet weak var heart2: UIButton!
    @IBOutlet weak var heart3: UIButton!
    @IBOutlet weak var heart4: UIButton!
    @IBOutlet weak var heart5: UIButton!
    @IBOutlet weak var rateLabel: UILabel!
    
    
    let heartFill:UIImage = UIImage(named:"heartFill")!
    let heartEmpty:UIImage = UIImage(named:"heartEmpty")!
    
    var rate: Int = 0 {
        willSet {
            let hearts = [heart1, heart2, heart3, heart4, heart5]
            for (i, heart) in hearts.enumerated() {
                heart?.isSelected = i < newValue
            }
            rateLabel.text = "\(newValue)"
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let hearts = [heart1, heart2, heart3, heart4, heart5]
        for heart in hearts {
            heart?.setImage(heartEmpty, for: .normal)
            heart?.setImage(heartFill, for: .selected)
        }
    }

    @IBAction func heart1Tapped(_ sender: AnyObject) {
        switch rate {
        case 1:
            rate = 0
            
        default:
            rate = 1
        }
    }
    
    @IBAction func heart2Tapped(_ sender: AnyObject) {
        rate = 2
    }
    
    @IBAction func heart3Tapped(_ sender: AnyObject) {
        rate = 3
    }
    
    @IBAction func heart4Tapped(_ sender: AnyObject) {
        rate = 4
    }
    
    @IBAction func heart5Tapped(_ sender: AnyObject) {
        rate = 5
    }
}

```

# ※10/9 追記
コメントでさらにアドバイスを頂きました。
StoryBoard上で、UIButtonのState ConfigをSelectedにしてImageにheartFill.pngを設定。viewDidLoad内のコードは要らなくなったので削除しました。

さらにheart1~5のUIButtonのtagにそれぞれ1～5を設定。
全てのButtonのIBActionを一つにまとめて、その中に処理を記述。

コードがめちゃくちゃ短くなりました＼(^O^)／
ありがとうございます。

```ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var heart1: UIButton!
    @IBOutlet weak var heart2: UIButton!
    @IBOutlet weak var heart3: UIButton!
    @IBOutlet weak var heart4: UIButton!
    @IBOutlet weak var heart5: UIButton!
    @IBOutlet weak var rateLabel: UILabel!
    
    
    let heartFill:UIImage = UIImage(named:"heartFill")!
    let heartEmpty:UIImage = UIImage(named:"heartEmpty")!
    
    var rate: Int = 0 {
        willSet {
            let hearts = [heart1, heart2, heart3, heart4, heart5]
            for (i, heart) in hearts.enumerated() {
                heart?.isSelected = i < newValue
            }
            rateLabel.text = "\(newValue)"
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func heartButtonDidTap(_ sender: UIButton) {
        switch rate {
        case 1:
            if sender.tag == 1 {
                rate = 0
            } else {
                rate = sender.tag
            }
        default:
            rate = sender.tag
        }
    }
}
```