Subclass Uiscrollview in Swift for Touches Began & Touches Moved

Subclass UIScrollView in Swift for touches Began & touches Moved

You're thinking about this wrong. If you want to know when a UIScrollView is moving, there's no need to subclass it. iOS has set up all the methods you need inside of the UIScrollViewDelegate.

You must implement the UISCrollViewDelegate to be notified about the actions in the UIScrollView and set the delegate by Interface Builder or in code, is up to you.

See the following example to know how to do it for example :

class ViewController: UIViewController, UIScrollViewDelegate {

@IBOutlet weak var scrollView: UIScrollView!

override func viewDidLoad() {
super.viewDidLoad()
self.scrollView.delegate = self
}
}

Still if you want to know how its touched in the way you explained above you must follow the following steps :

  1. You must subclass from the UIScrollView class as you did in your class PassTouchesScrollView and implement the delegate pattern to be notified about the UIScrollView in the following way:

    import UIKit

    protocol PassTouchesScrollViewDelegate {
    func touchBegan()
    func touchMoved()
    }

    class PassTouchesScrollView: UIScrollView {

    var delegatePass : PassTouchesScrollViewDelegate?

    override init(frame: CGRect) {
    super.init(frame: frame)
    }

    required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    }

    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {

    // Notify it's delegate about touched
    self.delegatePass?.touchBegan()

    if self.dragging == true {
    self.nextResponder()?.touchesBegan(touches, withEvent: event)
    } else {
    super.touchesBegan(touches, withEvent: event)
    }

    }

    override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {

    // Notify it's delegate about touched
    self.delegatePass?.touchMoved()

    if self.dragging == true {
    self.nextResponder()?.touchesMoved(touches, withEvent: event)
    } else {
    super.touchesMoved(touches, withEvent: event)
    }
    }
    }
  2. Your class ViewController must be implemented in the following way:

    class ViewController: UIViewController, UIScrollViewDelegate {

    @IBOutlet weak var scrollView: PassTouchesScrollView!

    override func viewDidLoad() {
    super.viewDidLoad()
    scrollView.delegate = self
    scrollView.delegatePass = self
    }

    override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
    }

    func touchMoved() {
    println("touch moved")
    }

    func touchBegan() {
    println("touch began")
    }

    }
  3. You must be in the Interface Builder select your UIScrollView and set the class for it as your class PassTouchesScrollView, in the Identity Inspector in the class part.

And you should see in your console the following:

touch began
touch began
touch began
touch began
touch began
touch began
touch moved
touch move

I hope this help you.

How to handle touches by an UIView on top of a UIScrollView and the UIScrollView both at the same time

Following this answer worked for me, though with some slight changes. Do note that for this solution, the UIScrollView is on top of the UIView. Firstly, you need to create a subclass of UIScrollView and override the touch methods.

protocol CustomScrollViewDelegate {
func scrollViewTouchBegan(_ touches: Set<UITouch>, with event: UIEvent?)
func scrollViewTouchMoved(_ touches: Set<UITouch>, with event: UIEvent?)
func scrollViewTouchEnded(_ touches: Set<UITouch>, with event: UIEvent?)
}

class CustomScrollView: UIScrollView {

var customDelegate: CustomScrollViewDelegate?

override func awakeFromNib() {
super.awakeFromNib()

for gesture in self.gestureRecognizers ?? [] {
gesture.cancelsTouchesInView = false
gesture.delaysTouchesBegan = false
gesture.delaysTouchesEnded = false
}
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
customDelegate?.scrollViewTouchBegan(touches, with: event)
super.touchesBegan(touches, with: event)
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
customDelegate?.scrollViewTouchMoved(touches, with: event)
super.touchesMoved(touches, with: event)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
customDelegate?.scrollViewTouchEnded(touches, with: event)
super.touchesEnded(touches, with: event)
}
}

Take note of the code in awakeFromNib(). UIScrollView has its own set of gestures. So for each gesture, delaysTouchesBegan and delaysTouchesEnded needs to be false to prevent delays for the touch events.

Finally, just assign the delegate to your ViewController and implement the methods like so.

class ViewController: UIViewController {

@IBOutlet weak var scrollView: CustomScrollView!
@IBOutlet weak var touchView: UIView!

override func viewDidLoad() {
super.viewDidLoad()
scrollView.customDelegate = self
}
}

extension ViewController: CustomScrollViewDelegate {
func scrollViewTouchBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Check if touch location is within the bounds of the UIView
if let touch = touches.first {
let position = touch.location(in: view)
if touchView.bounds.contains(position) {
print("Began")
}
}
}

func scrollViewTouchMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// Check if touch location is within the bounds of the UIView
if let touch = touches.first {
let position = touch.location(in: view)
if touchView.bounds.contains(position) {
print("Moved")
}
}
}

func scrollViewTouchEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// Check if touch location is within the bounds of the UIView
if let touch = touches.first {
let position = touch.location(in: view)
if touchView.bounds.contains(position) {
print("Ended")
}
}
}
}

UIScrollView and touchesBegan / touchesEnded etc

You have at least 2 options:

1) "By setting userInteractionEnabled to NO for your scroll view".
More info here https://stackoverflow.com/a/25108078/1702413

2) subclass your UIScrollView - for example with this https://dl.dropboxusercontent.com/u/19438780/ClassScrollViewDelegate.zip

UPDATE

Why? Please, read again the documentation:

"Because a scroll .. it must know whether a touch signals an intent
to scroll ... it temporarily intercepts a touch-down event ... .. the
scroll view cancels any tracking in the subview and performs the
scrolling itself"

And most important: **

Subclasses can override the
touchesShouldBegin:withEvent:inContentView:, pagingEnabled, and
touchesShouldCancelInContentView: methods

From here: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollView_Class/index.html#//apple_ref/doc/uid/TP40006922

touches methods not getting called on UIView placed inside a UIScrollView

Try setting canCancelContentTouches of the scrollView to NO and delaysContentTouches to YES.

EDIT:

I see that similiar question was answered here Drag & sweep with Cocoa on iPhone (the answer is exactly the same).

If the user tap-n-holds the signView (for about 0.3-0.5 seconds) then view's touchesBegan: method gets fired and all events from that moment on go to the signView until touchesEnded: is called.

If user quickly swipes trough the signView then UIScrollView takes over.

Since you already have UIView subclassed with touchesBegan: method implemented maybe you could somehow indicate to user that your app is prepared for him to sign ('green light' equivalent).

You could also use touchesEnded: to turn off this green light.

It might be better if you add signImageView as as subView of signView (instead of to customScrollView) and hide it when touchesBegan: is fired). You would add signView to customScrollview at the same place where you add signImageView in existing code instead.

With this you achieve that there is effectively only one subView on that place (for better touch-passing efficiency. And you could achieve that green light effect by un-hiding signImageView in touchesBegan:/touchesEnded:

If this app-behaviour (0.3-0.5s delay) is unacceptable then you'd also need to subclass UIScrollView. There Vignesh's method of overriding UIScrollView's touchesShouldBegin: could come to the rescue. There you could possibly detect if the touch accoured in signView and pass it to that view immediately.

Touch events on subclass of UIView as a subview of UIScrollView

There are several approaches you could try:

  1. Try setting the below properties on the UIScrollView:

    scrollView.delaysContentTouches = NO;
    scrollView.canCancelContentTouches = NO;

    See similar SO questions/answers here, here.

  2. Implement hitTest:withEvent:. See here, here.

  3. Use a UIGestureRecognizer. See here, here.

I would personally recommend using a UIGestureRecognizer, but it depends on your specific situation (any of these options may work fine for you).

UIScrollView touchesBegan

I think you will have to subclass UIScrollView to be able to do this.

touchesBegan:withEvent: is only sent to subclasses of UIView. You are problably implementing touchesBegan:withEvent: in your controller, right? If so, it won't work from there...

Alternatively, if you put a subclass of UIView (that you wrote) in your UIScrollView, you can also catch the touchesBegan event from there (but only when the user touches that particular subview).
UIScrollView passes on touches to its subviews by default (see touchesShouldBegin:withEvent:inContentView: in UIScrollView).

UIScrollView sending touches to subviews

Note: Thank you all for your contributions, specially to Aaron Hayman.

I was able to figure it out by doing the following on the UIScrollView sub-class I had:

-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint pointOfContact = [gestureRecognizer locationInView:self];

// The view with a tag of 200 is my A view.
return (![[self hitTest:pointOfContact withEvent:nil] isEqual:[self viewWithTag:200]]);
}


Related Topics



Leave a reply



Submit