LoginSignup
4
6

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-06-28

ネットサーフィンしていてたまたま見つけた記事「iOSアプリのチュートリアルに便利なMMPopLabel」を読んでいて、SwiftだからDelegateよりClosureの方がいいなーと思い作ってみました

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

MMPopLabelとは

最初に記事にもあったMMPopLabelとはなんだろうと
GitHubはこちらになります
READMEに詳細がありますが、簡単にいうとチュートリアルを作るときに吹き出し(pop)を簡単に実装できますよというライブラリです!

MMPopLabel-1.png

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使えるようにしてみようかなと

方針

  1. extensionで、MMPopLabelMMPopLabelDelegateつけてしまおう:sunglasses:
  2. deleagateで呼ばれる2つのメソッド用のclosureを準備
  3. 用意した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派の方がいましたら、その方の助けになれましたら嬉しいです:grinning:

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6