ネットサーフィンしていてたまたま見つけた記事「iOSアプリのチュートリアルに便利なMMPopLabel」を読んでいて、SwiftだからDelegateよりClosureの方がいいなーと思い作ってみました
(delegateよりclosureの方がいいと思うのは、自分の主観なのでお許しください
MMPopLabelとは
最初に記事にもあったMMPopLabelとはなんだろうと
GitHubはこちらになります
READMEに詳細がありますが、簡単にいうとチュートリアルを作るときに吹き出し(pop)を簡単に実装できますよというライブラリです!
popの色やメッセージのfontも指定できますし、
ボタン(画像だと「Skip Tutorial」と「OK, Got It!」)も付けられます!
Usage
インスタンス作成して、ボタンが必要なら追加して...と!
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を表示する時は、こちらです
let target = // popを表示させたい対象(UIViewまたはUIBarButtonItemの継承クラス)
label.pop(at: target)
popを非表示
popは、pop内のボタンを押された時にも消えますが、消したい時もあるかもしれないのでその時はこちらです
label.dismiss()
Delegate
popが消えるとき、pop内に設置したボタンが押されたときに呼ばれるメソッドがあります
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使えるようにしてみようかなと
方針
- extensionで、MMPopLabelにMMPopLabelDelegateつけてしまおう
- deleagateで呼ばれる2つのメソッド用のclosureを準備
- 用意したclosureの管理(保持)クラスが必要
こんな感じで作りました
ClosureVersion
// 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クラスに持たせることができないため、シングルトンにしました
// closure管理クラス
class MMPopLabelDelegateSingleton {
static let standard = MMPopLabelDelegateSingleton()
var dismissedHandler:((MMPopLabel) -> Void)?
var didPressButtonHandler:((MMPopLabel, Int) -> Void)?
}
管理クラスは、使う側から見える必要がないため、privateクラスやMMPopLabelのinnerクラスでも良いかもしれないです
Usage
closureをセットするメソッド呼んでもらうだけです
label.delegate = self
も必要無くなりますね
label.dismissedHandler { (popLabel) in
print("popLabel dismissed")
}
label.didPressButtonHandler { (popLabel, index) in
print("popLabel push: " + String(index))
}
失敗作
試行錯誤した結果、紹介した形になりました
最初は、「MMPopLabelを継承したクラスを作成して、そこでclosureも管理できたら」と考えてたのですが、classメソッドでインスタンス作成のところで詰まってしまい泣く泣く諦めました....
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派の方がいましたら、その方の助けになれましたら嬉しいです