ios 如何正确继承 UIControl?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1245248/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to correctly subclass UIControl?
提问by pixelfreak
I don't want UIButton
or anything like that. I want to subclass UIControl
directly and make my own, very special control.
我不想要UIButton
或类似的东西。我想UIControl
直接子类化并制作我自己的,非常特殊的控件。
But for some reason, none of any methods I override get ever called. The target-action stuff works, and the targets receive appropriate action messages. However, inside my UIControl
subclass I have to catch touch coordinates, and the only way to do so seems to be overriding these guys:
但出于某种原因,我覆盖的任何方法都没有被调用。目标动作的东西起作用,目标接收适当的动作消息。但是,在我的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;
}
They get never called, even though the UIControl
is instantiated with the designates initializer from UIView
, initWithFrame:
.
它们永远不会被调用,即使UIControl
是使用来自UIView
,的指定初始值设定项进行实例化initWithFrame:
。
All examples I can find always use a UIButton
or UISlider
as base for subclassing, but I want to go closer to UIControl
since that's the source for what I want: Fast and undelayed Touch coordinates.
我能找到的所有示例总是使用 aUIButton
或UISlider
作为子类化的基础,但我想更接近,UIControl
因为这是我想要的来源:快速且无延迟的触摸坐标。
回答by pixelfreak
I know this question is ancient, but I had the same problem and I thought I should give my 2 cents.
我知道这个问题很古老,但我遇到了同样的问题,我想我应该给我 2 美分。
If your control has any subviews at all, beginTrackingWithTouch
, touchesBegan
, etc might not get called because those subviews are swallowing the touch events.
如果您的控件有任何子视图beginTrackingWithTouch
,touchesBegan
, 等可能不会被调用,因为这些子视图正在吞噬触摸事件。
If you don't want those subviews to handle touches, you can set userInteractionEnabled
to NO
, so the subviews simply passes the event through. Then you can override touchesBegan/touchesEnded
and manage all your touches there.
如果您不希望这些子视图处理触摸,您可以设置userInteractionEnabled
为NO
,以便子视图简单地传递事件。然后您可以覆盖touchesBegan/touchesEnded
和管理您在那里的所有触摸。
Hope this helps.
希望这可以帮助。
回答by Suragch
Swift
迅速
These is more than one way to subclass UIControl
. When a parent view needs to react to touch events or get other data from the control, this is usually done using (1) targets or (2) the delegate pattern with overridden touch events. For completeness I will also show how to (3) do the same thing with a gesture recognizer. Each of these methods will behave like the following animation:
这些是创建子类的不止一种方法UIControl
。当父视图需要对触摸事件做出反应或从控件获取其他数据时,这通常使用 (1) 目标或 (2) 具有覆盖触摸事件的委托模式来完成。为了完整起见,我还将展示如何 (3) 用手势识别器做同样的事情。这些方法中的每一个都将像以下动画一样运行:
You only need to choose one of the following methods.
您只需要选择以下方法之一即可。
Method 1: Add a Target
方法一:添加目标
A UIControl
subclass has support for targets already built in. If you don't need to pass a lot of data to the parent, this is probably the method you want.
一个UIControl
子类有已建成的目标的支持。如果你不需要大量的数据传递给父母,这可能是你想要的方法。
MyCustomControl.swift
MyCustomControl.swift
import UIKit
class MyCustomControl: UIControl {
// You don't need to do anything special in the control for targets to work.
}
ViewController.swift
视图控制器.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)"
}
}
}
Notes
笔记
- There is nothing special about the action method names. I could have called them anything. I just have to be careful to spell the method name exactly like I did where I added the target. Otherwise you get a crash.
- The two colons in
didDragInsideControl:withEvent:
means that two parameters are being passed to thedidDragInsideControl
method. If you forget to add a colon or if you don't have the correct number of parameters, you will get a crash. - Thanks to this answerfor help with the
TouchDragInside
event.
- 操作方法名称没有什么特别之处。我可以叫他们任何东西。我只需要小心拼写方法名称,就像我添加目标时所做的那样。否则你会崩溃。
- 中的两个冒号
didDragInsideControl:withEvent:
表示将两个参数传递给该didDragInsideControl
方法。如果您忘记添加冒号,或者您没有正确数量的参数,则会导致崩溃。 - 感谢这个答案对
TouchDragInside
事件的帮助。
Passing other data
传递其他数据
If you have some value in your custom control
如果您的自定义控件有一些价值
class MyCustomControl: UIControl {
var someValue = "hello"
}
that you want to access in the target action method, then you can pass in a reference to the control. When you are setting the target, add a colon after the action method name. For example:
要在目标操作方法中访问,则可以传入对控件的引用。设置目标时,在操作方法名称后添加一个冒号。例如:
myCustomControl.addTarget(self, action: #selector(touchedDown), forControlEvents: UIControlEvents.TouchDown)
Notice that it is touchedDown:
(with a colon) and not touchedDown
(without a colon). The colon means that a parameter is being passed to the action method. In the action method, specify that the parameter is a reference to your UIControl
subclass. With that reference, you can get data from your control.
请注意,它是touchedDown:
(带冒号)而不是touchedDown
(不带冒号)。冒号表示将参数传递给操作方法。在 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)
}
Method 2: Delegate Pattern and Override Touch Events
方法 2:委托模式和覆盖触摸事件
Subclassing UIControl
gives us access to the following methods:
子类化UIControl
使我们可以访问以下方法:
beginTrackingWithTouch
is called when the finger first touches down within the control's bounds.continueTrackingWithTouch
is called repeatedly as the finger slides across the control and even outside of the control's bounds.endTrackingWithTouch
is called when the finger lifts off the screen.
beginTrackingWithTouch
当手指第一次在控件的边界内向下触摸时调用。continueTrackingWithTouch
当手指滑过控件甚至超出控件的边界时会重复调用。endTrackingWithTouch
当手指离开屏幕时调用。
If you need special control of the touch events or if you have a lot of data communication to do with the parent, then this method may work better then adding targets.
如果您需要对触摸事件进行特殊控制,或者您需要与父级进行大量数据通信,那么此方法可能比添加目标更有效。
Here is how to do it:
这是如何做到的:
MyCustomControl.swift
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
视图控制器.swift
This is how the view controller is set up to be the delegate and respond to touch events from our custom control.
这就是将视图控制器设置为委托并响应来自我们自定义控件的触摸事件的方式。
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"
}
}
Notes
笔记
- To learn more about the delegate pattern, see this answer.
It is not necessary to use a delegate with these methods if they are only being used within the custom control itself. I could have just added a
print
statement to show how the events are being called. In that case, the code would be simplified toimport 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") } }
- 要了解有关委托模式的更多信息,请参阅此答案。
如果这些方法仅在自定义控件本身中使用,则没有必要对这些方法使用委托。我可以添加一个
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") } }
Method 3: Use a Gesture Recognizer
方法 3:使用手势识别器
Adding a gesture recognizer can be done on any view and it also works on a UIControl
. To get similar results to the example at the top, we will use a UIPanGestureRecognizer
. Then by testing the various states when an event is fired we can determine what is happening.
添加手势识别器可以在任何视图上完成,它也适用于UIControl
. 为了获得与顶部示例类似的结果,我们将使用UIPanGestureRecognizer
. 然后通过测试触发事件时的各种状态,我们可以确定发生了什么。
MyCustomControl.swift
MyCustomControl.swift
import UIKit
class MyCustomControl: UIControl {
// nothing special is required in the control to make it work
}
ViewController.swift
视图控制器.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"
}
}
}
Notes
笔记
- Don't forget to add the colon after the action method name in
action: "gestureRecognized:"
. The colon means that parameters are being passed in. - If you need to get data from the control, you could implement the delegate pattern as in method 2 above.
- 不要忘记在
action: "gestureRecognized:"
. 冒号表示正在传入参数。 - 如果您需要从控件中获取数据,您可以像上面的方法 2 一样实现委托模式。
回答by jhabbott
I have looked long and hard for a solution to this problem and I don't think there is one. However, on closer inspection of the documentation I think it might be a misunderstanding that begintrackingWithTouch:withEvent:
and continueTrackingWithTouch:withEvent:
are supposed to be called at all...
我一直在努力寻找解决这个问题的方法,但我认为没有。但是,仔细检查文档后,我认为这可能是一种误解,begintrackingWithTouch:withEvent:
并且continueTrackingWithTouch:withEvent:
应该完全调用......
UIControl
documentation says:
UIControl
文档说:
You may want to extend a
UIControl
subclass for two basic reasons:To observe or modify the dispatch of action messages to targets for particular events To do this, override
sendAction:to:forEvent:
, evaluate the passed-in selector, target object, or “Note” bit mask and proceed as required.To provide custom tracking behavior (for example, to change the highlight appearance) To do this, override one or all of the following methods:
beginTrackingWithTouch:withEvent:
,continueTrackingWithTouch:withEvent:
,endTrackingWithTouch:withEvent:
.
您可能
UIControl
出于两个基本原因想要扩展子类:观察或修改对特定事件的目标的动作消息的分派 为此,覆盖
sendAction:to:forEvent:
、评估传入的选择器、目标对象或“注意”位掩码,并根据需要继续。提供自定义跟踪行为(例如,更改突出显示外观)为此,请覆盖以下一种或所有方法:
beginTrackingWithTouch:withEvent:
、continueTrackingWithTouch:withEvent:
、endTrackingWithTouch:withEvent:
。
The critical part of this, which is not very clear in my view, is that it says you may want to extend a UIControl
subclass - NOT you may want to extend UIControl
directly. It's possible that beginTrackingWithTouch:withEvent:
and continuetrackingWithTouch:withEvent:
are not supposed to get called in response to touches and that UIControl
direct subclasses are supposed to call them so that theirsubclasses can monitor tracking.
这其中的关键部分在我看来不是很清楚,它说您可能想要扩展UIControl
子类 - 而不是您可能想要UIControl
直接扩展。有可能beginTrackingWithTouch:withEvent:
并且continuetrackingWithTouch:withEvent:
不应该在响应触摸时被调用,并且UIControl
直接子类应该调用它们,以便它们的子类可以监视跟踪。
So my solution to this is to override touchesBegan:withEvent:
and touchesMoved:withEvent:
and call them from there as follows. Note that multi-touch is not enabled for this control and that I don't care about touches ended and touches canceled events, but if you want to be complete/thorough you should probably implement those too.
所以我对此的解决方案是覆盖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];
}
Note that you should also send any UIControlEvent*
messages that are relevant for your control using sendActionsForControlEvents:
- these may be called from the super methods, I haven't tested it.
请注意,您还应该UIControlEvent*
使用与您的控件相关的任何消息发送sendActionsForControlEvents:
- 这些消息可能会从超级方法中调用,我尚未对其进行测试。
回答by Duane Homick
The simplest approach that I often use is to extend UIControl, but to make use of the inherited addTarget method to receive callbacks for the various events. The key is to listen for both the sender and the event so that you can find out more information about the actual event (such as the location of where the event occurred).
我经常使用的最简单的方法是扩展 UIControl,但使用继承的 addTarget 方法来接收各种事件的回调。关键是要同时监听发送者和事件,以便您可以找到有关实际事件的更多信息(例如事件发生的位置)。
So, just simply subclass UIControl and then in the init method (make sure your initWithCoder is also setup if you are using nibs) , add the following:
因此,只需简单地将 UIControl 子类化,然后在 init 方法中(如果您使用笔尖,请确保您的 initWithCoder 也已设置),添加以下内容:
[self addTarget:self action:@selector(buttonPressed:forEvent:) forControlEvents:UIControlEventTouchUpInside];
Of course, you can choose any of the standard control events including UIControlEventAllTouchEvents. Notice that the selector will get passed two objects. The first is the control. The second is the info about the event. Here's an example of using the touch event to toggle a button depending on if the user pressed on the left and right.
当然,您可以选择任何标准控件事件,包括 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
}
}
}
Granted, this is for pretty simplistic controls and you may reach a point where this will not give you enough functionality, but this has worked for all my purposes for custom controls to this point and is really easy to use since I typically care about the same control events that the UIControl already supports (touch up, drag, etc...)
当然,这是用于非常简单的控件,您可能会达到这样的程度,这将无法为您提供足够的功能,但是到目前为止,这对于我自定义控件的所有目的都有效,并且非常易于使用,因为我通常关心相同UIControl 已经支持的控制事件(触摸、拖动等...)
Code sample of custom control here: Custom UISwitch(note: this does not register with the buttonPressed:forEvent: selector, but you can figure that out from the code above)
此处自定义控件的代码示例: Custom UISwitch(注意:这不会注册到 buttonPressed:forEvent: 选择器,但您可以从上面的代码中看出这一点)
回答by tt.Kilew
I think that you forgot to add [super] calls to touchesBegan/touchesEnded/touchesMoved. Methods like
我认为您忘记向 touchesBegan/touchesEnded/touchesMoved 添加 [super] 调用。方法如
(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
aren't working if you overriding touchesBegan / touchesEnded like this :
如果您像这样覆盖 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");
}
But! All works fine if methods will be like :
但!如果方法类似于:
- (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");
}
回答by digitalmasters
I was having trouble with a UIControl not responding to beginTrackingWithTouch
and continueTrackingWithTouch
.
我遇到了 UIControl 没有响应beginTrackingWithTouch
和 的问题continueTrackingWithTouch
。
I found my problem was when I did initWithFrame:CGRectMake()
I made the frame to small (the area that reacts) and had only a couple point spot where it did work. I made the frame the same size as the control and then anytime I pressed on anywhere in the control it responded.
我发现我的问题是当我做的时候initWithFrame:CGRectMake()
我把框架做得很小(反应的区域)并且只有几个点可以工作。我使框架与控件的大小相同,然后我在任何时候按下控件中的任何地方,它都会响应。
回答by supergegs
Obj C
对象C
I found a related article from 2014 https://www.objc.io/issues/13-architecture/behaviors/.
我找到了一篇来自 2014 https://www.objc.io/issues/13-architecture/behaviors/的相关文章。
The interesting thing about it is that its approach is to make use of IB and encapsulate the event handling logic inside a designated object (they call it behaviors), thus removing logic from your view/viewController making it lighter.
关于它的有趣之处在于它的方法是利用 IB 并将事件处理逻辑封装在指定的对象中(他们称之为行为),从而从您的视图/视图控制器中删除逻辑使其更轻。
- Github sample case: https://github.com/krzysztofzablocki/BehavioursExample
- Github 示例案例:https: //github.com/krzysztofzablocki/BehavioursExample