ProgramingTip

UIControl을 선택 하위 클래스 화하는 방법은 무엇입니까?

bestdevel 2020. 11. 17. 20:41
반응형

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서브 클래스를위한 기지로,하지만 나는 가까이 가고 싶어 UIControl그 소스이기 때문에 내가 원하는 것을 위해 : 빠른 속도와 지연되지 않은 터치 좌표를.


나는이 질문이 고대라는 뜻의 문제가 있다고 생각했습니다.

컨트롤이 전혀 어떤 하위 뷰가있는 경우 beginTrackingWithTouch, touchesBegan그 파단 터치 이벤트를 삼키는 있기 때문에, 등 호출하지 않을 수 있습니다.

당신이 핸들 접촉에 그 파단을 원하지 않는 경우 ,은 설정할 수 당신 있습니다 userInteractionEnabledNO파단이 단지를 통해 이벤트를 전달하므로. 다음 그런 touchesBegan/touchesEnded거기에서 모든 터치를 재정의 하고 관리 할 수 있습니다 .

도움이 되셨기를 바랍니다.


빠른

이것들은 하위 클래스를 만드는 두 가지 이상의 방법 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.
}

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'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")
        }
    }
    

방법 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"
        }
    }
}

메모

  • .NET의 작업 메서드 이름 뒤에 콜론을 추가하는 것을 잊지 마십시오 action: "gestureRecognized:". 콜론은 매개 변수가 전달되고 있음을 의미합니다.
  • 컨트롤에서 데이터를 가져와야하는 경우 위의 방법 2에서와 같이 대리자 패턴을 구현할 수 있습니다.

나는이 문제에 대한 해결책을 찾기 위해 길고 열심히 노력해 왔으며 하나도 없다고 생각합니다. 그러나, 문서의 더 정밀한 점검에 나는 그것이 오해 할 수 있다고 생각 begintrackingWithTouch:withEvent:하고 continueTrackingWithTouch:withEvent:전혀 호출로되어있다 ...

UIControl 문서는 말한다 :

UIControl두 가지 기본 이유로 서브 클래스 를 확장 할 수 있습니다 .

특정 이벤트의 대상에 대한 작업 메시지 전달을 관찰하거나 수정하려면이 작업을 수행하려면을 재정의 sendAction:to:forEvent:하고 전달 된 선택기, 대상 개체 또는 "Note"비트 마스크를 평가 한 다음 필요에 따라 진행합니다.

이, 재정 다음 방법 중 하나 또는 모두를 수행하려면 (하이라이트 모양을 변경, 예를 들어) 사용자 추적 동작을 제공하려면 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];
}

UIControlEvent*컨트롤과 관련된 모든 메시지를 보내야합니다.이 메시지 sendActionsForControlEvents:는 슈퍼 메서드에서 호출 될 수 있지만 테스트하지는 않았습니다.


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");
}

내가 자주 사용하는 가장 간단한 방법은 UIControl을 확장하는 것이지만 상속 된 addTarget 메서드를 사용하여 다양한 이벤트에 대한 콜백을 수신하는 것입니다. 핵심은 실제 이벤트에 대한 자세한 정보 (예 : 이벤트가 발생한 위치)를 찾을 수 있도록 보낸 사람과 이벤트를 모두 수신하는 것입니다.

따라서 간단히 UIControl을 하위 클래스로 만든 다음 init 메서드에서 (nibs를 사용하는 경우 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 : 선택기에 등록되지 않지만 위 코드에서 확인할 수 있음)


beginTrackingWithTouch및에 응답하지 않는 UIControl에 문제가 발생했습니다 continueTrackingWithTouch.

내 문제는 initWithFrame:CGRectMake()프레임을 작게 만들었을 (반응하는 영역) 작동하는 지점이 몇 점 밖에 없다는 것입니다. 나는 프레임을 컨트롤과 같은 크기로 만든 다음 컨트롤의 아무 곳이나 누르면 응답했습니다.


Obj C

2014 https://www.objc.io/issues/13-architecture/behaviors/ 에서 관련 기사를 찾았습니다 .

흥미로운 점은 IB를 사용하고 이벤트 처리 로직을 지정된 객체 (비헤이비어라고 부름)에 캡슐화하여 뷰 / 뷰 컨트롤러에서 로직을 제거하여 더 가볍게 만드는 것입니다.

참고 URL : https://stackoverflow.com/questions/1245248/how-to-correctly-subclass-uicontrol

반응형