I have a nested UIViewController that covers half of the screen.
Under this controller there is the parent ViewController.
I would like to propagate only the lateral swipe events to the bottom (parent) controller.
I think I should use the following func
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return (my test)
})
}
but I also know that this func should be applied to a UIView, not to a UIViewController. So I'm stuck, and I don't know how to proceed.
You can create
class SomeView:UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return true/false
}
}
and assign it to the child's vc'view in IB , you can also implement this inside the vc
override func touchesBegan(_ touches: Set<AnyHashable>, withEvent event: UIEvent) {
var touch: UITouch? = touches.first
//location is relative to the current view
// do something with the touched point
if touch?.view == childVC.view {
}
}
Edit:
to propagate swipe actions to parent add a delegate and swipe actions to the child and when triggered notify the parent with the delegate
Related
What's the best way to determine whether there is any active touch happening in a view hierarchy? Something like:
view.hasTouches
returns true if view or any of its descendant has active touches.
To those who would suggest using UIResponder's touch event handling API like touchesBegan, notice that view doesn't receive these calls if one of its descendant is handling the touch events.
You can use touchesBegan and touchesEnded methods in UIViewController as follows:
class ViewController: UIViewController {
var isTouching = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = true
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = false
}
}
The isHighlighted property on UIButtonis mostly analogous to whether the button is pressed or not.
The Apple docs are vague about it:
Controls automatically set and clear this state in response to
appropriate touch events.
https://developer.apple.com/documentation/uikit/uicontrol/1618231-ishighlighted
Which method is the system calling to set this?
It appears to be touchesBegan, because you can override this method to prevent it.
But, surprisingly, if you manually call touchesBegan it doesn't get set (for example, from a superview-- see code below).
So it seems it's not simple as touchesBegan. I've tried overriding every method I could...pressesBegan, pressesChanged, pressesCancelled, pressesEnded, touchesCancelled, beginTracking, continueTracking, hitTest, etc.
Only touchesBegan had any effect.
Here's how I tried calling touchesBegan from a superview:
class MyView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return self
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: self) else { return }
for view in self.subviews {
if let button = view as? UIButton {
if button.frame.contains(point) {
button.touchesBegan(touches, with: event)
print("\(button.isHighlighted)") //prints false
}
}
}
}
}
I really need touchesMoved(...) method in UIScrollView. I know that there is a method scrollViewDidScroll(...), but I really need the other method.
So I create subclass of UIScrollView and implement those methods.
class MyScrollView: UIScrollView {
...
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
print("Move")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
print("Touch")
}
}
If I click on the scroll view I get (let say I click two times) :
Touch
Touch
If I click on scroll view and wait a second and then move my finger I get :
Touch
Move
Move
But then scroller start to scroll and I don't receive anything anymore.
Why scroll gesture doesn't call those methods?
How do I persuade scroll view to call those methods on scroll?
I'm building my own UIGestureRecognizer subclass in order to combine the tap and swipe gestures and track the direction of their swiping motion. In this intended gesture, the user would touch the screen, let their finger bounce fully off the screen one or more times before returning their finger to the screen and dragging; that is a tap followed immediately by a swipe.
I have been able to get the actual gesture portion of this recognizer to work properly, but where I am finding issue is in storing the trail of points where the user's swipe has traveled.
First, I initialize an array of CGPoints as a class member of my custom recognizer class
class UITapSwipeGestureRecognizer: UIGestureRecognizer {
...
var trail: [CGPoint] = []
Then, in touchesBegan(_, with), I first clear the array if not empty, and feed the starting point into the array
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
...
if !trail.isEmpty { // EXC_BAD_ACCESS error thrown here
trail.removeAll(keepingCapacity: true)
}
trail.append(location(in: view?.window))
...
}
I would go on to discuss my usage in touchesMoved(_, with), but this is enough to trigger my problem. Upon the first attempt to access the array, at !trail.isEmpty, I get the following error:
Thread 1 EXC_BAD_ACCESS (code=1, address=0x10)
Do I need to make a thread safe version of this array? If so, how should I go about doing this? Or am I just going about this entirely the wrong way?
I have been able to replicate your error, by adding the Gesture Recognizer through the StoryBoard.
When I add it programatically, I don't get the error.
Here's the Gesture Recognizer class
import UIKit
import UIKit.UIGestureRecognizerSubclass
class MyGestureClass: UIGestureRecognizer
{
var trail: [CGPoint] = []
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
state = .began
if !trail.isEmpty {
trail.removeAll(keepingCapacity: true)
}
trail.append(location(in: view?.window))
print(trail.count) // Just for debugging
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
trail.append(location(in: view?.window))
print(trail.count) // Just for debugging
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches , with: event)
state = .ended
print("Finished \(trail.count)") // Just for debugging
}
}
and here's how I access it from the View Controller
override func viewDidLoad()
{
super.viewDidLoad()
let myRecognizer = MyGestureClass(target: self, action: #selector(self.tap(_:)))
self.view.addGestureRecognizer(myRecognizer)
}
func tap(_ sender: UITapGestureRecognizer)
{
// No need to do anything here, but appears to be required
}
I have subclassed a UITableView in my app so that I can intercept touch events. I am using this to allow me to provide 3D Touch gestures on the entire view (including on top of the table view).
This works great, however the problem is that using 3D Touch on one of the cells and then releasing your finger activates the cell tap.
I need to only activate the cell tap if there is no force exerted. I should explain that I am fading an image in gradually over the entire screen as you apply pressure.
Here is my subclass:
protocol PassTouchesTableViewDelegate {
func touchMoved(touches: Set<UITouch>)
func touchEnded(touches: Set<UITouch>)
}
class PassTouchesTableView: UITableView {
var delegatePass: PassTouchesTableViewDelegate?
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesMoved(touches, withEvent: event)
self.delegatePass?.touchMoved(touches)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
self.delegatePass?.touchEnded(touches)
}
}
And here are the methods I'm calling from my view controller when the touches end and move:
internal func touchMoved(touches: Set<UITouch>) {
let touch = touches.first
self.pressureImageView.alpha = (touch!.force / 2) - 1.0
}
internal func touchEnded(touches: Set<UITouch>) {
UIView.animateWithDuration(0.2, animations: {
self.pressureImageView.alpha = 0.0
})
}
You could create a boolean named isForceTouch which is set to false in touchesBegan, and then once force touch is detected, set it to true. Then in didSelectRowAtIndexPath just return false if isForceTouch is true. It may need tweaking but that should work.