如何正确子类UIControl?

66 objective-c uicontrol ios

我不想要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;
}
Run Code Online (Sandbox Code Playgroud)

它们永远不会被调用,即使它UIControl是使用指定的初始化程序实例化的UIView,initWithFrame:.

我可以找到的所有示例总是使用UIButtonUISlider作为子类的基础,但我想更接近,UIControl因为这是我想要的源:快速和未延迟的触摸坐标.

pix*_*eak 66

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

如果你的控件有任何子视图可言,beginTrackingWithTouch,touchesBegan,等会不会被调用,因为这些子视图吞咽触摸事件.

如果您不希望这些子视图处理触摸,您可以设置userInteractionEnabledNO,因此子视图只是传递事件.然后,您可以覆盖touchesBegan/touchesEnded和管理那里的所有触摸.

希望这可以帮助.

  • 这正是我的问题.我的自定义UIControl有一个吞下事件的子视图.设置userInteractionEnabled为NO解决了它! (5认同)
  • 我知道这是旧的,但我想我会补充:你也可以覆盖` - (UIView*)hitTest:(CGPoint)指向withEvent:(UIEvent*)事件;`默认情况下它调用`-pointInside:withEvent:`on子视图并返回触摸应该去的那个.如果你覆盖它来自己调用`-pointInside:withEvent:`然后只返回self或nil子视图将不会接收任何触摸. (3认同)

Sur*_*gch 25

迅速

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

在此输入图像描述

您只需选择以下方法之一.


方法1:添加目标

一个UIControl子类有已建成的目标的支持.如果你不需要大量的数据传递给父母,这可能是你想要的方法.

MyCustomControl.swift

import UIKit
class MyCustomControl: UIControl {
    // You don't need to do anything special in the control for targets to work.
}
Run Code Online (Sandbox Code Playgroud)

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)"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记

  • 动作方法名称没有什么特别之处.我可以给他们打电话.我必须小心拼写方法名称,就像我添加目标时一样.否则你会崩溃.
  • 两个冒号didDragInsideControl:withEvent:意味着两个参数被传递给didDragInsideControl方法.如果您忘记添加冒号或者如果没有正确数量的参数,则会发生崩溃.
  • 感谢这个答案,以帮助解决这个问题TouchDragInside.

传递其他数据

如果您的自定义控件中有一些值

class MyCustomControl: UIControl {
    var someValue = "hello"
}
Run Code Online (Sandbox Code Playgroud)

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

myCustomControl.addTarget(self, action: #selector(touchedDown), forControlEvents: UIControlEvents.TouchDown) 
Run Code Online (Sandbox Code Playgroud)

请注意它是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)
}
Run Code Online (Sandbox Code Playgroud)

方法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 (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()
    }
}
Run Code Online (Sandbox Code Playgroud)

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"
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记

  • 要了解有关委托模式的更多信息,请参阅此答案.
  • 如果它们仅在自定义控件本身中使用,则不必使用具有这些方法的委托.我本来可以添加一个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")
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

方法3:使用手势识别器

添加手势识别器可以在任何视图上完成,它也适用于UIControl.要获得与顶部示例类似的结果,我们将使用a UIPanGestureRecognizer.然后通过在事件触发时测试各种状态,我们可以确定发生了什么.

MyCustomControl.swift

import UIKit
class MyCustomControl: UIControl {
    // nothing special is required in the control to make it work
}
Run Code Online (Sandbox Code Playgroud)

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"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记

  • 不要忘记在操作方法名称后添加冒号action: "gestureRecognized:".冒号表示正在传入参数.
  • 如果需要从控件获取数据,可以像上面的方法2一样实现委托模式.

  • @Greg,如果原来的海报不再存在,那么没有接受的答案实际上更好,因为现在这个答案(如果值得的话)最终会升到顶峰. (2认同)

jha*_*ott 20

我已经看了很久很难找到解决这个问题的方法,我认为没有.然而,在文档的仔细检查,我认为它可能是一个误会begintrackingWithTouch:withEvent:,并continueTrackingWithTouch:withEvent:应该在所有被称为...

UIControl 文件说:

您可能希望扩展UIControl 子类有两个基本原因:

观察或修改将动作消息分派给特定事件的目标要执行此操作,请覆盖 sendAction:to:forEvent:,评估传入的选择器,目标对象或"注意"位掩码,然后根据需要继续操作.

要提供自定义跟踪行为(例如,更改高亮外观)要做到这一点,覆盖一个或所有下面的方法: beginTrackingWithTouch:withEvent:, continueTrackingWithTouch:withEvent:, endTrackingWithTouch:withEvent:.

在我看来,这个问题的关键部分是它说你可能想扩展一个UIControl子类 - 不是你可能想UIControl直接扩展.有可能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];
}
Run Code Online (Sandbox Code Playgroud)

请注意,您还应该发送UIControlEvent*与您的控件相关的任何消息sendActionsForControlEvents:- 这些可以从超级方法调用,我还没有测试过.


小智 9

我经常使用的最简单的方法是扩展UIControl,但是要使用继承的addTarget方法来接收各种事件的回调.关键是要监听发件人和事件,以便您可以找到有关实际事件的更多信息(例如事件发生的位置).

因此,只需简单地将UIControl子类化,然后在init方法中(如果您使用的是nib,请确保您的initWithCoder也已设置),添加以下内容:

[self addTarget:self action:@selector(buttonPressed:forEvent:) forControlEvents:UIControlEventTouchUpInside];
Run Code Online (Sandbox Code Playgroud)

当然,您可以选择任何标准控制事件,包括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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,这是为了非常简单的控制,你可能会达到这样的程度,这不会给你足够的功能,但这对我的所有自定义控件的目的都有用,并且非常容易使用,因为我通常关心相同的控制UIControl已经支持的事件(触摸,拖动等......)

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


tt.*_*lew 9

我想你忘了把[super]调用添加到touchesBegan/touchesEnded/touchesMoved.方法如

(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event    
(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
Run Code Online (Sandbox Code Playgroud)

如果你覆盖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");
}
Run Code Online (Sandbox Code Playgroud)

但!如果方法如下,一切正常:

- (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");
}
Run Code Online (Sandbox Code Playgroud)

  • 我发现无论a)都没有实现touchesBegan和touchesMoved; 和b)强迫他们和超级电话.无论哪种方式,beginTrackingWithTouch和continueTrackingWithTouch都不会被调用. (3认同)