如何正确地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美分。
如果你的控件有任何子视图, beginTrackingWithTouch
, touchesBegan
等可能不会被调用,因为这些子视图正在吞咽触摸事件。
如果你不想要这些子视图来处理触摸,你可以设置userInteractionEnabled
为NO
,所以子视图只是传递事件。 然后你可以覆盖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没有响应beginTrackingWithTouch
和continueTrackingWithTouch
麻烦。
我发现我的问题是,当我做了initWithFrame:CGRectMake()
我把框架小(反应区),只有一个点的地方,它的工作。 我使框架与控件的大小相同,然后随时按下控件中的任何地方。
Obj C
我从2014年发现了一篇相关文章https://www.objc.io/issues/13-architecture/behaviors/ 。
有趣的是它的方法是利用IB并将事件处理逻辑封装在一个指定的对象(他们称之为行为)中,从而从你的view / viewController中删除逻辑,使其更轻。
- Github示例案例: https : //github.com/krzysztofzablocki/BehavioursExample