[Swift 5]Floatyライブラリでオーバーレイ付きFABを実装したのでカスタマイズ例など

##背景
iOSアプリ制作中に、Floating Action Button(FAB)の実装が必要になり、最終的には[Floaty](https://github.com/kciter/Floaty)を選択しました。
他にもGitHubでStarが多いものもありましたが、長らく更新がなさそうだったため、今回はSwift5にも対応しているFloatyを選びました。

##表示例
<img width="375" alt="Simulator Screen Shot - iPhone 8 - 2019-08-06 at 11.45.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54170/68f8d1cb-4082-a7e7-fee6-545fd7f8565d.png">
サンプルアプリで大体の動作は確かめられます。
文字表示化、カスタムアイコン設定化、ボタンのキーボード追従やドラッグ化
と必要な機能は揃っていそうです。

##導入要件
READMEによると、執筆現在のFloaty導入要件は以下の通りです。

- iOS 10.0+
- Swift 5.0

##Catrhageで導入して実装してみました
今回、Carthageで導入したので、Carthageの場合を書きますが、CocoaPodsやマニュアルでの導入も可能なようです。
詳しくはREADMEをお読みください。

###Cartfileに追記します。
Cartfileがまだない場合は、ターミナルでプロジェクトディレクトリまで移動して`vi Cartfile`などで作成してください。

```Cartfile
github "kciter/Floaty" == 4.2.0
```

執筆現在の最新バージョンが4.2.0なので4.2.0を指定しています。
その時の最新バージョンを指定しておけば大丈夫かと思います。

###ターミナルでCarthage update
```
carthage update --platform ios Floaty
```

ビルドされます。

###ビルドされたフレームワークを追加する
Xcode上でプロジェクト名を選択→TARGETSを選択→General→Linked Frameworks and LibrariesにFloaty.frameworkファイルを追加する。
![スクリーンショット 2019-08-06 11.15.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54170/b9603c36-80ab-267b-6ba9-cde86ee17ffc.png)
（スクショの他のフレームワークは気にしないでください）

###iuput.xcfilelistとoutput.xcfilelistに追記
input.xcfilelist, output.xcfilelistを使用する場合は、以下のように追記しましょう。

```input.xcfilelist
$(SRCROOT)/Carthage/Build/iOS/Floaty.framework
```

```output.xcfilelist
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Floaty.framework
```

xcfilelistを使用していない場合は、Build PhasesのRun Scriptに追記します。
ググると書き方がたくさん出てくるかと思います。

##実装
###まず、一番簡単な実装例です。

```swift
import UIKit
import Floaty

final class ViewController: UIViewController {
        
    override func viewDidLoad() {
        super.viewDidLoad()
        // Floatyを生成
        let floaty = Floaty()
        // 開いた時に現れるボタンアイテムを追加
        floaty.addItem(title: "SampleButton")
        // viewにFloatyを追加
        view.addSubview(floaty)
    }
```

これで、デフォルトの＋ボタンで開閉し、開いた時にはSampleButtonをいうラベル付きのボタンが表示できました。

###FloatyのDelegateメソッドを実装する
Floatyの開閉ボタンを押した時に動作を追加したいことも多々あるかと思います。
その場合はFloatyDelegateに適合して用意されたメソッドを使用します。

```swift
import UIKit
import Floaty

final class ViewController: UIViewController {
        
    override func viewDidLoad() {
        super.viewDidLoad()
        // Floatyを生成
        let floaty = Floaty()
        // 開いた時に現れるボタンアイテムを追加
        floaty.addItem(title: "SampleButton")
        // Delegateに自らを代入して指定
        floaty.fabDelegate = self  // 追加！
        // viewにFloatyを追加
        view.addSubview(floaty)
    }

// MARK: - FloatyDelegate
extension ViewController: FloatyDelegate {
    
    func floatyWillOpen(_ floaty: Floaty) {
        // ボタンが押されてFloatyが開く時に行いたいことを書く
    }
    
    func floatyWillClose(_ floaty: Floaty) {
        FloatyFactory.floatyWillClose(floaty)
        // ボタンが押されてFloatyが閉じる時に行いたいことを書く
    }
}
```

`floatyWillOpen`, `floatyWillClose`の他には、

- `floatyDidOpen`
- `floatyDidClose`
- `emptyFloatySelected`
- `floatyShouldOpen`
- `floatyShouldClose`

があります。

###細かくカスタマイズしていく
カスタマイズの前に…
今回、複数の画面で同じFABを使用したかったため、Factoryクラスを用意して使っています。
まずは

```FloatyFactory.swift
final class FloatyFactory {
    
    // Delegateメソッドでも使うので定数化
    static let openButtonImage = UIImage(named: "consultant")
    static let openButtonImageAfterOpening = UIImage(named: "1x1")
    static let paddingX: CGFloat = 8
    static let paddingY: CGFloat = -20
    static let opendPaddingX: CGFloat = -20
    static let opendPaddingY: CGFloat = -120
    
    static func makeFloaty(vc: UIViewController & FloatyDelegate) -> Floaty {
        
        let fab = Floaty()
        // 閉じた状態のボタン画像
        fab.buttonImage = openButtonImage
        // デフォルトのボタンを見えなくするために透明を指定
        fab.buttonColor = .clear
        fab.buttonShadowColor = .clear
        // アイテムもカスタム画像を使用するのでデフォルトの色を透明化
        fab.itemButtonColor = .clear
        // カスタム画像を使用するのでサイズを指定。正円になるもよう
        fab.size = 130
        // 右側からの余白
        fab.paddingX = paddingX
        // 下側からの余白
        fab.paddingY = paddingY
        // アイテム間の余白
        fab.itemSpace = 16
        fab.hasShadow = false
        // アニメーション付けたかったが、アイテムの位置をpaddingで指定しているので、
        // アニメーションをつけると不自然になってしまう
        fab.openAnimationType = .none
        // 開いた際の背景色を指定できる
        fab.overlayColor = UIColor(hex: "000000", alpha: 0.87)
        // デリゲートメソッドを使うために使用するViewControllerを指定する
        fab.fabDelegate = vc
        // 閉じるボタン
        let closeItem = FloatyItem()
        // サイズを指定しないと画像は小さく表示されてしまう
        closeItem.imageSize = CGSize(width: 76, height: 76)
        closeItem.icon = UIImage(named: "fab-close")
        closeItem.title = "Close".localized
        // Labelを指定できるので柔軟に見た目を変えられそう
        closeItem.titleLabel.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
        closeItem.titleLabel.textAlignment = .right
        // Floatyに定義したアイテムを追加する
        fab.addItem(item: closeItem)
        
        let lineItem = FloatyItem()
        lineItem.imageSize = CGSize(width: 68, height: 68)
        lineItem.title = "Inquiry via LINE".localized
        lineItem.titleLabel.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
        lineItem.titleLabel.textAlignment = .right
        lineItem.icon = UIImage(named: "LINE")
        lineItem.handler = { item in
            guard let url = URL(string: "LINEのURLスキームを指定") else { return }
            if UIApplication.shared.canOpenURL(url) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            } else {
                vc.showAlert(title: "LINE App is not installed", message: nil)
            }
        }
        fab.addItem(item: lineItem)
                
        return fab
    }
    
    // デリゲートメソッドで行いたいことも共通なのでメソッド化しておく
    static func floatyWillOpen(_ floaty: Floaty) {
        floaty.buttonImage = openButtonImageAfterOpening
        floaty.paddingX = opendPaddingX
        floaty.paddingY = opendPaddingY
    }
    
    static func floatyWillClose(_ floaty: Floaty) {
        floaty.buttonImage = openButtonImage
        floaty.paddingX = paddingX
        floaty.paddingY = paddingY
    }
}
```