73

したくないとかUIButton、そういうの。UIControl直接サブクラス化し、独自の非常に特別なコントロールを作成したいと考えています。

しかし、何らかの理由で、私がオーバーライドしたメソッドはどれも呼び出されません。ターゲット アクションが機能し、ターゲットは適切なアクション メッセージを受け取ります。ただし、UIControlサブクラス内でタッチ座標をキャッチする必要があり、そうする唯一の方法はこれらの人をオーバーライドすることのようです:

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    NSLog(@"begin touch track");
    return YES;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    NSLog(@"continue touch track");
    return YES;
}

が からのUIControl指定初期化子でインスタンス化されていても、それらは呼び出されません。UIViewinitWithFrame:

私が見つけることができるすべての例は、サブクラス化のベースとして常にUIButtonorを使用しますが、それが私が望むもののソースであるため、もっと近づきたいです: 高速で遅延のないタッチ座標。UISliderUIControl

4

7 に答える 7

83

私はこの質問が古くからあることを知っていますが、私は同じ問題を抱えていたので、2セントを与えるべきだと思いました.

コントロールにサブビューがある場合beginTrackingWithTouchtouchesBegan、 などは呼び出されない可能性があります。これらのサブビューがタッチ イベントを飲み込んでいるからです。

これらのサブビューでタッチを処理したくない場合は、 に設定userInteractionEnabledNOて、サブビューが単純にイベントを渡すようにすることができます。touchesBegan/touchesEnded次に、そこですべてのタッチをオーバーライドして管理できます。

お役に立てれば。

于 2011-09-25T01:53:57.383 に答える
32

迅速

これらは をサブクラス化する複数の方法UIControlです。親ビューがタッチ イベントに反応するか、コントロールから他のデータを取得する必要がある場合、これは通常、(1) ターゲットまたは (2) オーバーライドされたタッチ イベントを含むデリゲート パターンを使用して行われます。完全を期すために、(3)ジェスチャ認識エンジンで同じことを行う方法も示します。これらの各メソッドは、次のアニメーションのように動作します。

ここに画像の説明を入力

次のいずれかの方法を選択するだけです。


方法 1: ターゲットを追加する

A UIControlsubclass has support for targets already built in. 多くのデータを親に渡す必要がない場合は、おそらくこれが必要なメソッドです。

MyCustomControl.swift

import UIKit
class MyCustomControl: UIControl {
    // You don't need to do anything special in the control for targets to work.
}

ViewController.swift

import UIKit
class ViewController: UIViewController {

    @IBOutlet weak var myCustomControl: MyCustomControl!
    @IBOutlet weak var trackingBeganLabel: UILabel!
    @IBOutlet weak var trackingEndedLabel: UILabel!
    @IBOutlet weak var xLabel: UILabel!
    @IBOutlet weak var yLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Add the targets
        // Whenever the given even occurs, the action method will be called
        myCustomControl.addTarget(self, action: #selector(touchedDown), forControlEvents: UIControlEvents.TouchDown)
        myCustomControl.addTarget(self, action: #selector(didDragInsideControl(_:withEvent:)),
                              forControlEvents: UIControlEvents.TouchDragInside)
        myCustomControl.addTarget(self, action: #selector(touchedUpInside), forControlEvents: UIControlEvents.TouchUpInside)
    }

    // MARK: - target action methods

    func touchedDown() {
        trackingBeganLabel.text = "Tracking began"
    }

    func touchedUpInside() {
        trackingEndedLabel.text = "Tracking ended"
    }

    func didDragInsideControl(control: MyCustomControl, withEvent event: UIEvent) {

        if let touch = event.touchesForView(control)?.first {
            let location = touch.locationInView(control)
            xLabel.text = "x: \(location.x)"
            yLabel.text = "y: \(location.y)"
        }
    }
}

ノート

  • アクション メソッド名については、特別なことは何もありません。私は彼らを何と呼んでもよかった。ターゲットを追加したときとまったく同じように、メソッド名のスペルに注意する必要があります。そうしないと、クラッシュします。
  • の 2 つのコロンdidDragInsideControl:withEvent:は、2 つのパラメーターがメソッドに渡されることを意味しますdidDragInsideControl。コロンを追加するのを忘れた場合、または正しい数のパラメーターがない場合、クラッシュが発生します。
  • イベントを手伝ってくれたこの回答に感謝しTouchDragInsideます。

他のデータを渡す

カスタムコントロールに値がある場合

class MyCustomControl: UIControl {
    var someValue = "hello"
}

ターゲット アクション メソッドでアクセスしたい場合は、コントロールへの参照を渡すことができます。ターゲットを設定するときは、アクション メソッド名の後にコロンを追加します。例えば:

myCustomControl.addTarget(self, action: #selector(touchedDown), forControlEvents: UIControlEvents.TouchDown) 

touchedDown:(コロン付き) であり、(コロンなし) ではないことに注意してくださいtouchedDown。コロンは、パラメーターがアクション メソッドに渡されることを意味します。アクション メソッドで、パラメーターがUIControlサブクラスへの参照であることを指定します。その参照を使用して、コントロールからデータを取得できます。

func touchedDown(control: MyCustomControl) {
    trackingBeganLabel.text = "Tracking began"

    // now you have access to the public properties and methods of your control
    print(control.someValue)
}

方法 2: パターンをデリゲートし、タッチ イベントをオーバーライドする

サブクラス化UIControlにより、次のメソッドにアクセスできます。

  • beginTrackingWithTouch指がコントロールの境界内に最初に触れたときに呼び出されます。
  • continueTrackingWithTouch指がコントロールを横切って、さらにはコントロールの境界の外までスライドすると、繰り返し呼び出されます。
  • endTrackingWithTouch指が画面から離れたときに呼び出されます。

タッチ イベントを特別に制御する必要がある場合や、親とのデータ通信が多い場合は、ターゲットを追加するよりもこの方法の方がうまくいく可能性があります。

方法は次のとおりです。

MyCustomControl.swift

import UIKit

// These are out self-defined rules for how we will communicate with other classes
protocol ViewControllerCommunicationDelegate: class {
    func myTrackingBegan()
    func myTrackingContinuing(location: CGPoint)
    func myTrackingEnded()
}

class MyCustomControl: UIControl {

    // whichever class wants to be notified of the touch events must set the delegate to itself
    weak var delegate: ViewControllerCommunicationDelegate?

    override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {

        // notify the delegate (i.e. the view controller)
        delegate?.myTrackingBegan()

        // returning true means that future events (like continueTrackingWithTouch and endTrackingWithTouch) will continue to be fired
        return true
    }

    override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {

        // get the touch location in our custom control's own coordinate system
        let point = touch.locationInView(self)

        // Update the delegate (i.e. the view controller) with the new coordinate point
        delegate?.myTrackingContinuing(point)

        // returning true means that future events will continue to be fired
        return true
    }

    override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) {

        // notify the delegate (i.e. the view controller)
        delegate?.myTrackingEnded()
    }
}

ViewController.swift

これは、ビュー コントローラーがデリゲートとして設定され、カスタム コントロールからのタッチ イベントに応答する方法です。

import UIKit
class ViewController: UIViewController, ViewControllerCommunicationDelegate {

    @IBOutlet weak var myCustomControl: MyCustomControl!
    @IBOutlet weak var trackingBeganLabel: UILabel!
    @IBOutlet weak var trackingEndedLabel: UILabel!
    @IBOutlet weak var xLabel: UILabel!
    @IBOutlet weak var yLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        myCustomControl.delegate = self
    }

    func myTrackingBegan() {
        trackingBeganLabel.text = "Tracking began"
    }

    func myTrackingContinuing(location: CGPoint) {
        xLabel.text = "x: \(location.x)"
        yLabel.text = "y: \(location.y)"
    }

    func myTrackingEnded() {
        trackingEndedLabel.text = "Tracking ended"
    }
}

ノート

  • デリゲート パターンの詳細については、この回答を参照してください。
  • これらのメソッドがカスタム コントロール自体の中でのみ使用されている場合は、これらのメソッドでデリゲートを使用する必要はありません。printイベントがどのように呼び出されているかを示すステートメントを追加することもできます。その場合、コードは次のように簡略化されます。

    import UIKit
    class MyCustomControl: UIControl {
    
        override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
            print("Began tracking")
            return true
        }
    
        override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
            let point = touch.locationInView(self)
            print("x: \(point.x), y: \(point.y)")
            return true
        }
    
        override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) {
            print("Ended tracking")
        }
    }
    

方法 3: Gesture Recognizer を使用する

ジェスチャ レコグナイザーの追加は、任意のビューで行うことができ、UIControl. 上の例と同様の結果を得るには、UIPanGestureRecognizer. 次に、イベントが発生したときのさまざまな状態をテストすることで、何が起こっているかを判断できます。

MyCustomControl.swift

import UIKit
class MyCustomControl: UIControl {
    // nothing special is required in the control to make it work
}

ViewController.swift

import UIKit
class ViewController: UIViewController {

    @IBOutlet weak var myCustomControl: MyCustomControl!
    @IBOutlet weak var trackingBeganLabel: UILabel!
    @IBOutlet weak var trackingEndedLabel: UILabel!
    @IBOutlet weak var xLabel: UILabel!
    @IBOutlet weak var yLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        // add gesture recognizer
        let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(gestureRecognized(_:)))
        myCustomControl.addGestureRecognizer(gestureRecognizer)
    }

    // gesture recognizer action method
    func gestureRecognized(gesture: UIPanGestureRecognizer) {

        if gesture.state == UIGestureRecognizerState.Began {

            trackingBeganLabel.text = "Tracking began"

        } else if gesture.state == UIGestureRecognizerState.Changed {

            let location = gesture.locationInView(myCustomControl)

            xLabel.text = "x: \(location.x)"
            yLabel.text = "y: \(location.y)"

        } else if gesture.state == UIGestureRecognizerState.Ended {
            trackingEndedLabel.text = "Tracking ended"
        }
    }
}

ノート

  • のアクション メソッド名の後にコロンを追加することを忘れないでくださいaction: "gestureRecognized:"。コロンは、パラメーターが渡されることを意味します。
  • コントロールからデータを取得する必要がある場合は、上記の方法 2 のようにデリゲート パターンを実装できます。
于 2016-01-13T10:35:01.827 に答える
20

私はこの問題の解決策を長い間懸命に探してきましたが、解決策はないと思います。begintrackingWithTouch:withEvent:ただし、ドキュメントを詳しく調べると、誤解である可能性があり、呼び出されることになっていると思いますcontinueTrackingWithTouch:withEvent:...

UIControlドキュメンテーションは言う:

UIControl 次の 2 つの基本的な理由から、サブクラスを拡張する必要がある場合があります。

特定のイベントのターゲットへのアクション メッセージのディスパッチを監視または変更するには これを行うには、 をオーバーライド sendAction:to:forEvent:し、渡されたセレクター、ターゲット オブジェクト、または「メモ」ビット マスクを評価し、必要に応じて続行します。

カスタム トラッキング動作を提供するには (ハイライトの外観を変更するなど) 、 次のメソッドの 1 つまたはすべてをオーバーライドし beginTrackingWithTouch:withEvent:ます 。continueTrackingWithTouch:withEvent:endTrackingWithTouch:withEvent:

これの重要な部分は、私の見解ではあまり明確ではありませんが、サブクラスを拡張したいかもしれないと言っているということです-直接UIControl拡張したいかもしれません. UIControlとがタッチに応答して呼び出されることは想定されておらず、サブクラスが追跡を監視できるように、直接のサブクラスがそれらを呼び出すことになっている可能beginTrackingWithTouch:withEvent:性があります。continuetrackingWithTouch:withEvent:UIControl

したがって、これに対する私の解決策は、次のようにオーバーライドtouchesBegan:withEvent:touchesMoved:withEvent:てそこから呼び出すことです。このコントロールではマルチタッチが有効になっておらず、終了したタッチとキャンセルされたタッチのイベントは気にしないことに注意してください。

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    [super touchesBegan:touches withEvent:event];
    // Get the only touch (multipleTouchEnabled is NO)
    UITouch* touch = [touches anyObject];
    // Track the touch
    [self beginTrackingWithTouch:touch withEvent:event];
}

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
    [super touchesMoved:touches withEvent:event];
    // Get the only touch (multipleTouchEnabled is NO)
    UITouch* touch = [touches anyObject];
    // Track the touch
    [self continueTrackingWithTouch:touch withEvent:event];
}

UIControlEvent*を使用してコントロールに関連するメッセージも送信する必要があることに注意してくださいsendActionsForControlEvents:-これらはスーパーメソッドから呼び出される可能性がありますが、私はテストしていません。

于 2010-10-13T12:14:54.097 に答える
10

touchesBegan / touchesEnded/touchesMovedに[super]呼び出しを追加するのを忘れたと思います。のような方法

(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event    
(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event

touchesBegin / touchesEndedをオーバーライドすると機能しなくなります:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Touches Began");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Touches Moved");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Touches Ended");
}

だが!メソッドが次のようになる場合は、すべて正常に機能します。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   [super touchesBegan:touches withEvent:event];
   NSLog(@"Touches Began");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
   [super touchesMoved:touches withEvent:event];
     NSLog(@"Touches Moved");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
   [super touchesEnded:touches withEvent:event];
   NSLog(@"Touches Ended");
}
于 2009-11-18T15:03:35.010 に答える
10

私がよく使用する最も簡単な方法は、UIControl を拡張することですが、継承された addTarget メソッドを利用して、さまざまなイベントのコールバックを受け取ります。重要なのは、実際のイベントに関する詳細情報 (イベントが発生した場所など) を見つけることができるように、送信者とイベントの両方をリッスンすることです。

したがって、単純に UIControl をサブクラス化し、次に init メソッドで (ペン先を使用している場合は initWithCoder もセットアップされていることを確認してください)、次を追加します。

[self addTarget:self action:@selector(buttonPressed:forEvent:) forControlEvents:UIControlEventTouchUpInside];

もちろん、UIControlEventAllTouchEvents を含む標準コントロール イベントを選択できます。セレクターが 2 つのオブジェクトを渡すことに注意してください。1つ目はコントロールです。2つ目はイベント情報です。これは、タッチ イベントを使用して、ユーザーが左右に押したかどうかに応じてボタンを切り替える例です。

- (IBAction)buttonPressed:(id)sender forEvent:(UIEvent *)event
{
    if (sender == self.someControl)
    {        
        UITouch* touch = [[event allTouches] anyObject];
        CGPoint p = [touch locationInView:self.someControl];
        if (p.x < self. someControl.frame.size.width / 2.0)
        {
            // left side touch
        }
        else
        {
            // right side touch
        }
    }
}

確かに、これは非常に単純なコントロール用であり、十分な機能が得られない点に到達する可能性がありますが、これまでのカスタム コントロールのすべての目的で機能しており、通常は同じことを気にするので非常に使いやすいです。 UIControl が既にサポートしているコントロール イベント (タッチアップ、ドラッグなど)

カスタム コントロールのコード サンプルは次のとおりです: カスタム UISwitch (注: これは buttonPressed:forEvent: セレクターに登録されませんが、上記のコードから理解できます)

于 2009-11-17T18:44:34.370 に答える
4

UIControl がbeginTrackingWithTouchおよびに応答しないという問題がありましたcontinueTrackingWithTouch

私の問題はinitWithFrame:CGRectMake()、フレームを小さくして(反応する領域)、それが機能するポイントスポットが2、3しかないことでした。フレームをコントロールと同じサイズにしてから、コントロールのどこかを押すと、いつでも応答しました。

于 2013-05-16T14:24:34.290 に答える
0

オブジェクト C

2014 年の関連記事https://www.objc.io/issues/13-architecture/behaviors/を見つけました。

興味深いのは、IB を利用して、指定されたオブジェクト内にイベント処理ロジックをカプセル化するというアプローチ (ビヘイビアーと呼ばれます) であり、ビュー/viewController からロジックを削除して軽量化することです。

于 2016-05-07T11:05:12.543 に答える