Touch up and Touch down action for UIImageView - ios

what i want to achieve is when user touch on UIImageView set Image1, when user lifts his finger set Image2.
i can only get UIGestureRecognizerState.Ended with this code
var tap = UITapGestureRecognizer(target: self, action: Selector("tappedMe:"))
imageView.addGestureRecognizer(tap)
imageView.userInteractionEnabled = true
func tappedMe(gesture: UITapGestureRecognizer)
{
if gesture.state == UIGestureRecognizerState.Began{
imageView.image=originalImage
dbgView.text="Began"
}else if gesture.state == UIGestureRecognizerState.Ended{
imageView.image=filteredImage
dbgView.text="Ended"
}else if gesture.state == UIGestureRecognizerState.Cancelled{
imageView.image=filteredImage
dbgView.text="Cancelled"
}else if gesture.state == UIGestureRecognizerState.Changed{
imageView.image=filteredImage
dbgView.text="Changed"
}
}

The UITapGestureRecognizer doesn't change it's state to .Began, but the UILongPressGestureRecognizer does. If you for some reason font want to override the touch callbacks directly you could use a UILongPressGestureRecognizer with a very short minimumPressDuration of like 0.1 to achieve the effect.
Example by #Daij-Djan:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var tap = UILongPressGestureRecognizer(target: self, action: Selector("pressedMe:"))
tap.minimumPressDuration = 0
self.view.addGestureRecognizer(tap)
self.view.userInteractionEnabled = true
}
func pressedMe(gesture: UITapGestureRecognizer) {
if gesture.state == .Began{
self.view.backgroundColor = UIColor.blackColor()
} else if gesture.state == .Ended {
self.view.backgroundColor = UIColor.whiteColor()
}
}
}

Here is the solution:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if touch.view == self {
//began
}
}
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if touch.view == self {
//end
}
}
super.touchesEnded(touches, with: event)
}
Note: Put this inside a UIView SubClass and add: userInteractionEnabled = true inside the init block

The best and most practical solution would be to embed your UIImageView within a UIControl subclass.
By default UIImageView has user interaction enabled. If you create a simple UIControl subclass you can easily add your image view into it and then use the following methods to achieve what you want:
let control = CustomImageControl()
control.addTarget(self, action: "imageTouchedDown:", forControlEvents: .TouchDown)
control.addTarget(self, action: "imageTouchedUp:", forControlEvents: [ .TouchUpInside, .TouchUpOutside ])
The advantages of doing it this way is that you get access to all of the different touch events saving you time from having to detect them yourself.
Depending on what you want to do, you could also override var highlighted: Bool or var selected: Bool to detect when the user is interacting with the image.
It's better to do it this way so that your user has a consistent user experience with all the controls in their app.
A simple implementation would look something like this:
final class CustomImageControl: UIControl {
let imageView: UIImageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
// setup our image view
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imageView)
imageView.leadingAnchor.constraintEqualToAnchor(leadingAnchor).active = true
imageView.trailingAnchor.constraintEqualToAnchor(trailingAnchor).active = true
imageView.topAnchor.constraintEqualToAnchor(topAnchor).active = true
imageView.bottomAnchor.constraintEqualToAnchor(bottomAnchor).active = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
final class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let control = CustomImageControl()
control.translatesAutoresizingMaskIntoConstraints = false
control.imageView.image = ... // set your image here.
control.addTarget(self, action: "imageTouchedDown:", forControlEvents: .TouchDown)
control.addTarget(self, action: "imageTouchedUp:", forControlEvents: [ .TouchUpInside, .TouchUpOutside ])
view.addSubview(control)
control.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true
control.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor).active = true
}
#objc func imageTouchedDown(control: CustomImageControl) {
// pressed down on the image
}
#objc func imageTouchedUp(control: CustomImageControl) {
// released their finger off the image
}
}

Related

Swift -How to make a passthrough view recognize a down swipe gesture

I have a second window that has a view with a passthrough view inside of it. The passthrough works fine but I need it to recognize downSwipe gestures while still passing all other touch events to the view below it. How can I do this?
class PassThroughView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
print("Passing all touches to the next view (if any), in the view stack.")
return false
}
}
class MyVC: UIViewController {
lazy var backdropView: PassThroughView = {
let v = PassThroughView(frame: self.view.bounds)
v.backgroundColor = .clear
v.isUserInteractionEnabled = true
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
addGesture()
}
func addGesture() {
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(handleGesture))
swipeDown.direction = .down
backdropView.addGestureRecognizer(swipeDown)
}
#objc func handleGesture(gesture: UISwipeGestureRecognizer) -> Void {
if gesture.direction == .down {
print("Swipe Down")
}
}
}

Tap gesture events on overlapping area

I have a view that is with the black border and it has two different views on it. And these views are overlapping in a small area. And each of them has own UITapGestureRecognizer. When I tap each item's discrete area, the action of that item is triggered. But when I tap the common area, only the second view's action is triggered. I want that both actions have to be triggered. How can I achieve this? Here is my code:
class ViewController: UIViewController {
#IBOutlet weak var view1: UIView!
#IBOutlet weak var view2: UIView!
#IBOutlet weak var outerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
outerView.layer.borderWidth = 2.0
outerView.layer.borderColor = UIColor.black.cgColor
view1.layer.borderWidth = 2.0
view1.layer.borderColor = UIColor.red.cgColor
view2.layer.borderWidth = 2.0
view2.layer.borderColor = UIColor.blue.cgColor
self.initialize()
}
private func initialize(){
let tapGesture1 = UITapGestureRecognizer(target: self, action: #selector(detectTap1(_:)))
let tapGesture2 = UITapGestureRecognizer(target: self, action: #selector(detectTap2(_:)))
self.view1.addGestureRecognizer(tapGesture1)
self.view2.addGestureRecognizer(tapGesture2)
}
#objc func detectTap1(_ gesture : UITapGestureRecognizer) {
print("detectTap1")
}
#objc func detectTap2(_ gesture : UITapGestureRecognizer) {
print("detectTap2")
}
}
Kindly share your suggestions.
For this problem i have found this solution, maybe is not the best solution but it works, i will look for further improvements anyway
I had subclassed UIGestureRecognizer class
Updated
import UIKit
import UIKit.UIGestureRecognizerSubclass
class CustomGestureRecognizer: UIGestureRecognizer {
var anotherGestureRecognizer : CustomGestureRecognizer?
private var touchBeganSended : Bool = false
private var touchLocation : CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if let validTouch = touches.first?.location(in: self.view) {
if (self.view!.point(inside: validTouch, with: event)) {
if(!touchBeganSended) {
touchBeganSended = true
touchLocation = validTouch
anotherGestureRecognizer?.touchesBegan(touches, with: event)
state = .recognized
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
if let validTouch = touches.first?.location(in: self.view) {
if (self.view!.point(inside: validTouch, with: event)) {
if(touchBeganSended) {
touchBeganSended = false
anotherGestureRecognizer?.touchesEnded(touches, with: event)
state = .recognized
}
}
}
}
override func location(in view: UIView?) -> CGPoint {
if let desiredView = view {
if(desiredView == self.view) {
return touchLocation ?? CGPoint(x: 0, y: 0)
} else {
return super.location(in: view)
}
} else {
return super.location(in: view)
}
}
}
Updated
then you need to modify your initialize() method to this one, with the last update you don't need to take into account which view is on top on view hierarchy
private func initialize(){
let tapGesture1 = CustomGestureRecognizer(target: self, action: #selector(detectTap1(_:)))
let tapGesture2 = CustomGestureRecognizer(target: self, action: #selector(detectTap2(_:)))
tapGesture1.cancelsTouchesInView = true
tapGesture1.delegate = self
tapGesture2.cancelsTouchesInView = true
tapGesture2.delegate = self
self.view1.addGestureRecognizer(tapGesture1)
tapGesture1.anotherGestureRecognizer = tapGesture2
tapGesture2.anotherGestureRecognizer = tapGesture1
self.view2.addGestureRecognizer(tapGesture2)
}
this works as you can see here
Try the following:
private func initialize(){
let tapGesture1 = UITapGestureRecognizer(target: self, action: #selector(detectTap1(_:)))
let tapGesture2 = UITapGestureRecognizer(target: self, action: #selector(detectTap2(_:)))
tapGesture1.cancelsTouchesInView = false
tapGesture2.cancelsTouchesInView = false
self.view1.addGestureRecognizer(tapGesture1)
self.view2.addGestureRecognizer(tapGesture2)
}
When you set
gesture.cancelsTouchesInView = false
it propagates the gesture to the views underneath.
Try to implement this UIGestureRecognizerDelegate method:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
...
...
private func initialize() {
...
gesture1.delegate = self
gesture2.delegate = self
...
}
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
// its up to you
//guard otherGestureRecognizer == yourAnotherGesture else { return false }
return true
}

UILongPressGestureRecognizer over UITextField in swift

Is there away to use a UILongPressGestureRecognizer over a UITextField without triggering field edit while still being able to edit the textfield on a regular tap?
I have tried adding a long press gesture recognizer to the UITextField but the long press seems to only work a fraction of the time.
init(frame: CGRect, userCompany: WLUserCompany) {
super.init(frame: frame)
var textField: UITextField?
var longPress = UILongPressGestureRecognizer(target: self, action: #selector(self.longPress(gesture:)))
textField?.addGestureRecognizer(longPress)
self.addSubview(textField!)
}
#objc func longPress(gesture: UILongPressGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.began {
print("Long Press")
}
}
Create subclass of UIGestureRecognizer
import UIKit.UIGestureRecognizerSubclass
class TouchGestureRecognizer: UIGestureRecognizer {
var isLongPress: Bool = false
fileprivate var startDateInterval: TimeInterval = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
state = .began
self.startDateInterval = Date().timeIntervalSince1970
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
state = .ended
self.isLongPress = (Date().timeIntervalSince1970 - self.startDateInterval) > 1.0
}
}
Add gesture recognizer to your textField
let gesture = TouchGestureRecognizer(target: self, action: #selector(ViewController.textFiledPressed(gesture:)))
self.textField?.addGestureRecognizer(gesture)
And now you can check in textFiledPressed(gesture:) function if it's long press or not
func textFiledPressed(gesture: TouchGestureRecognizer) {
switch gesture.state {
case .ended:
if gesture.isLongPress {
//Do whatever you need
} else {
self.textField?.becomeFirstResponder()
}
default: break
}
}
After looking at similar SO here and here, and experimenting myself, I don't think this is possible -- at least the way you describe your intent.
I was able to add a background view with gesture control, but the gesture conflicts with the user interaction on the text field. Wouldn't a button to the side of the textfield create a better user experience?
FWIW, here is the code to add a background UIView using a double tap. a long press gesture had the same result
#IBOutlet weak var backgroundView: UIView!
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.myTextFieldTapped))
tapGesture.numberOfTapsRequired = 2
backgroundView.addGestureRecognizer(tapGesture)
}
#objc func myTextFieldTapped() {
print("Double tapped on textField")
}
Here is an image of the storyboard:

How to click a button lying below UITableView

say, I have a button lying under UITableView, how can I click the button through the UITableViewCell but do not trigger the cell click event:
The reason I put the button behind the tableview is that I want to see and click the button under the cell whose color set to be clear, and when I scroll the table, the button can be covered by cell which is not with clear color
I created a sample project and got it working:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let tap = UITapGestureRecognizer(target: self, action: #selector(TableViewVC.handleTap))
tap.numberOfTapsRequired = 1
self.view.addGestureRecognizer(tap)
}
func handleTap(touch: UITapGestureRecognizer) {
let touchPoint = touch.locationInView(self.view)
let isPointInFrame = CGRectContainsPoint(button.frame, touchPoint)
print(isPointInFrame)
if isPointInFrame == true {
print("button pressed")
}
}
To check of button is really being pressed we need to use long tap gesture:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let tap = UILongPressGestureRecognizer(target: self, action: #selector(TableViewVC.handleTap))
tap.minimumPressDuration = 0.01
self.view.addGestureRecognizer(tap)
}
func handleTap(touch: UILongPressGestureRecognizer) {
let touchPoint = touch.locationInView(self.view)
print(" pressed")
if touch.state == .Began {
let isPointInFrame = CGRectContainsPoint(button.frame, touchPoint)
print(isPointInFrame)
if isPointInFrame == true {
print("button pressed")
button.backgroundColor = UIColor.lightGrayColor()
}
}else if touch.state == .Ended {
button.backgroundColor = UIColor.whiteColor()
}
}
Get the touch point on the main view. Then use following method to check the touch point lies inside the button frame or not.
bool CGRectContainsPoint(CGRect rect, CGPoint point)
You can write your custom view to touch button or special view behind the topview
class MyView: UIView {
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
for subview in self.subviews {
if subview is UIButton {
let subviewPoint = self.convertPoint(point, toView: subview)
if subview.hitTest(subviewPoint, withEvent: event) != nil { // if touch inside button view, return button to handle event
return subview
}
}
}
// if not inside button return nomal action
return super.hitTest(point, withEvent: event)
}
}
Then set your controller view to custom MyView class

How to Refer to a Subview Button of Custom UIView in UITableViewController

I'm facing problems with UITableViewCell being reused, and I am receiving suggestions to make the action of a UIButton occur within the UITableViewController.
The problem is that because the UIButton is instantiated within the custom UIView subclass, I cannot find a way to refer to it.
The code is:
class StarButton: UIView {
var buttonRow : Int = 0
var buttonSelected : Bool = false
override init (frame : CGRect)
{
super.init(frame : frame)
initStar()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
initStar()
}
func initStar(){
let filledStarImage = UIImage(named: "filledStar")
let emptyStarImage = UIImage(named: "emptyStar")
let button = UIButton(frame: CGRect(x: 2, y: 2, width: 33, height: 33))
button.userInteractionEnabled = true
button.addTarget(self, action: #selector(StarButton.fillingStar(_:)), forControlEvents: UIControlEvents.TouchUpInside)
button.setImage(emptyStarImage, forState: .Normal)
button.setImage(filledStarImage, forState: .Selected)
if buttonSelected == true{
button.selected = true
}
addSubview(button)
}
//Could have various errors
func fillingStar(sender: UIButton){
if (sender.selected) == false{
FavoritesManager.favoritesList.setObject(ChemQuizCollection.wordListObject.quizList[buttonRow], forKey: "\(buttonRow)")
sender.selected = !sender.selected
FavoritesManager.favoritesList.synchronize()
} else{
FavoritesManager.favoritesList.removeObjectForKey("\(buttonRow)")
sender.selected = !sender.selected
FavoritesManager.favoritesList.synchronize()
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
for view in subviews
{
view.touchesBegan(touches, withEvent: event)
}
}
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool
{
return true
}
}
This is the custom UIView subclass. I want to refer to the "button" in func initStar().
Using
for subview in instantiatedStarButton.subviews{
subview.selected = true
}
throws an error saying UIView doesn't have member "selected". Is there a better way?
Thank you all in advance.
The problem is with your code
StarButton.subviews{ subview.selected = true }
.subviews give UIView not UIButton: UIView has no .selected property
What should be done is following code or something similar. You have to cast tour UIView to UIButton
let filtured = instantiatedStarButton.subviews.filter({
if let button = $0 as? UIButton{
return button.selected
}
return false
})
buttonSelected is set to false while you are only changing the state of the button if its true. That's why you're getting the error.
You can either change the buttonSelected to true, or even better assign a tag to the button and compare that tag with the 'sender' in fillingStar.

Resources