如何正确地inheritanceUIControl?

我不想要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使用来自UIView的指定初始值设定项(initWithFrame:)初始化,它们也不会被调用。

我可以find的所有示例都使用UIButton或UISlider作为子类的基础,但是我想更接近于UIControl,因为这是我想要的源代码:快速和未延迟的触摸坐标。

我知道这个问题是古老的,但是我有同样的问题,我想我应该给我2美分。

如果你的控件有任何子视图, beginTrackingWithTouchtouchesBegan等可能不会被调用,因为这些子视图正在吞咽触摸事件。

如果你不想要这些子视图来处理触摸,你可以设置userInteractionEnabledNO ,所以子视图只是传递事件。 然后你可以覆盖touchesBegan/touchesEnded并pipe理你所有的触摸。

希望这可以帮助。

为了解决这个问题,我长期以来一直在寻找解决办法,我不认为有这样的解决办法。 然而,仔细检查文档,我认为这可能是一个误解, begintrackingWithTouch:withEvent:continueTrackingWithTouch:withEvent:应该被称为… …

UIControl文档说:

您可能想要扩展一个UIControl子类有两个基本的原因:

观察或修改对特定事件的目标的动作消息的分派为此,覆盖sendAction:to:forEvent:评估传入的select器,目标对象或“注释”位掩码,并根据需要继续。

要提供自定义的跟踪行为(例如,更改突出显示的外观)为此,请重写以下一个或所有方法: beginTrackingWithTouch:withEvent:continueTrackingWithTouch:withEvent:endTrackingWithTouch:withEvent:

在我看来,这个关键部分不是很清楚,它说你可能想要扩展一个UIControl子类 – 不是你可能想直接扩展UIControl 。 有可能beginTrackingWithTouch:withEvent:continuetrackingWithTouch:withEvent:不应该被调用来响应触摸,并且UIControl直接子类应该调用它们,以便它们的子类可以监视跟踪。

所以我的解决scheme是重写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]; } 

请注意,您还应该使用sendActionsForControlEvents:发送与您的控件相关的任何UIControlEvent*消息sendActionsForControlEvents: – 这些可能是从超级方法调用的,我没有testing过。

迅速

这些不止是UIControl子类的一种方法。 当父视图需要对触摸事件作出反应或从控件获取其他数据时,通常使用(1)目标或(2)具有重写的触摸事件的委托模式来完成此操作。 为了完整起见,我还将展示如何(3)用手势识别器做同样的事情。 这些方法中的每一个都像下面的animation一样:

在这里输入图像说明

您只需要select以下方法之一。


方法1:添加一个目标

UIControl子类支持已经内置的目标。如果不需要将大量数据传递给父级,那么这可能是您想要的方法。

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)" } } } 

笔记

  • 操作方法名称没有什么特别之处。 我本可以叫他们任何东西。 我只需要小心地拼写方法名称就像我添加目标的地方一样。 否则你会崩溃。
  • didDragInsideControl:withEvent:的两个冒号didDragInsideControl:withEvent:表示将两个parameter passing给了didDragInsideControl方法。 如果您忘记添加冒号,或者如果您没有正确数量的参数,则会发生崩溃。
  • 感谢这个答案的TouchDragInside事件的帮助。

传递其他数据

如果你在自定义控件中有一些价值

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

您想要在目标操作方法中访问,那么您可以传入对控件的引用。 在设置目标时,请在操作方法名称后添加冒号。 例如:

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

请注意,它是touchedDown:冒号),而不是touchedDown (没有冒号)。 冒号意味着一个参数被传递给action方法。 在action方法中,指定参数是对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 (ie 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 (ie 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 (ie 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:使用手势识别器

添加一个手势识别器可以在任何视图上完成,它也可以在UIControl 。 为了得到类似的结果,我们将使用UIPanGestureRecognizer 。 然后通过testing事件发生时的各种状态,我们可以确定发生了什么事情。

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来实现委托模式。

我认为你忘了给touchesBegan / touchesEnded / touchesMoved添加[super]调用。 像

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

不工作,如果你重写touchesBegan / 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"); } 

我经常使用的最简单的方法是扩展UIControl,但是使用inheritance的addTarget方法来接收各种事件的callback。 关键是听取发件人和事件,以便您可以find有关实际事件的更多信息(例如事件发生的位置)。

所以,只是简单的子类UIControl,然后在init方法(确保你的initWithCoder也设置,如果你使用笔尖),添加以下内容:

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

当然,你可以select任何标准的控制事件,包括UIControlEventAllTouchEvents。 请注意,select器将传递两个对象。 首先是控制。 第二个是关于事件的信息。 下面是使用触摸事件来切换button的示例,具体取决于用户是否按下了左侧和右侧。

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

当然,这是相当简单的控制,你可能会达到一个点,这不会给你足够的function,但这已经适用于所有我的目的自定义控件到这一点,真的很容易使用,因为我通常关心相同控制UIControl已经支持的事件(触摸,拖动等等)

自定义控件的代码示例在这里: 自定义UISwitch (注意:这不注册buttonPressed:forEvent:select器,但你可以从上面的代码中找出)

我遇到了一个UIControl没有响应beginTrackingWithTouchcontinueTrackingWithTouch麻烦。

我发现我的问题是,当我做了initWithFrame:CGRectMake()我把框架小(反应区),只有一个点的地方,它的工作。 我使框架与控件的大小相同,然后随时按下控件中的任何地方。

Obj C

我从2014年发现了一篇相关文章https://www.objc.io/issues/13-architecture/behaviors/

有趣的是它的方法是利用IB并将事件处理逻辑封装在一个指定的对象(他们称之为行为)中,从而从你的view / viewController中删除逻辑,使其更轻。

  • Github示例案例: https : //github.com/krzysztofzablocki/BehavioursExample
Interesting Posts