I want to get values from touches began and touches move. I have this code but this is only use for moving not getting value.
override func touchesMoved(touches: Set, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
let touch: UITouch = touches.first as! UITouch
if touch.view == btn1 {
println("touchesMoved")
} else {
println("touchesMoved This is not an ImageView")
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
if touch.view == self.button {
// Do whatever
// get button tag => button.tag
// get button titleLabel => button.titleLabel.text
// get button image => button.imageView.image
} else {
return
}
}
}
Use this.
Related
I'm implementing a passThroughView by creating a transparent View on top and override hitTest().
passThroughView should consume touches from Apple Pencil and if touch type is not from pencil, it pass touches to the view underneath.
The problems are:
parameter "event" in hitTest contains no touch, so I can't check touch type in hitTest
I can get touch from touchesBegan and check touch type, but it get called only after hitTest returned true
I subclass UIWindow and override sendEvent() but this function also called after hitTest (and I don't know why)
class WindowAbleToKnowTouchTypes: UIWindow {
override func sendEvent(_ event: UIEvent) {
if event.type == .touches {
// This get called after Hittest
if event.allTouches!.first!.type == .pencil {
print("This touch is from Apple Pencil")
}
}
super.sendEvent(event)
}
}
Is there anyway to check touchType to decide to pass or consume touches?
I ended up using a different approach, it could be useful for many cases: If I can't get touchType in hitTest(), I can still get the touchType with GestureRecognize:
class CustomGestureRecognizer : ImmediatePanGesture {
var beganTouch : UITouch!
var movedTouch : UITouch!
var endedTouch : UITouch!
override func shouldReceive(_ event: UIEvent) -> Bool {
// You can check for touchType here and decide if this gesture should receice the touch or not
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
// Save touch for later use
if let firstTouch = touches.first {
beganTouch = firstTouch
}
super.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
// Save touch for later use
if let touch = touches.first {
movedTouch = touch
}
super.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
// Save touch for later use
if let touch = touches.first {
endedTouch = touch
}
super.touchesEnded(touches, with: event)
}
}
In the target function of the gestureRecognizer, you can get the UITouch by:
let beganTouch = customGesture.beganTouch
let touchType = beganTouch.touchType
I have a drawing app with a canvas larger than the size of the phone screen. I want to implement scrolling with two fingers and drawing with one finger. So far I can make the scrolling work just fine but when it comes to drawing, the line begins and then the view where the drawing is loses control of the touch such that only the first part of the line is drawn. I think the scrollview takes control back. Dots can be draw just fine.
This is my subclassed UIScrollView
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touches = event?.touches(for: self) else { return }
if touches.count < 2 {
self.next?.touchesBegan(touches, with: event)
} else {
super.touchesBegan(touches, with: event)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touches = event?.touches(for: self) else { return }
if touches.count < 2 {
self.next?.touchesEnded(touches, with: event)
} else {
super.touchesEnded(touches, with: event)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touches = event?.touches(for: self) else { return }
if touches.count < 2 {
self.next?.touchesMoved(touches, with: event)
} else {
super.touchesMoved(touches, with: event)
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touches = event?.touches(for: self) else { return }
if touches.count < 2 {
self.next?.touchesCancelled(touches, with: event)
} else {
super.touchesCancelled(touches, with: event)
}
}
override func touchesShouldCancel(in view: UIView) -> Bool {
if (type(of: view)) == UIScrollView.self {
return true
}
return false
}
You will need a Long Press Gesture Recognizer connected to your ScrollerView, set with Min Duration 0 seconds, also to recognize only 1 Touch and Cancel touches in view option active.
You can find all these options under the Attributes Inspector on Interface Builder.
Please play a little with the Tolerance settings to fine tune the results.
Is it possible to detect touches and get the location of a touch from a UIViewController which is being currently used as previewingContext view controller for 3D Touch? (I want to change the image of within the preview controller when the touch moves from left to right)
I've tried both touchesBegan and touchesMoved none of them are fired.
class ThreeDTouchPreviewController: UIViewController {
func getLocationFromTouch(touches: Set<UITouch>) -> CGPoint?{
guard let touch = touches.first else { return nil }
return touch.location(in: self.view)
}
//Not fired
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let location = getLocationFromTouch(touches: touches)
print("LOCATION", location)
}
//Not fired
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let location = getLocationFromTouch(touches: touches)
print("LOCATION", location)
}
}
Even tried adding a UIPanGesture.
Attempting to replicate FaceBook's 3D Touch feature where a user can move finger from left to right to change the current image being displayed.
Video for context: https://streamable.com/ilnln
If you want to achive result same as in FaceBook's 3D Touch feature you need to create your own 3D Touch gesture class
import UIKit.UIGestureRecognizerSubclass
class ForceTouchGestureRecognizer: UIGestureRecognizer {
var forceValue: CGFloat = 0
var isForceTouch: Bool = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
handleForceWithTouches(touches: touches)
state = .began
self.isForceTouch = false
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
handleForceWithTouches(touches: touches)
if self.forceValue > 6.0 {
state = .changed
self.isForceTouch = true
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
state = .ended
handleForceWithTouches(touches: touches)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
state = .cancelled
handleForceWithTouches(touches: touches)
}
func handleForceWithTouches(touches: Set<UITouch>) {
if touches.count != 1 {
state = .failed
return
}
guard let touch = touches.first else {
state = .failed
return
}
forceValue = touch.force
}
}
and now you can add this gesture in your ViewController in viewDidLoad method
override func viewDidLoad() {
super.viewDidLoad()
let gesture = ForceTouchGestureRecognizer(target: self, action: #selector(imagePressed(sender:)))
self.view.addGestureRecognizer(gesture)
}
Now you can manage your controller UI in Storyboard. Add cover view above UICollectionView and UIImageView in a center and connect it with IBOutlets in code.
Now you can add handler methods for gesture
func imagePressed(sender: ForceTouchGestureRecognizer) {
let location = sender.location(in: self.view)
guard let indexPath = collectionView?.indexPathForItem(
at: location) else { return }
let image = self.images[indexPath.row]
switch sender.state {
case .changed:
if sender.isForceTouch {
self.coverView?.isHidden = false
self.selectedImageView?.isHidden = false
self.selectedImageView?.image = image
}
case .ended:
print("force: \(sender.forceValue)")
if sender.isForceTouch {
self.coverView?.isHidden = true
self.selectedImageView?.isHidden = true
self.selectedImageView?.image = nil
} else {
//TODO: handle selecting items of UICollectionView here,
//you can refer to this SO question for more info: https://stackoverflow.com/questions/42372609/collectionview-didnt-call-didselectitematindexpath-when-superview-has-gesture
print("Did select row at indexPath: \(indexPath)")
self.collectionView?.selectItem(at: indexPath, animated: true, scrollPosition: .centeredVertically)
}
default: break
}
}
From this point you need to customize your view to make it look in same way as Facebook do.
Also I created small example project on GitHub https://github.com/ChernyshenkoTaras/3DTouchExample to demonstrate it
In my AppDelegate, I want to detect when a tap event has been made to the status bar. In order to do so, I need to get the CGPoint from the event. How do I get it from this code?
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
let location = // how to get a CGPoint ????
let statusBarFrame = UIApplication.sharedApplication().statusBarFrame
if CGRectContainsPoint(statusBarFrame, location){
print("Status bar touched")
}else{
print("Not touched")
}
}
You're using Swift 2; you could retrieve the tap location as follows:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent)
{
if let touch = touches.first as? UITouch
{
//The view you would like to get the tap location from.
let tapPoint = touch.locationInView(self.view)
let statusBarFrame = UIApplication.sharedApplication().statusBarFrame
if CGRectContainsPoint(statusBarFrame, tapPoint)
{
print("Status bar touched")
}
else
{
print("Not touched")
}
}
}
A UITouch object has a method locationInView: that allows you to find the location of a touch in a particular view. From the docs:
Returns the current location of the receiver in the coordinate system of the given view.
The view object in whose coordinate system you want the touch located. A custom view that is handling the touch may specify self to get the touch location in its own coordinate system. Pass nil to get the touch location in the window’s coordinates.
So try:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
guard let touch = touches.first else {
return
}
let location = touch.locationInView(nil)
let statusBarFrame = UIApplication.sharedApplication().statusBarFrame
if CGRectContainsPoint(statusBarFrame, location) {
print("Status bar touched")
} else {
print("Not touched")
}
}
This works regardless of device orientation
Swift 5.1
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let touch = touches.first else {
return
}
let location = touch.location(in: nil)
}
You'd have to have a view that you touched. If you've managed to get your app delegate to receive touch events, you could try getting the locationInView: using the app delegates window.
How can one unselect a given segment in a UISegmented control, by pressing the same segment again?
E.g. Press segment 0, it becomes selected and remains highlighted. Press segment 0 again and it becomes unselected and unhighlighted.
The control only fires UIControlEventValueChanged events. Other events don't seem to work with it.
There is a property 'momentary' which when set to YES almost allows the above behavior, excepting that highlighting is only momentary. When momentary=YES pressing the same segment twice results in two UIControlEventValueChanged events, but when momentary=NO only the first press of a given segment results in a UIControlEventValueChanged event being fired. I.e. subsequent presses on the same segment will not fire the UIControlEventValueChanged event.
you can subclass UISegmentedControl:
Swift 3
class ReselectableSegmentedControl: UISegmentedControl {
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let previousSelectedSegmentIndex = self.selectedSegmentIndex
super.touchesEnded(touches, withEvent: event)
if previousSelectedSegmentIndex == self.selectedSegmentIndex {
if let touch = touches.first {
let touchLocation = touch.locationInView(self)
if CGRectContainsPoint(bounds, touchLocation) {
self.sendActionsForControlEvents(.ValueChanged)
}
}
}
}
}
Swift 4
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let previousSelectedSegmentIndex = self.selectedSegmentIndex
super.touchesEnded(touches, with: event)
if previousSelectedSegmentIndex == self.selectedSegmentIndex {
let touch = touches.first!
let touchLocation = touch.location(in: self)
if bounds.contains(touchLocation) {
self.sendActions(for: .valueChanged)
}
}
}
and then
#IBAction func segmentChanged(sender: UISegmentedControl) {
if (sender.selectedSegmentIndex == selectedSegmentIndex) {
sender.selectedSegmentIndex = UISegmentedControlNoSegment;
selectedSegmentIndex = UISegmentedControlNoSegment;
}
else {
selectedSegmentIndex = sender.selectedSegmentIndex;
}
}
Not EXACTLY what you ask for but I was searching for a similar feature and decided on this.
Add doubleTap gesture to UISegmentedControl that sets the selectedSegmentIndex
When you initialize your UISegmentedControl.
let doubleTap = UITapGestureRecognizer(target: self, action #selector(YourViewController.doubleTapToggle))
doubleTap.numberOfTapsRequired = 2
yourSegmentedControl.addGestureRecognizer(doubleTap)
Function to deselect segment
#objc func doubleTapToggle () {
yourSegmentedControl.selectedSegmentIndex = -1
yourSegmentedControl.sendActions(for: valueChanged)
}
Based on #protspace answer, a simpler way, without having to write anything in the #IBAction:
class DeselectableSegmentedControl: UISegmentedControl {
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let previousSelectedSegmentIndex = selectedSegmentIndex
super.touchesEnded(touches, with: event)
if previousSelectedSegmentIndex == selectedSegmentIndex {
let touch = touches.first!
let touchLocation = touch.location(in: self)
if bounds.contains(touchLocation) {
selectedSegmentIndex = UISegmentedControl.noSegment
sendActions(for: .valueChanged)
}
}
}
}
UISegmentedControl can be programmatically deselected with Swift using #IBOutlet weak var mySegmentedControl: UISegmentedControl! and mySegmentedControl.selectedSegmentIndex = -1. As far as detecting the touch of the segment, I do not know. Hopefully someone else will have that answer.