我不想要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:
进行实例化,也永远不会调用它们。
我可以找到的所有示例总是使用UIButton
或UISlider
作为子类的基础,但我想更接近UIControl
,因为这是我想要的来源:快速和未延迟的触摸坐标。
答案 0 :(得分:65)
我知道这个问题很古老,但我遇到了同样的问题,我认为应该给我2美分。
如果您的控件有任何子视图,beginTrackingWithTouch
,touchesBegan
等可能无法调用,因为这些子视图正在吞噬触摸事件。
如果您不希望这些子视图处理触摸,您可以将userInteractionEnabled
设置为NO
,因此子视图只会传递事件。然后,您可以覆盖touchesBegan/touchesEnded
并管理所有触摸。
希望这有帮助。
答案 1 :(得分:23)
这些是子类UIControl
的多种方式。当父视图需要对触摸事件作出反应或从控件获取其他数据时,通常使用(1)目标或(2)具有重写触摸事件的委托模式来完成。为了完整性,我还将展示如何(3)用手势识别器做同样的事情。这些方法中的每一个都将表现如下动画:
您只需选择以下方法之一。
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
方法。如果您忘记添加冒号或者如果您没有正确数量的参数,则会发生崩溃。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)
}
子类化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")
}
}
添加手势识别器可以在任何视图上完成,它也适用于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 :(得分:20)
我已经看了很久很难找到解决这个问题的方法,我认为没有。但是,仔细检查文档后,我认为可能会误解begintrackingWithTouch:withEvent:
和continueTrackingWithTouch:withEvent:
应该被调用...
UIControl
文档说:
您可能希望延长
UIControl
子类有两个基本原因:观察或修改发送 对目标的动作消息 特定事件要执行此操作,请覆盖
sendAction:to:forEvent:
,评估一下 传入选择器,目标对象或 “注意”位掩码并继续执行 必需的。提供自定义跟踪行为 (例如,更改突出显示 外观)为此,请覆盖一个 或以下所有方法:
beginTrackingWithTouch:withEvent:
,continueTrackingWithTouch:withEvent:
,endTrackingWithTouch:withEvent:
。
这一点的关键部分,在我看来并不是很清楚,它表示你可能想要扩展UIControl
子类 - 不是你可能想直接扩展UIControl
。 {(1}}和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*
消息 - 这些消息可以从超级方法调用,我还没有测试过。
答案 3 :(得分:9)
我认为您忘记将[super]调用添加到touchesBegan / touchesEnded / touchesMoved。 像
这样的方法(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");
}
答案 4 :(得分:8)
我经常使用的最简单的方法是扩展UIControl,但是要使用继承的addTarget方法来接收各种事件的回调。关键是要监听发件人和事件,以便您可以找到有关实际事件的更多信息(例如事件发生地点的位置)。
因此,只需简单地将UIControl子类化,然后在init方法中(确保在使用nib时也设置了initWithCoder),添加以下内容:
[self addTarget:self action:@selector(buttonPressed:forEvent:) forControlEvents:UIControlEventTouchUpInside];
当然,您可以选择任何标准控制事件,包括UIControlEventAllTouchEvents。请注意,选择器将传递两个对象。第一个是控制。第二个是关于该事件的信息。以下是使用触摸事件切换按钮的示例,具体取决于用户是否按下左侧和右侧。
- (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已经支持的相同控制事件(触摸,拖动等等)
自定义控件的代码示例:Custom UISwitch(注意:这不会在buttonPressed:forEvent:selector中注册,但你可以从上面的代码中找出来)
答案 5 :(得分:3)
我在使用UIControl无法回复beginTrackingWithTouch
和continueTrackingWithTouch
时遇到问题。
我发现我的问题是我做的时候initWithFrame:CGRectMake()
我把框架做得很小(反应的区域)并且只有几个点位置才有效。我使框架的大小与控件相同,然后我随时按下它所响应的控件中的任何位置。
答案 6 :(得分:0)
我发现了2014年https://www.objc.io/issues/13-architecture/behaviors/的相关文章。
有趣的是它的方法是利用IB并将事件处理逻辑封装在指定的对象中(它们称之为行为),从而从view / viewController中删除逻辑,使其更轻松。