LoginSignup
7
10

More than 5 years have passed since last update.

UIBarButtonItemでLongPressGestureを使えるようにする

Posted at

なんでUIButtonではできて、UIBarButtonItemだとできない?

ジェスチャーの登録はUIViewの拡張クラスでやってるぽい

extension UIView {

    @available(iOS 3.2, *)
    public var gestureRecognizers: [UIGestureRecognizer]?

    @available(iOS 3.2, *)
    public func addGestureRecognizer(gestureRecognizer: UIGestureRecognizer)
    @available(iOS 3.2, *)
    public func removeGestureRecognizer(gestureRecognizer: UIGestureRecognizer)

    // called when the recognizer attempts to transition out of UIGestureRecognizerStatePossible if a touch hit-tested to this view will be cancelled as a result of gesture recognition
    // returns YES by default. return NO to cause the gesture recognizer to transition to UIGestureRecognizerStateFailed
    // subclasses may override to prevent recognition of particular gestures. for example, UISlider prevents swipes parallel to the slider that start in the thumb
    @available(iOS 6.0, *)
    public func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool
}

UIButtonの継承関係

UIView -> UIControl -> UIButton からなってる
正確にはもっと継承してるんだけど割愛

public class UIButton : UIControl, NSCoding
public class UIControl : UIView

UIBarButtonItemの継承関係

NSObject -> UIBarItem -> UIBarButtonItem からなってる

public class UIBarButtonItem : UIBarItem, NSCoding
public class UIBarItem : NSObject, NSCoding, UIAppearance

つまり、UIBarButtonItem はUIViewを継承してないのでジェスチャーの登録が使えないぽい。

UIBarButtonItemと言えども、ボタンなんだからどうにかできるっしょ

Propertyの確認

public var style: UIBarButtonItemStyle // default is UIBarButtonItemStylePlain
public var width: CGFloat // default is 0.0
public var possibleTitles: Set<String>? // default is nil
public var customView: UIView? // default is nil
public var action: Selector // default is NULL
weak public var target: AnyObject? // default is nil

このcutomViewにジェスチャー登録すれば済むっしょと思いきや、デフォルトはnilのためジェスチャーが認識されない模様。

じゃあ、親のUIBarItemのPropertyは?

public var enabled: Bool // default is YES
public var title: String? // default is nil
public var image: UIImage? // default is nil
@available(iOS 5.0, *)
public var landscapeImagePhone: UIImage? // default is nil
public var imageInsets: UIEdgeInsets // default is UIEdgeInsetsZero
@available(iOS 5.0, *)
public var landscapeImagePhoneInsets: UIEdgeInsets // default is UIEdgeInsetsZero. These insets apply only when the landscapeImagePhone property is set.
    public var tag: Int // default is 0

うん、ないね

諦めないお

ぶっちゃけUIViewがないわけないと思うので、調べてみると公開してないUIViewありました。

取得方法

UIBarButtonItemに対してvalueForKeyを行う。

  • Swift
let buttonView = self.valueForKey("view")

UIView系のオブジェクトからプライベートなViewを取得する際は、subviewsで順番に見てくやり方もあるのですが、UIBarButtonItemはUIViewを継承していないため、valueForKeyで取得します。

そしてこのViewに対してジェスチャーを登録すればいけそうです!

実装内容

//
//  TSKLongPressBarButtonItem.swift
//  ToolBar
//
//  Created by Toshiki Chiba on 2015/11/14.
//  Copyright © 2015年 Toshiki Chiba. All rights reserved.
//

import UIKit

protocol TSKLongPressBarButtonDelegate : class {
    func handleLongPressGesture(sender: AnyObject)
}

class TSKLongPressBarButtonItem: UIBarButtonItem, UIGestureRecognizerDelegate {
    private var recognizerState: UIGestureRecognizerState = .Possible // Default state
    weak var delegate: TSKLongPressBarButtonDelegate?

    func setLongTapGesture(allowableMovement: CGFloat, minimumPressDuration: CFTimeInterval,
        numberOfTapsRequired: Int, numberOfTouchesRequired: Int, state: UIGestureRecognizerState,
        buttonDelegate: TSKLongPressBarButtonDelegate?) {
        self.removeLongGesture()
        if let buttonView = self.valueForKey("view") {
            let longPressGesture = UILongPressGestureRecognizer(target: self, action: "p_handleLongPressGesture:")
            longPressGesture.allowableMovement = allowableMovement
            longPressGesture.minimumPressDuration = minimumPressDuration
            longPressGesture.numberOfTapsRequired = numberOfTapsRequired
            longPressGesture.numberOfTouchesRequired = numberOfTouchesRequired
            longPressGesture.delegate = self
            buttonView.addGestureRecognizer(longPressGesture)
        }
        self.recognizerState = state
        self.delegate = buttonDelegate
    }

    func removeLongGesture() {
        // TODO: Refactor
        if let buttonView = self.valueForKey("view") {
            if let recognizerList: [UIGestureRecognizer] = buttonView.gestureRecognizers {
                for recognizer in recognizerList {
                    if recognizer.isKindOfClass(UILongPressGestureRecognizer) {
                        buttonView.removeGestureRecognizer(recognizer)
                    }
                }
            }
        }
    }

    func p_handleLongPressGesture(sender: UIGestureRecognizer) {
        if sender.state == self.recognizerState {
            self.delegate?.handleLongPressGesture(self)
        }
    }

    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

実行してみた


うまくいった!

まとめ

正式に用意されてる方法ではないと思うので使う際は注意が必要かも。
ジェスチャーの細かい設定も簡単にしたいなーと思ったのでいい感じのUIBarButtonカスタムクラスを作りました。

Sample code

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