Как правильно подклассифицировать UIControl?
Я не хочу 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
качестве основы для UISlider
подклассов, но я хочу приблизиться к UIControl
так как это источник того, что я хочу: быстрые и незадействованные координаты касания.
Ответы
Ответ 1
Я знаю, что этот вопрос древний, но у меня была та же проблема, и я думал, что должен дать свои 2 цента.
Если ваш элемент управления вообще имеет какие-либо подъязыки, beginTrackingWithTouch
, touchesBegan
и т.д. не могут быть вызваны, потому что эти подпрограммы поглощают события касания.
Если вы не хотите, чтобы эти подзаголовки обрабатывали штрихи, вы можете установить userInteractionEnabled
в NO
, так что subviews просто передает событие через. Затем вы можете переопределить touchesBegan/touchesEnded
и управлять всеми своими контактами.
Надеюсь, что это поможет.
Ответ 2
Swift
Это более чем один способ подкласса UIControl
. Когда родительское представление должно реагировать на события касания или получать другие данные из элемента управления, это обычно делается с использованием (1) целей или (2) шаблона делегата с переопределенными событиями касания. Для полноты я также покажу, как (3) сделать то же самое с распознающим жестом. Каждый из этих методов будет вести себя следующим образом:
Вам нужно только выбрать один из следующих способов.
Способ 1: Добавить цель
Подкласс A 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
(без двоеточия). Двоеточие означает, что параметр передается методу действия. В методе действий укажите, что этот параметр является ссылкой на ваш подкласс 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 (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 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")
}
}
Способ 3: используйте распознаватель жестов
Добавление распознавателя жестов может быть выполнено на любом представлении, а также работает с 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 выше.
Ответ 3
Я долго и долго искал решение этой проблемы, и я не думаю, что она есть. Однако при ближайшем рассмотрении документации я думаю, что может быть недоразумением, что 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:
и вызвать их оттуда следующим образом. Обратите внимание, что для этого элемента управления не задействовано multi-touch, и я не забочусь о завершении штрихов и затрагивает отмененные события, но если вы хотите быть полными/тщательными, вы, вероятно, тоже должны их реализовать.
- (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*
, релевантные для вашего элемента управления, используя sendActionsForControlEvents:
- они могут быть вызваны из супер методов, я его не тестировал.
Ответ 4
Я думаю, что вы забыли добавить [супер] призывы к касаниюBegan/touchesEnded/touchesMoved.
Методы вроде
(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
не работают, если вы перекрываете touchBegan/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");
}
Ответ 5
Самый простой подход, который я часто использую, - это расширение UIControl, но использование унаследованного метода addTarget для получения обратных вызовов для различных событий. Ключ заключается в том, чтобы прослушивать как отправителя, так и событие, чтобы вы могли узнать больше информации о фактическом событии (например, о месте, где произошло событие).
Итак, просто подкласс UIControl и затем в методе init (убедитесь, что ваш initWithCoder также настроен, если вы используете nibs), добавьте следующее:
[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:, но вы можете понять это из приведенного выше кода)
Ответ 6
У меня возникли проблемы с UIControl, не отвечающим на beginTrackingWithTouch
и continueTrackingWithTouch
.
Я обнаружил, что моя проблема была, когда я сделал initWithFrame:CGRectMake()
. Я сделал фрейм маленьким (область, которая реагирует) и имела только пару точек, где она работала. Я сделал рамку того же размера, что и элемент управления, а затем в любое время, когда я нажал на любой элемент управления, на который он ответил.
Ответ 7
Obj C
Я нашел связанную статью с 2014 года https://www.objc.io/issues/13-architecture/behaviors/.
Интересно, что его подход состоит в том, чтобы использовать IB и инкапсулировать логику обработки событий внутри выделенного объекта (они называют его поведением), таким образом удаляя логику из вашего view/viewController, делая его легче.