2009-08-07 21 views
61

Tôi không muốn UIButton hoặc bất cứ điều gì như thế. Tôi muốn phân lớp UIControl trực tiếp và tự mình kiểm soát rất đặc biệt.Cách phân lớp UIControl đúng cách?

Nhưng vì một lý do nào đó, không có phương pháp nào tôi ghi đè lên bao giờ được gọi. Công cụ nhắm mục tiêu hoạt động và mục tiêu nhận được thông điệp hành động thích hợp. Tuy nhiên, bên trong lớp con UIControl của tôi, tôi phải bắt tọa độ cảm ứng, và cách duy nhất để làm điều đó dường như được trọng những kẻ:

- (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; 
} 

Họ nhận không bao giờ được gọi là, mặc dù UIControl được khởi tạo với chỉ định initializer từ UIView, initWithFrame :.

Tất cả các ví dụ tôi có thể tìm alyways sử dụng UIButton hoặc UISlider làm cơ sở cho phân lớp, nhưng tôi muốn tiếp cận gần hơn với UIControl vì đó là nguồn cho những gì tôi muốn: Các tọa độ chạm nhanh và không bị trễ.

+0

Bạn chắc chắn có thể phân lớp UIControl, và đó là những phương pháp đúng để ghi đè. Bạn có đang lập tức điều khiển chương trình của mình hay thông qua IB không? Nếu trước đây, bạn có thể đăng mã đó không? – zpasternack

+0

Tôi cũng bị mắc kẹt. Bất cứ ai cũng có một hướng dẫn tốt để thực hiện các điều khiển tùy chỉnh thông qua subclassing UIControl? – bentford

+3

Được xếp thứ ba. Tôi tạo ra một UIControl mà chỉ hoạt động tốt nếu tôi nhanh chóng nó trong phương pháp loadView của bộ điều khiển (nib-ít phong cách). Nhưng nếu tôi khởi tạo một từ một nib, chạm của nóBegan: withEvent: gia đình của các phương pháp vẫn được gọi, nhưng beginTrackingWithTouch: withEvent: những người không bao giờ làm. Baffling. – rgeorge

Trả lời

7

Cách tiếp cận đơn giản nhất mà tôi thường sử dụng là mở rộng UIControl, nhưng để tận dụng phương thức addTarget được thừa kế để nhận các cuộc gọi lại cho các sự kiện khác nhau. Điều quan trọng là lắng nghe cả người gửi và sự kiện để bạn có thể tìm hiểu thêm thông tin về sự kiện thực tế (chẳng hạn như vị trí xảy ra sự kiện).

Vì vậy, chỉ đơn giản là phân lớp UIControl và sau đó trong phương pháp init (chắc chắn initWithCoder của bạn cũng được thiết lập nếu bạn đang sử dụng ngòi), thêm dòng sau:

[self addTarget:self action:@selector(buttonPressed:forEvent:) forControlEvents:UIControlEventTouchUpInside]; 

Tất nhiên, bạn có thể chọn bất kỳ các sự kiện kiểm soát tiêu chuẩn bao gồm UIControlEventAllTouchEvents. Lưu ý rằng bộ chọn sẽ nhận được hai đối tượng. Đầu tiên là điều khiển. Thứ hai là thông tin về sự kiện. Dưới đây là ví dụ về cách sử dụng sự kiện chạm để chuyển đổi nút tùy thuộc vào người dùng đã nhấn ở bên trái và bên phải.

- (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 
     } 
    } 
} 

Cấp, điều này là cho các điều khiển khá đơn giản và bạn có thể đạt được một điểm mà điều này sẽ không cung cấp cho bạn đủ chức năng, nhưng điều này đã làm việc cho tất cả các mục đích của tôi cho điều khiển tùy chỉnh đến thời điểm này và thực sự dễ sử dụng vì tôi thường quan tâm đến các sự kiện kiểm soát tương tự mà UIControl đã hỗ trợ (chạm, kéo, vv ...)

Mẫu mã điều khiển tùy chỉnh tại đây: Custom UISwitch (lưu ý: điều này không đăng ký với nútPress: forEvent: công cụ chọn, nhưng bạn có thể hiểu điều đó từ mã bên trên)

+1

Dude, liên kết của bạn với UISwitch tùy chỉnh của bạn đã chết ... :( – nickfox

9

Tôi nghĩ rằng y Bạn quên thêm các cuộc gọi [siêu] để chạm vàoBán/chạm chạmBước/chạm được yêu thích. Các phương pháp như

(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event  
(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event 

không làm việc nếu bạn ghi đè touchesBegan/touchesEnded như thế này:

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

Nhưng! Tất cả hoạt động tốt nếu các phương pháp sẽ như sau:

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

Tôi đã nhận thấy rằng bất kể a) không thực hiện các thao tác chạm vàoBán và chạm vàoTất cả; và b) impementing chúng và gọi siêu. một trong hai cách, beginTrackingWithTouch và continueTrackingWithTouch không bao giờ được gọi. – jhabbott

+0

Đây là đề xuất hợp lệ. Nếu tôi nhận xét cuộc gọi [super] từ 'touchesBegan ...' thì 'startsTracking ...' sẽ ngừng gọi. Nó hoạt động ngay sau khi tôi gọi [siêu] một lần nữa. +1 Ngoài ra, đây là tất cả mà không cần phải gọi 'beginTracking ...' theo cách thủ công như được đề xuất trong câu trả lời khác. –

19

Tôi đã xem xét lâu dài và khó khăn cho một giải pháp cho vấn đề này và tôi không nghĩ có một vấn đề. Tuy nhiên, khi kiểm tra kỹ hơn tài liệu, tôi nghĩ rằng có thể là một sự hiểu lầm rằng begintrackingWithTouch:withEvent:continueTrackingWithTouch:withEvent: được cho là sẽ được gọi ...

UIControl tài liệu nói:

Bạn có thể muốn mở rộng một lớp con UIControl vì hai lý do cơ bản:

Chấp hành hoặc sửa đổi các công văn của thông điệp hành động với các mục tiêu cho sự kiện đặc biệt Để làm điều này, ghi đè sendAction:to:forEvent:, đánh giá bộ chọn được chuyển vào , đối tượng đích hoặc mặt nạ bit “Note” và tiến hành như yêu cầu.

Để cung cấp theo dõi tùy chỉnh hành vi (ví dụ, thay đổi giao diện nổi bật ) Để làm điều này, ghi đè lên một hoặc tất cả các phương pháp sau đây: beginTrackingWithTouch:withEvent:, continueTrackingWithTouch:withEvent:, endTrackingWithTouch:withEvent:.

Phần quan trọng của việc này, mà không phải là rất rõ ràng trong quan điểm của tôi, đó là nó nói bạn có thể muốn mở rộng một lớp con UIControl - KHÔNG bạn có thể muốn mở rộng UIControl trực tiếp. Có thể là beginTrackingWithTouch:withEvent:continuetrackingWithTouch:withEvent: không được phép gọi để phản hồi chạm và rằng UIControl các lớp con trực tiếp được yêu cầu gọi chúng để các lớp con của chúng có thể theo dõi việc theo dõi.

Vì vậy, giải pháp của tôi cho điều này là ghi đè touchesBegan:withEvent:touchesMoved:withEvent: và gọi chúng từ đó như sau. Lưu ý rằng đa cảm ứng không được kích hoạt cho điều khiển này và tôi không quan tâm đến các thao tác chạm đã kết thúc và chạm vào các sự kiện đã hủy, nhưng nếu bạn muốn hoàn thành/toàn diện, bạn cũng nên triển khai những điều đó.

- (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]; 
} 

Lưu ý rằng bạn cũng nên gửi bất kỳ UIControlEvent* thông điệp có liên quan để kiểm soát bằng cách sử dụng sendActionsForControlEvents: - đây có thể được gọi từ những phương pháp siêu, tôi đã không kiểm tra nó.

+0

Điều này là chính xác. Đảm bảo rằng bạn gọi [super touchesBegan: withEvent:] etc để các lớp con nếu UIControl tùy chỉnh của bạn hoạt động đúng cách. – samuraisam

+0

hoạt động tốt. –

53

Tôi biết câu hỏi này là cổ xưa, nhưng tôi đã có cùng một vấn đề và tôi nghĩ rằng tôi nên cho tôi 2 xu.

Nếu kiểm soát của bạn có bất kỳ bản xem trước nào, beginTrackingWithTouch, touchesBegan, v.v. có thể không được gọi vì các bản xem trước đó đang nuốt các sự kiện chạm.

Nếu bạn không muốn các bài đánh giá đó xử lý các lần chạm, bạn có thể đặt userInteractionEnabled thành NO, vì vậy, các bản phụ đề chỉ cần chuyển sự kiện qua. Sau đó, bạn có thể ghi đè lên touchesBegan/touchesEnded và quản lý tất cả các liên lạc của mình ở đó.

Hy vọng điều này sẽ hữu ích.

+5

Đây chính là vấn đề của tôi. UIControl tùy chỉnh của tôi có một subview đã nuốt các sự kiện. Cài đặt userInteractionEnabled thành NO giải quyết nó! – nrj

+2

Tôi biết điều này là cũ nhưng tôi nghĩ rằng tôi muốn thêm: bạn cũng có thể ghi đè lên '- (UIView *) hitTest: (CGPoint) điểm vớiEvent: (UIEvent *) sự kiện;' Theo mặc định nó gọi '-pointInside: withEvent:' trên các bản xem trước và trả về bản chạm mà bạn cần. Nếu bạn ghi đè lên nó để gọi '-pointInside: withEvent:' trên chính bạn và sau đó chỉ trả về tự hoặc không thì các bản phụ sẽ không nhận được bất kỳ chạm nào. –

3

Tôi gặp sự cố với UIControl không phản hồi beginTrackingWithTouchcontinueTrackingWithTouch.

Tôi thấy vấn đề của mình là khi tôi làm initWithFrame:CGRectMake() Tôi đã tạo khung nhỏ (khu vực phản ứng) và chỉ có một điểm hai điểm mà nó hoạt động. Tôi đã làm cho khung hình có cùng kích thước với bộ điều khiển và sau đó bất cứ lúc nào tôi nhấn vào bất cứ nơi nào trong điều khiển nó phản hồi.

15

Swift

Đây là nhiều cách để phân lớp UIControl. Khi chế độ xem gốc cần phản ứng với sự kiện chạm hoặc lấy dữ liệu khác từ điều khiển, điều này thường được thực hiện bằng (1) mục tiêu hoặc (2) mẫu đại biểu có sự kiện chạm đã ghi đè. Để hoàn thành, tôi cũng sẽ chỉ cách (3) làm điều tương tự với trình nhận dạng cử chỉ. Mỗi phương pháp này sẽ hoạt động giống như các hình ảnh động sau:

enter image description here

Bạn chỉ cần chọn một trong những phương pháp sau đây.


Phương pháp 1: Thêm một mục tiêu

Một lớp con UIControl đã hỗ trợ cho các mục tiêu đã được xây dựng trong Nếu bạn không cần phải vượt qua rất nhiều dữ liệu để phụ huynh, đây có lẽ là phương pháp bạn. muốn.

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

Ghi chú

  • Không có gì đặc biệt là về tên phương thức hành động. Tôi có thể gọi họ là gì. Tôi chỉ cần cẩn thận để đánh vần tên phương thức chính xác như tôi đã làm nơi tôi đã thêm mục tiêu. Nếu không, bạn sẽ gặp sự cố.
  • Hai dấu hai chấm trong didDragInsideControl:withEvent: có nghĩa là hai thông số đang được chuyển đến phương thức didDragInsideControl. Nếu bạn quên thêm dấu hai chấm hoặc nếu bạn không có số tham số chính xác, bạn sẽ gặp sự cố.
  • Cảm ơn this answer để được trợ giúp về sự kiện TouchDragInside.

Truyền dữ liệu khác

Nếu bạn có một số giá trị trong điều khiển tùy chỉnh của bạn

class MyCustomControl: UIControl { 
    var someValue = "hello" 
} 

mà bạn muốn truy cập trong phương pháp hành động mục tiêu, sau đó bạn có thể vượt qua trong một tham chiếu đến sự kiểm soát. Khi bạn đang thiết lập mục tiêu, hãy thêm dấu hai chấm sau tên phương thức hành động. Ví dụ:

myCustomControl.addTarget(self, action: #selector(touchedDown), forControlEvents: UIControlEvents.TouchDown) 

Chú ý rằng nó là touchedDown: (với một dấu hai chấm) và không touchedDown (không có dấu hai chấm). Dấu hai chấm có nghĩa là một tham số đang được chuyển tới phương thức hành động. Trong phương thức hành động, hãy chỉ định tham số là tham chiếu đến lớp con UIControl của bạn. Với tham chiếu đó, bạn có thể lấy dữ liệu từ kiểm soát của mình.

func touchedDown(control: MyCustomControl) { 
    trackingBeganLabel.text = "Tracking began" 

    // now you have access to the public properties and methods of your control 
    print(control.someValue) 
} 

Cách 2: Ủy Pattern và Override Sự kiện cảm ứng

subclassing UIControl cho chúng ta tiếp cận với các phương pháp sau:

  • beginTrackingWithTouch được gọi khi ngón tay đầu tiên chạm xuống trong giới hạn của kiểm soát.
  • continueTrackingWithTouch được gọi nhiều lần khi các ngón tay trượt qua điều khiển và thậm chí nằm ngoài phạm vi kiểm soát.
  • endTrackingWithTouch được gọi khi ngón tay nhấc khỏi màn hình.

Nếu bạn cần kiểm soát đặc biệt các sự kiện chạm hoặc nếu bạn có nhiều thông tin liên lạc dữ liệu để làm với cha mẹ, thì phương pháp này có thể hoạt động tốt hơn sau đó thêm mục tiêu.

Sau đây là cách để làm điều đó:

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

Đây là cách điều khiển xem được thiết lập để được các đại biểu và trả lời các sự kiện chạm từ điều khiển tùy chỉnh của chúng tôi.

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

Ghi chú

  • Để tìm hiểu thêm về mô hình đại biểu, xem this answer.
  • Không nhất thiết phải sử dụng đại biểu với các phương pháp này nếu chúng chỉ được sử dụng trong chính quyền điều khiển tùy chỉnh. Tôi có thể vừa thêm tuyên bố print để cho biết cách các sự kiện đang được gọi. Trong trường hợp đó, các mã sẽ được đơn giản hóa để

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

Phương pháp 3: Sử dụng một Recognizer Gesture

Thêm một nhận dạng cử chỉ có thể được thực hiện trên bất kỳ quan điểm và nó cũng hoạt động trên một UIControl. Để có kết quả tương tự với ví dụ ở trên cùng, chúng tôi sẽ sử dụng một số UIPanGestureRecognizer. Sau đó, bằng cách kiểm tra các trạng thái khác nhau khi một sự kiện được kích hoạt, chúng ta có thể xác định những gì đang xảy ra.

MyCustomControl.swift

import UIKit 
class MyCustomControl: UIControl { 
    // nothing special is required in the control to make it work 
} 

ViewController.nhanh chóng

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

Ghi chú

  • Đừng quên để thêm ruột sau tên phương pháp hành động trong action: "gestureRecognized:". Dấu hai chấm có nghĩa là các thông số đang được chuyển.
  • Nếu bạn cần lấy dữ liệu từ điều khiển, bạn có thể triển khai mẫu đại biểu như trong phương pháp 2 ở trên.
+0

Tôi nghĩ điều này sẽ đủ điều kiện để trở thành câu trả lời được chấp nhận vì nó được nghiên cứu kỹ lưỡng và có liên quan liên quan đến các vấn đề liên quan đến Swift. Trong 7 năm tác giả ban đầu (nếu anh ta vẫn còn sống, xuất hiện không còn trên SO) chưa bao giờ chấp nhận một câu trả lời khác. Có lẽ điều này là dành cho người kiểm duyệt. – Greg

+1

@Greg, Nó thực sự tốt hơn là không có câu trả lời được chấp nhận nếu áp phích ban đầu không còn xung quanh, bởi vì bây giờ câu trả lời này (nếu nó là xứng đáng) cuối cùng có thể tăng lên hàng đầu. – Suragch

0

Obj C

Tôi tìm thấy một bài viết có liên quan từ năm 2014 https://www.objc.io/issues/13-architecture/behaviors/.

Điều thú vị về nó là cách tiếp cận của nó là sử dụng IB và đóng gói logic xử lý sự kiện bên trong một đối tượng được chỉ định (chúng gọi nó là hành vi), do đó loại bỏ logic khỏi view/viewController của bạn làm cho nó nhẹ hơn.

Các vấn đề liên quan