I have a slider below webView. What will be shown on the webView depends on slider's value. I am sliding and it works OK. I want to prevent my app, or more precisely my webView, doing anything while my finger is on slider(while I am moving a slider's circle). Then, after I finish sliding(remove my finger out of the slider's circle), app/webView should be able to do other things. Is this possible?
#IBAction func sliderButton(_ sender: Any) {
loadWebView()
}
sliderButton() method triggers when user is sliding, but I want loadWebView() method to be called only after user stops sliding.
For slider, you can do something like this,
sliderButton.addTarget(self, action: #selector(onSliderValChanged(slider:event:)), for: .valueChanged)
#objc func onSliderValChanged(slider: UISlider, event: UIEvent) {
if let touchEvent = event.allTouches?.first {
switch touchEvent.phase {
case .began:
// handle drag began
print("handle drag began")
break
case .moved:
// handle drag moved
print("handle drag moved")
break
case .ended:
// handle drag ended
print("handle drag ended")
//do for `WKWebView`'s user interaction
break
default:
break
}
}
}
Related
I have a view that has a LongPressGestureRecognizer assigned to it which calls the following method:
#IBAction func longPressOnView1Recognized(_ sender: UIGestureRecognizer) {
if sender.state == .began {
// this runs when user's finger is down a "long time"
}
if sender.state == .ended {
// this runs when user's finger goes up again after the .began state
}
}
This all works as expected, but I'm trying to find a (good/proper) way of being able to programmatically cancel the long press recognizer (in certain circumstances) while the user's finger is still down.
That is, while a user's finger is still down on the view, and the recognizer has entered the .began state, (but before the user has lifted their finger -- before recognizer enters the .ended state)... is there some code we can run that will prevent the method above from firing when the user lifts their finger... like prematurely telling IOS to no longer listen for UP events for the remainder of this gesture?
I've read these docs, but I don't have that much experience with IOS touch, and I can't seem to find any method that is designed for this purpose.
my GestureRecognizer.reset() does not seem to do what I'm describing.
I can think of two possibilities:
1) A boolean flag, that would go inside the if sender.state == .ended {} closure
2) this:
myLongPressRecognizer.isEnabled = false
myLongPressRecognizer.isEnabled = true
Both of these work but seem not so great.
You are all good with disabling and reenabling the gesture recognizer so doing
myLongPressRecognizer.isEnabled = false
myLongPressRecognizer.isEnabled = true
is completely correct.
What I am worried about is you don't completely understand gesture recognizers. You should always use switch statement when handling gesture recognizer. Check the comments:
func handleLongPressGestureRecognizer(_ sender: UIGestureRecognizer) {
switch sender.state {
case .began:
// This will be called only once when the gesture starts
print("Long press did begin at \(sender.location(in: sender.view))")
case .changed:
// This will be called whenever your finger moves (at some frequency obviously).
// At this point your long press gesture is acting exactly the same as pan gesture
print("Long press changed position to \(sender.location(in: sender.view))")
case .ended:
// This is when user lifts his finger assuming the gesture was not canceled
print("Long press ended at \(sender.location(in: sender.view))")
case .cancelled:
// This is equally important as .ended case. You gesture may be canceled for many reasons like a system gesture overriding it. Make sure to implement logic here as well.
print("Long press canceled at \(sender.location(in: sender.view))")
case .failed, .possible:
// These 2 have been added additionally at some point. Useless as far I am concerned.
break
}
}
So at least you should handle cancelled status. But also note that the changed status will be triggered whenever the gesture is moved.
You have your solution on hand already. Toggling the UILongPressGestureRecognizer isEnabled is the best way to go. Setting the state property isn't possible, because it's a get-only property.
open var state: UIGestureRecognizer.State { get } // the current state of the gesture recognizer
isEnabled property is documented as:
default is YES. disabled gesture recognizers will not receive touches. when changed to NO the gesture recognizer will be cancelled if it's currently recognizing a gesture.
You can import the gesture recognizer header:
import UIKit.UIGestureRecognizer
That will make the state property a readwrite property. Thus, to cancel the gesture, just change its state to .cancelled.
So, for example, you can cancel the long press gesture recognizer one second after it was recognized with something like:
weak var timer: Timer?
#objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
print("began")
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
gesture.state = .cancelled
}
case .ended, .cancelled:
print(gesture.state == .ended ? "Ended" : "Cancelled")
timer?.invalidate()
default:
break
}
}
I have a table view that doesn't cover the whole screen (It's kind of like a drawer from the bottom of the screen). When the user scrolls down to the end of the content I want to stop the scrolling and then add a pan gesture recognizer. I do this like so:
// MARK: UIScrollViewDelegate Methods
extension TutorProfileVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Limit top vert bounce
guard mode == .drawer else { return }
if scrollView.contentOffset.y < -80.0 {
scrollView.contentOffset = CGPoint(x: 0, y: -80.0)
tableView.addGestureRecognizer(tablePanGR)
}
}
}
The gesture has been added but won't register until the user touches the screen again. Their finger is already on the tableview. Is it possible to start the gesture without them having to touch the screen again?
I think you have same problem with this question. Take a look at it if you want to see a code sample.
To resolve problem, you should add gesture from beginning but only handle gesture action when user scrolls to bottom. So you don't need to touch the screen again because gesture is started when you begin scrolling. The method to handle gesture will look like below
#objc func handlePanGestureRecognizer(_ gestureRecognizer: UIPanGestureRecognizer) {
switch gestureRecognizer.state {
case .began:
// Do nothing
break
case .changed:
let translation = gestureRecognizer.translation(in: gestureRecognizer.view!.superview!)
let velocity = gestureRecognizer.velocity(in: gestureRecognizer.view!.superview)
let state = gestureRecognizer.state
// Don't do anything until |scrollView| reached bottom
if scrollView.contentOffset.y >= -80.0 {
return;
}
// Do whatever you want with |scrollView|
}
break;
case .cancelled:
case .ended:
case .failed:
default:
break;
}
}
Also implement gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: to make gesture and scroll view work together
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
I have a view containing three buttons.
one button starts audio being recorded, the other button stops the audio being recorded and the third button plays the recorded audio.
I want to condense the record start and stop into one button so that when pressed it records but then when released it stops recording.
I tried doing this by using two events on one button with touch down to start the recording and then touch up inside to stop it, however it doesn't seem to work properly.
Rather than using a UIButton, you should create a view and style it to look like the button you want.
Then rely on the touches began & ended methods to provide the functionality you want.
In this particular case, I create a custom view class that you can use.
The following code is for Swift 3.0:
class TouchView : UIView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// Start recording
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
// End recording
}
}
Use a UIButton to which you have attached a UILongPressGestureRecognizer. You get a .began message when the recognizer decides that the user is holding a finger down on the view, and an .ended message when the finger is lifted. Set the button's isSelected to indicate the "down" state:
#IBAction func doPress(_ sender: UILongPressGestureRecognizer) {
switch sender.state {
case .began:
(sender.view as! UIControl).isSelected = true
// start recording
case .ended:
(sender.view as! UIControl).isSelected = false
// stop recording
default: break
}
}
I am trying my hands on Apple TV.
I have implemented this below method however with press on right, left, down, up only "Select" button event is fired.
override func pressesBegan(presses: Set<UIPress>, withEvent event: UIPressesEvent?) {
for press in presses {
switch press.type {
case .UpArrow:
print("Up Arrow")
case .DownArrow:
print("Down arrow")
case .LeftArrow:
print("Left arrow")
case .RightArrow:
print("Right arrow")
case .Select:
print("Select")
case .Menu:
print("Menu")
case .PlayPause:
print("Play/Pause")
}
}
}
How can I find the events here?
When I drag the view and let it go in a new location .Ended is called everytime.
When I drag the view for a while and let it go in the same location it started, nothing is called, I expect .Ended or default to be called but doesn't happen.
Why does this happen? How can I learn when the user let go of the view consistently?
var gestureRecognizer = UIPanGestureRecognizer(target: self, action: Selector("dragged:"))
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
func dragged(gesture: UIPanGestureRecognizer) {
switch gesture.state {
case UIGestureRecognizerState.Began:
print("calls this everytime touch began")
case UIGestureRecognizerState.Ended:
print("doesn't call this everytime")
default:
print("never calls this")
}
}
You should almost always use UIGestureRecognizerState.Ended || UIGestureRecognizerState.Cancelled as one of the two will definitely be called at the end of a gesture. This way you can also handle cases where the user has dragged past the screen.
This happened to me when I had a case for .recognized in my switch statement (don't do this).
In my case this happened only when I used two-finger pinch or rotate gestures in the Simulator, using the option key on the keyboard.
Usually pressing the option key once more after the gesture has ended (without moving the mouse) seems to resolve the issue and the .ended state is reached.
I did not encounter this problem on an actual device.