Swift
有多种方法可以对UIControl
进行子类化。当父视图需要响应触摸事件或从控件获取其他数据时,通常使用(1)目标或(2)通过覆盖触摸事件的委托模式来完成。为了完整起见,我还将展示如何使用手势识别器(3)完成相同的事情。每种方法都会像以下动画一样运作:
您只需要选择以下方法之一。
方法1:添加目标
UIControl
子类已内置支持目标。如果您不需要向父级传递大量数据,则这可能是您想要的方法。
MyCustomControl.swift
import UIKit
class MyCustomControl: UIControl {
}
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()
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)
}
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:
请注意,它是
touchedDown:
(带有冒号),而不是
touchedDown
(没有冒号)。 冒号表示正在将参数传递给操作方法。 在操作方法中,指定该参数是对您的
UIControl
子类的引用。 有了该引用,您可以从控件获取数据。
func touchedDown(control: MyCustomControl) {
trackingBeganLabel.text = "Tracking began"
print(control.someValue)
}
方法二:委托模式和重写触摸事件
通过子类化UIControl
,我们可以访问以下方法:
beginTrackingWithTouch
:当手指第一次触摸控件边界时调用。
continueTrackingWithTouch
:当手指在控件上滑动甚至超出控件边界时,将重复调用此方法。
endTrackingWithTouch
:当手指离开屏幕时调用。
如果您需要对触摸事件进行特殊控制或者与父级进行大量数据通信,则使用此方法可能比添加目标更好。
以下是如何实现:
MyCustomControl.swift
import UIKit
protocol ViewControllerCommunicationDelegate: class {
func myTrackingBegan()
func myTrackingContinuing(location: CGPoint)
func myTrackingEnded()
}
class MyCustomControl: UIControl {
weak var delegate: ViewControllerCommunicationDelegate?
override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
delegate?.myTrackingBegan()
return true
}
override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
let point = touch.locationInView(self)
delegate?.myTrackingContinuing(point)
return true
}
override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) {
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"
}
}
注
- 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 to
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 {
}
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()
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(gestureRecognized(_:)))
myCustomControl.addGestureRecognizer(gestureRecognizer)
}
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种方法中那样实现委托模式。