39

問題

私のiPadアプリでは、押し続けるイベントの後でのみ、ボタンバーアイテムにポップオーバーをアタッチできません。しかし、これは元に/やり直しの標準のようです。他のアプリはこれをどのように行いますか?

バックグラウンド

UIKit(iPad)アプリのツールバーに元に戻すボタン(UIBarButtonSystemItemUndo)があります。元に戻すボタンを押すと、元に戻すアクションが実行され、正しく実行されます。

ただし、iPadでの元に戻す/やり直しの「標準のUE規則」では、元に戻すを押すと元になりが実行されますが、ボタンを押し続けると、コントローラーが閉じられるまでユーザーが「元に戻す」または「やり直し」のいずれかを選択したポップオーバーコントローラーが表示されます。

ポップオーバーコントローラーを接続する通常の方法は、presentPopoverFromBarButtonItem:を使用することです。これは、簡単に構成できます。これを長押しした後にのみ表示するには、次のスニペットのように「長押し」ジェスチャイベントに応答するビューを設定する必要があります。

UILongPressGestureRecognizer *longPressOnUndoGesture = [[UILongPressGestureRecognizer alloc] 
       initWithTarget:self 
               action:@selector(handleLongPressOnUndoGesture:)];
//Broken because there is no customView in a UIBarButtonSystemItemUndo item
[self.undoButtonItem.customView addGestureRecognizer:longPressOnUndoGesture];
[longPressOnUndoGesture release];

これにより、ビューを長押しした後、メソッドhandleLongPressOnUndoGesture:が呼び出され、このメソッド内で、元に戻す/やり直しのポップオーバーを構成して表示します。ここまでは順調ですね。

これに伴う問題は、アタッチするビューがないことです。self.undoButtonItemはUIButtonBarItemであり、ビューではありません。

可能な解決策

1)[理想]ボタンバーアイテムにジェスチャレコグナイザーを取り付けます。ビューにジェスチャレコグナイザーをアタッチすることは可能ですが、UIButtonBarItemはビューではありません。.customViewのプロパティはありますが、buttonbaritemが標準のシステムタイプ(この場合はそうです)の場合、そのプロパティはnilです。

2)別のビューを使用します。UIToolbarを使用することもできますが、そもそも可能であれば、奇妙なヒットテストが必要であり、万能のハックになるでしょう。私が考えることができる他の使用する代替ビューはありません。

3)customViewプロパティを使用します。UIBarButtonSystemItemUndoのような標準タイプにはcustomViewがありません(nilです)。customViewを設定すると、必要な標準コンテンツが消去されます。これは、可能であれば、UIBarButtonSystemItemUndoのすべての外観と関数を再実装することになります。

質問

この「ボタン」にジェスチャレコグナイザーをアタッチするにはどうすればよいですか?具体的には、iPadアプリに標準の長押しして表示をやり直すポップオーバーを実装するにはどうすればよいですか?

アイデア?特に誰かが実際にアプリでこれを動作させていて(私はあなたのことを考えています、オムニ)、共有したい場合は特にありがとうございます...

4

15 に答える 15

25

注: iOS 11 以降、これは機能しなくなりました

ツールバーのサブビュー リストで UIBarButtonItem のビューを見つけようとする混乱の代わりに、アイテムがツールバーに追加されたら、これを試すこともできます。

[barButtonItem valueForKey:@"view"];

これは、Key-Value Coding フレームワークを使用して、作成したビューを保持する UIBarButtonItem のプライベート _view 変数にアクセスします。

確かに、これがAppleのプライベートAPIの観点からどこに当てはまるかはわかりません(これは、パブリッククラスのプライベート変数にアクセスするために使用されるパブリックメソッドです-プライベートフレームワークにアクセスして、派手なApple専用の効果などを作成するのとは異なります)。それは機能し、かなり痛みを伴いません。

于 2012-02-21T02:09:36.390 に答える
13

これは古い質問ですが、今でも Google 検索で出てきます。他のすべての回答は非常に複雑です。

押されたときに action:forEvent: メソッドを呼び出す、ボタンバー項目を持つボタンバーがあります。

そのメソッドに、次の行を追加します。

bool longpress=NO;
UITouch *touch=[[[event allTouches] allObjects] objectAtIndex:0];
if(touch.tapCount==0) longpress=YES;

シングルタップの場合、tapCount は 1 です。ダブルタップの場合、tapCount は 2 です。長押しの場合、tapCount はゼロです。

于 2014-02-24T23:14:51.263 に答える
9

オプション 1 は確かに可能です。残念ながら、UIBarButtonItem が作成する UIView を見つけるのは大変なことです。これが私がそれを見つけた方法です:

[[[myToolbar subviews] objectAtIndex:[[myToolbar items] indexOfObject:myBarButton]] addGestureRecognizer:myGesture];

これは本来よりも難しいことですが、ボタンのルック アンド フィールをいじるのを防ぐために明確に設計されています。

固定/フレキシブル スペースはビューとしてカウントされないことに注意してください!

スペースを処理するには、それらを検出する何らかの方法が必要ですが、悲しいことに、SDK にはこれを行う簡単な方法がありません。解決策はいくつかありますが、その一部を次に示します。

1) UIBarButtonItem のタグ値を、ツールバーの左から右へのインデックスに設定します。これは、IMO の同期を維持するためにあまりにも多くの手作業を必要とします。

2) スペースの有効なプロパティを NO に設定します。次に、このコード スニペットを使用してタグ値を設定します。

NSUInteger index = 0;
for (UIBarButtonItem *anItem in [myToolbar items]) {
    if (anItem.enabled) {
        // For enabled items set a tag.
        anItem.tag = index;
        index ++;
    }
}

// Tag is now equal to subview index.
[[[myToolbar subviews] objectAtIndex:myButton.tag] addGestureRecognizer:myGesture];

もちろん、他の理由でボタンを無効にした場合、これには潜在的な落とし穴があります。

3) ツールバーを手動でコーディングし、自分でインデックスを処理します。UIBarButtonItem を自分で構築するので、サブビューでどのインデックスになるかを事前に知ることができます。必要に応じて、後で使用するために事前に UIView を収集するために、このアイデアを拡張できます。

于 2010-06-19T03:16:20.760 に答える
7

サブビューを探し回る代わりに、自分でボタンを作成し、カスタム ビューでボタン バー項目を追加できます。次に、GR をカスタム ボタンに接続します。

于 2010-07-26T19:00:04.990 に答える
5

この質問は1年以上前のものですが、これはまだかなり厄介な問題です. 私は Apple ( rdar://9982911 ) にバグレポートを提出しました。同じように感じている他の人には、バグレポートを複製することをお勧めします。

于 2011-08-19T12:09:23.450 に答える
3

これを簡単に行うこともできます...

let longPress = UILongPressGestureRecognizer(target: self, action: "longPress:")
navigationController?.toolbar.addGestureRecognizer(longPress)

func longPress(sender: UILongPressGestureRecognizer) {
let location = sender.locationInView(navigationController?.toolbar)
println(location)
}
于 2015-02-03T12:05:42.727 に答える
2

ベンが提案したものと同様のことを試しました。UIButton を使用してカスタム ビューを作成し、それを UIBarButtonItem の customView として使用しました。このアプローチには、私が気に入らなかった点がいくつかありました。

  • UIToolBar の親指のように突き出ないように、ボタンのスタイルを設定する必要がありました
  • UILongPressGestureRecognizer を使用すると、「Touch up Inside」のクリック イベントを取得できなかったようです (これは、私のプログラミング エラーである可能性が最も高いです)。

代わりに、せいぜいハック的なものに落ち着きましたが、それは私にとってはうまくいきます。私は XCode 4.2 を使用しており、以下のコードでは ARC を使用しています。CustomBarButtonItemView という新しい UIViewController サブクラスを作成しました。CustomBarButtonItemView.xib ファイルで、UIToolBar を作成し、単一の UIBarButtonItem をツールバーに追加しました。次に、ツールバーをほぼボタンの幅に縮小しました。次に、File's Owner ビュー プロパティを UIToolBar に接続しました。

CustomBarButtonViewController の Interface Builder ビュー

次に、ViewController の viewDidLoad: メッセージで、2 つの UIGestureRecognizer を作成しました。1 つ目はクリック アンド ホールド用の UILongPressGestureRecognizer で、2 つ目は UITapGestureRecognizer です。ビューで UIBarButtonItem のアクションを適切に取得できないように見えるので、UITapGestureRecognizer で偽造します。UIBarButtonItem はクリックされているように表示され、UIBarButtonItem のアクションとターゲットが設定されているかのように、UITapGestureRecognizer がアクションを処理します。

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib

    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGestured)];

    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(buttonPressed:)];

    CustomBarButtomItemView* customBarButtonViewController = [[CustomBarButtomItemView alloc] initWithNibName:@"CustomBarButtonItemView" bundle:nil];

    self.barButtonItem.customView = customBarButtonViewController.view;

    longPress.minimumPressDuration = 1.0;

    [self.barButtonItem.customView addGestureRecognizer:longPress];
    [self.barButtonItem.customView addGestureRecognizer:singleTap];        

}

-(IBAction)buttonPressed:(id)sender{
    NSLog(@"Button Pressed");
};
-(void)longPressGestured{
    NSLog(@"Long Press Gestured");
}

ViewController の barButtonItem (xib ファイル経由で接続) でシングル クリックが発生すると、タップ ジェスチャが buttonPressed: メッセージを呼び出します。ボタンが押されている場合、longPressGestured が発生します。

UIBarButton の外観を変更するには、CustomBarButtonItemView のプロパティを作成して、カスタム BarButton へのアクセスを許可し、それを ViewController クラスに格納することをお勧めします。longPressGestured メッセージが送信されると、ボタンのシステム アイコンを変更できます。

私が見つけた落とし穴の 1 つは、customview プロパティがビューをそのまま取得することです。たとえば、CustomBarButtonItemView.xib からカスタム UIBarButtonitem を変更してラベルを@"really long string"に変更すると、ボタン自体のサイズが変更されますが、表示されているボタンの左端のみが UIGestuerRecognizer インスタンスによって監視されているビューに表示されます。

于 2011-11-04T19:55:00.483 に答える
2

長押しジェスチャを追加したボタンのタイトルを変更するまで、@voi1dのソリューションを試してみました。タイトルを変更すると、元のボタンを置き換える新しい UIView が作成されるように見えるため、ボタンに変更が加えられるとすぐに、追加されたジェスチャが機能しなくなります (これは私のアプリで頻繁に発生します)。

私の解決策は、UIToolbar をサブクラス化し、addSubview:メソッドをオーバーライドすることでした。また、ジェスチャのターゲットへのポインターを保持するプロパティも作成しました。正確なコードは次のとおりです。

- (void)addSubview:(UIView *)view {
    // This method is overridden in order to add a long-press gesture recognizer
    // to a UIBarButtonItem. Apple makes this way too difficult, but I am clever!
    [super addSubview:view];
    // NOTE - this depends the button of interest being 150 pixels across (I know...)
    if (view.frame.size.width == 150) {
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:targetOfGestureRecognizers 
                                                                                                action:@selector(showChapterMenu:)];
        [view addGestureRecognizer:longPress];
    }
}

私の特定の状況では、関心のあるボタンは幅が 150 ピクセルであり (それが唯一のボタンです)、それが私が使用するテストです。おそらく最も安全なテストではありませんが、私にとってはうまくいきます。明らかに、独自のテストを考え出し、独自のジェスチャーとセレクターを提供する必要があります。

この方法の利点は、UIBarButtonItem が変更される (したがって、新しいビューが作成される) たびに、カスタム ジェスチャがアタッチされるため、常に機能することです。

于 2012-01-25T13:16:21.990 に答える
2

これが古いことは知っていますが、受け入れられる解決策を見つけようとして、一晩中壁に頭をぶつけました。ボタンの色合い、色合いの無効化などの組み込み機能をすべて削除し、UIBarButtonItems がヒット ボックスをかなり広げている間、長押しするとそのような小さなヒット ボックスが表示されるため、customView プロパティを使用しませんでした。方法。私はこのソリューションを思いつきました。これは本当にうまく機能し、実装するのはほんの少しの苦痛です。

私の場合、バーの最初の 2 つのボタンを長押しすると同じ場所に移動するため、特定の X ポイントの前にプレスが発生したことを検出する必要がありました。長押しジェスチャ認識機能を UIToolbar に追加し (UINavigationBar に追加しても機能します)、2 番目のボタンの直後に 1 ピクセル幅の追加の UIBarButtonItem を追加しました。ビューが読み込まれると、1 ピクセル幅の UIView をその UIBarButtonItem に customView として追加します。これで、長押しが発生したポイントをテストし、X が customview のフレームの X より小さいかどうかを確認できます。ここに小さなSwift 3コードがあります

@IBOutlet var thinSpacer: UIBarButtonItem!

func viewDidLoad() {
    ...
    let thinView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 22))
    self.thinSpacer.customView = thinView

    let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(gestureRecognizer:)))
    self.navigationController?.toolbar.addGestureRecognizer(longPress)
    ...
}

func longPressed(gestureRecognizer: UIGestureRecognizer) {
    guard gestureRecognizer.state == .began, let spacer = self.thinSpacer.customView else { return }
    let point = gestureRecognizer.location(ofTouch: 0, in: gestureRecognizer.view)
    if point.x < spacer.frame.origin.x {
        print("Long Press Success!")
    } else {
        print("Long Pressed Somewhere Else")
    }
}

間違いなく理想的ではありませんが、私のユースケースでは十分簡単です。特定の場所で特定のボタンを長押しする必要がある場合は、少し面倒ですが、長押しを検出するために必要なボタンを薄いスペーサーで囲み、ポイントの X がこれらのスペーサーの両方の間。

于 2017-04-20T08:55:35.827 に答える
0

これは、私が思いついた最も Swift にやさしく、ハックの少ない方法です。iOS 12 で動作します。

スイフト5

var longPressTimer: Timer?

let button = UIButton()
button.addTarget(self, action: #selector(touchDown), for: .touchDown)
button.addTarget(self, action: #selector(touchUp), for: .touchUpInside)
button.addTarget(self, action: #selector(touchCancel), for: .touchCancel)
let undoBarButton = UIBarButtonItem(customView: button)

@objc func touchDown() {
    longPressTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(longPressed), userInfo: nil, repeats: false)
}

@objc func touchUp() {
    if longPressTimer?.isValid == false { return } // Long press already activated
    longPressTimer?.invalidate()
    longPressTimer = nil
    // Do tap action
}

@objc func touchCancel() {
    longPressTimer?.invalidate()
    longPressTimer = nil
}

@objc func longPressed() {
    // Do long press action
}
于 2018-10-21T04:19:51.930 に答える