チュートリアルに最適なMMPopLabelをSwiftに最適化

ネットサーフィンしていてたまたま見つけた記事[「iOSアプリのチュートリアルに便利なMMPopLabel」](https://re-engines.com/2017/06/21/iosアプリのチュートリアルに便利なmmpoplabel/)を読んでいて、SwiftだからDelegateよりClosureの方がいいなーと思い作ってみました

(delegateよりclosureの方がいいと思うのは、自分の主観なのでお許しください:sob:

# MMPopLabelとは
最初に記事にもあった**MMPopLabel**とはなんだろうと
GitHubは[こちら](https://github.com/mgcm/MMPopLabel)になります
READMEに詳細がありますが、簡単にいうとチュートリアルを作るときに吹き出し(pop)を簡単に実装できますよというライブラリです！
> ![MMPopLabel-1.png](https://qiita-image-store.s3.amazonaws.com/0/150615/922a9f97-0b2d-2ec3-6739-9a660ebde0f4.png)

popの色やメッセージのfontも指定できますし、
ボタン（画像だと「Skip Tutorial」と「OK, Got It!」）も付けられます！

## Usage
インスタンス作成して、ボタンが必要なら追加して...と！

```swift
var label: MMPopLabel! = nil

override func viewDidLoad() {
	super.viewDidLoad()

	// popの詳細設定
	MMPopLabel.appearance().labelColor = UIColor.darkGray
	MMPopLabel.appearance().labelTextColor = UIColor.white
	MMPopLabel.appearance().labelTextHighlightColor = UIColor.cyan
	MMPopLabel.appearance().labelFont = UIFont(name: "HelveticaNeue-Light", size: 12)
	MMPopLabel.appearance().buttonFont = UIFont(name: "HelveticaNeue", size: 12)

	label = MMPopLabel(text: "かっこいいチュートリアルメッセージ")
	label.delegate = self
		
	// popに設置したいボタン
	let skipButton = UIButton(frame: CGRect.zero)
	skipButton.setTitle(NSLocalizedString("Skip Tutorial", comment: "skip tutorial here") , for: .normal)
	label.add(skipButton)
		
	// popに設置したいボタン
	let okButton = UIButton(frame: CGRect.zero)
	okButton.setTitle(NSLocalizedString("OK, Got It!", comment: "tutorial dismissed"), for: .normal)
	label.add(okButton)
		
	self.view.addSubview(label)
}
```

### popを表示
popを表示する時は、こちらです

```swift
let target = // popを表示させたい対象（UIViewまたはUIBarButtonItemの継承クラス）
label.pop(at: target)
```

### popを非表示
popは、pop内のボタンを押された時にも消えますが、消したい時もあるかもしれないのでその時はこちらです

```swift
label.dismiss()
```

### Delegate
popが消えるとき、pop内に設置したボタンが押されたときに呼ばれるメソッドがあります

```swift
extension xxxx: MMPopLabelDelegate {

	// pop内に設置したボタンが押されたときに呼ばれる
	func didPressButton(for popLabel: MMPopLabel!, at index: Int) {
		print("popLabel push: " + String(index))
	}
    
	// popが消えるときに呼ばれる
	func dismissedPopLabel(_ popLabel: MMPopLabel!) {
		print("popLabel dismissed")
	}
}
```

# DelegateToClosure
最初の方にも述べましたが、せっかくSwiftなのだからClosure使えるようにしてみようかなと

## 方針
1. extensionで、**MMPopLabel**に**MMPopLabelDelegate**つけてしまおう:sunglasses:
2. deleagateで呼ばれる2つのメソッド用のclosureを準備
3. 用意したclosureの管理（保持）クラスが必要

こんな感じで作りました

## ClosureVersion
```swift
// MMPopLabelにMMPopLabelDelegateをつける
extension MMPopLabel: MMPopLabelDelegate {

	// 引数にpopが消えた時のclosureを入れる
	public func dismissedHandler(_ handler:@escaping (MMPopLabel) -> Void) {
		// 自分自身にdeleagateを設定
		self.delegate = self
		// closure管理クラスにclosureを渡す
		MMPopLabelDelegateSingleton.standard.dismissedHandler = handler
	}
	
	// 引数にpop内に設置したボタンが押された時のclosureを入れる
	public func didPressButtonHandler(_ handler:@escaping (MMPopLabel, Int) -> Void) {
		self.delegate = self
		MMPopLabelDelegateSingleton.standard.didPressButtonHandler = handler
	}

	// MMPopLabelDelegateメソッド
	public func dismissedPopLabel(_ popLabel: MMPopLabel!) {
		// 対応するclosureがあれば呼び出す
		guard let dismissedHandler = MMPopLabelDelegateSingleton.standard.dismissedHandler else {
			return
		}
		dismissedHandler(popLabel)
	}
	
	// MMPopLabelDelegateメソッド
	public func didPressButton(for popLabel: MMPopLabel!, at index: Int) {
		guard let didPressButtonHandler = MMPopLabelDelegateSingleton.standard.didPressButtonHandler else {
			return
		}
		didPressButtonHandler(popLabel, index)
	}
}
```

closure管理クラスのインスタンスを作っても、変数としてMMPopLabelクラスに持たせることができないため、シングルトンにしました

```swift
// closure管理クラス
class MMPopLabelDelegateSingleton {
	static let standard = MMPopLabelDelegateSingleton()
	
	var dismissedHandler:((MMPopLabel) -> Void)?
	var didPressButtonHandler:((MMPopLabel, Int) -> Void)?

}
```

管理クラスは、使う側から見える必要がないため、privateクラスやMMPopLabelのinnerクラスでも良いかもしれないです

## Usage
closureをセットするメソッド呼んでもらうだけです
`label.delegate = self`も必要無くなりますね

```swift
label.dismissedHandler { (popLabel) in
	print("popLabel dismissed")
}
		
label.didPressButtonHandler { (popLabel, index) in
	print("popLabel push: " + String(index))
}
```

# 失敗作
試行錯誤した結果、紹介した形になりました
最初は、「MMPopLabelを継承したクラスを作成して、そこでclosureも管理できたら」と考えてたのですが、classメソッドでインスタンス作成のところで詰まってしまい泣く泣く諦めました....

```swift
public class MMPopLabels: MMPopLabel, MMPopLabelDelegate {
	
	private var _dismissedHandler:((MMPopLabel) -> Void)?
	private var _didPressButtonHandler:((MMPopLabel, Int) -> Void)?

	public func dismissedHandler(_ handler:@escaping (MMPopLabel) -> Void) {
		self.delegate = self
		self._dismissedHandler = handler
	}

	public func didPressButtonHandler(_ handler:@escaping (MMPopLabel, Int) -> Void) {
		self.delegate = self
		self._didPressButtonHandler = handler
	}

	class func popLabels(text: String!) -> MMPopLabels {
		// 親クラスから子クラスに変換をかけているのがよくないのか落ちます...
		return super.init(text: text) as! MMPopLabels
	}
	
	class func popLabels(text: String!, options: MMPopLabelAnimationOptions) -> MMPopLabels {
		return super.init(text: text, options: options) as! MMPopLabels
	}

	public func setDelegate() {
		self.delegate = self
	}
	
	public func dismissedPopLabel(_ popLabel: MMPopLabel!) {
		guard let dismissedHandler = self._dismissedHandler else {
			return
		}
		dismissedHandler(popLabel)
	}
	
	public func didPressButton(for popLabel: MMPopLabel!, at index: Int) {
		guard let didPressButtonHandler = self._didPressButtonHandler else {
			return
		}
		didPressButtonHandler(popLabel, index)
	}

	required public init?(coder aDecoder: NSCoder) {
		fatalError("init(coder:) has not been implemented")
	}
}
```

#　まとめ
delegate派、closure派どちらもいると思います
自分がclosure派だっただけですw
なので、他にもclosure派の方がいましたら、その方の助けになれましたら嬉しいです:grinning: