Posted at

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


背景

iOSアプリ制作中に、Floating Action Button(FAB)の実装が必要になり、最終的にはFloatyを選択しました。

他にもGitHubでStarが多いものもありましたが、長らく更新がなさそうだったため、今回はSwift5にも対応しているFloatyを選びました。


表示例

Simulator Screen Shot - iPhone 8 - 2019-08-06 at 11.45.29.png

サンプルアプリで大体の動作は確かめられます。

文字表示化、カスタムアイコン設定化、ボタンのキーボード追従やドラッグ化

と必要な機能は揃っていそうです。


導入要件

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


  • iOS 10.0+

  • Swift 5.0


Catrhageで導入して実装してみました

今回、Carthageで導入したので、Carthageの場合を書きますが、CocoaPodsやマニュアルでの導入も可能なようです。

詳しくはREADMEをお読みください。


Cartfileに追記します。

Cartfileがまだない場合は、ターミナルでプロジェクトディレクトリまで移動してvi 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

(スクショの他のフレームワークは気にしないでください)


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に追記します。

ググると書き方がたくさん出てくるかと思います。


実装


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

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に適合して用意されたメソッドを使用します。

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
}
}