To set the stage, I have a 4x5 grid of UIImageViews that I would like to flip and show a new image upon tap. I'd like to accomplish this with a single #IBaction. I'm simply having a bit of trouble referencing the selected UIIMageView when a tap is recognized. I'm sure it's something very simple. I'm just now starting to work with UITapGestureRecognizer, so I don't know all the ins and outs just yet. Here's the #IBAction I'm trying to use:
#IBAction func tileTapped(recognizer: UITapGestureRecognizer, _ sender: UIImageView) {
print("Tile Tapped: \(sender.tag)")
}
My print statement is giving me the following no matter what UIImageView is tapped:
Tile Tapped: 0
My tags are set up to reference the row and column they fall in. For example, the tags for my first row are:
00, 01, 02, 03
My biggest challenge is simply retrieving the tag for the appropriate UIIMageView. Once I figure that out, I should be solid.
Thanks in advance!
That is not a valid function for a gesture recognizer. See the documentation for UIGestureRecognizer on the possible signatures.
To access the view associated with the gesture, access the gesture's view property.
#IBAction func tileTapped(_ recognizer: UITapGestureRecognizer) {
if let view = recognizer.view as? UIImageView {
print("Tile Tapped: \(view.tag)")
}
}
Related
I have got a wide LineChart with many entries. I want to let user tap (or better longtap/3D touch) on an entry to show modal card where user could edit data entry. I tried implementing chartValueSelected but the problem is that it runs even when user taps to scroll (i e taps without releasing finger) which is not how a button should behave. Is there any way to implement tap recognizing for LineChart label?
It appears that overriding the tap gesture recognizer for the chart can work. This question has some answers based on someone who was looking for a similar solution.
You can attach your own gesture recognizer to LineChartView and use method getHighlightByTouchPoint to get information about the selected point.
override func viewDidLoad() {
// ...
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(onLongTap))
lineChartView.addGestureRecognizer(longTapRecognizer)
// ...
}
#objc func onLongTap(recognizer: UILongPressGestureRecognizer) {
if recognizer.state == .ended {
let highlight = lineChartView.getHighlightByTouchPoint(recognizer.location(in: lineChartView))
print("\(highlight)")
}
}
I have a cell with a button on it. When I press the button, I start an animation indicating something is preparing. I do this within the #IBAction function like so: (this is in my custom tableViewCell function).
#IBAction func playNowTapped(_ sender: UIButton) {
let loadingShape = CAShapeLayer()
//Some animating of the shape
}
I define this shape within the #IBAction as the process should repeat if I press the button again
However, because only the necessary cells displayed on the device are loaded in one chunk in the cellForRowAt function for the tableView, my animation repeats every few cells if I scroll down while the animation is loading.
What I have done so far is append to a list of all the previously pressed buttons in my by defining a function and calling it in the #IBAction function of the button like so:
func findCell() {
//Iterate tableView list and compare to current cellText
for value in list {
if value == cellText.text {
//If found, checking if value is already stored in pressedBefore
for selected in pressedBefore {
if selected == value { return }
}
alreadyPlay.append(song: cellText.text!)
}
}
}
Then, in my cellForRowAt function, I simply make a reverse operation, which checks if the current index in the list is the same as any value in the already selected ones.
Having all of these filtered out, I now only have a list of the non-selected ones.However, I do not know what to do now.
Weirdly, cell.bringSubview(tofront: cell.cellText)
cell.bringSubview(tofront: cell.buttonText) does NOT change the order of the subviews. What would do I do now? Is it possible that CAShapeLayer() is not regarded as a subview, but only a layer?
Thank you in advance!
Weirdly, cell.bringSubview(tofront: cell.cellText)
cell.bringSubview(tofront: cell.buttonText) does NOT change the order of the subviews. What would do I do now?
bringSubview(tofront:) only works with direct subviews. Traditionally your cellText and buttonText are subviews to cell.contentView.
so try
cell.contentView.bringSubview(tofront: cell.buttonText)
Is it possible that CAShapeLayer() is not regarded as a subview, but
only a layer?
Yes, CAShapeLayer inherits from CALayer and only regarded as a layer to its view and will likely need to be updated via layoutSubviews() or draw()
Saw those nested for loops and if statements, thought I'd offer a way to clean that up a bit.
func findCell() {
//find list elements that match cell's text and ensure it hasn't been pressed before
list.filter { $0 == cellText.text && !pressedBefore.contains($0) }.forEach {
alreadyPlay.append(alreadyPlayed(song: LeLabelOne.text!, artist: leLabelThree.text!))
}
}
// alternative using Sets
func findCell() {
let cellTextSet = Set(list).intersection([cellText.text])
// find entries in cellTextSet that haven't been pressed before
cellTextSet.subtract(Set(pressedBefore)).forEach {
alreadyPlay.append(alreadyPlayed(song: LeLabelOne.text!, artist: leLabelThree.text!))
}
}
I see that there are a ton of these questions, and I think I'm following the accepted Swift 3 methodology, but I'm still getting nothing. I can see that the UITapGestureRecognizer has been attached. Here's my code:
let tileClick = UITapGestureRecognizer(target: self, action: #selector(GameManagement.initiateTileClick(_:)))
newView.addGestureRecognizer(tileClick)
newView.isUserInteractionEnabled = true
func initiateTileClick(_ sender: UITapGestureRecognizer) {
print("initiate tile click")
}
A few things to note:
1) The view that I'm attaching the gesture recognizer to has a two views and a label within it that each cover the entire frame of the view, however, I tried attaching the recognizer to the label, which is the topmost child item and it still doesn't work.
2) Both the function that adds the recognizer and the function that is called on the tap are contained in an NSObject file. I have a variety of interconnected functions that I want to be able to call from multiple view controllers and would prefer to keep this in the separate NSObject file. The process worked when I had everything in a UIViewController file and stopped working when I moved the functions to the NSObject file.
3) I've tried changing GameManagement.initiateTileClick to self.initiateTileClick or just initiateTileClick and none of those worked.
If you are putting your views inside NSObject subclass then these views will lose their behaviors for UIResponder which manages the UI interactions as I am not able to see how you are adding these views to interface.
As you said, it was working inside ViewController because it manages view hierarchy and responder chain.
The solution would be to write extensions to separate code or better abstractions.
extension YourViewController {
newView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(GameManagement.initiateTileClick(_:))))
newView.isUserInteractionEnabled = true
func initiateTileClick(_ sender: UITapGestureRecognizer) {
print("initiate tile click")
}
}
Giving you an idea how the tap recogniser works.
Firstly add Tap gesture recogniser to your view controller. You have to put the object here as shown in the image.
Then control+drag the tap gesture object to your view and select delegate.
Then control+drag the recogniser to your swift file and action will be like this.
#IBAction func tapGesture(_ sender: UITapGestureRecognizer) {
}
Now you must have seen when you give some input to a text field, the keyboard appears. But if you press outside the text field, that is anywhere in the view, the keyboard hides. This is because of the tap gesture recogniser.
Consider you have a text field such that if you click in that text field, keyboard is appeared. But when you tap outside the textfield, the keyboard must hide.
Add this delegate
UITextFieldDelegate
Implement this:
#IBOutlet var phoneText: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
exampleText.delegate = self
}
#IBAction func tapGesture(_ sender: UITapGestureRecognizer) {
exampleText.endEditing(true)
}
Obviously,this function is instance method.
func initiateTileClick(_ sender: UITapGestureRecognizer) {
print("initiate tile click")
}
-
UITapGestureRecognizer(target: self, action:#selector(GameManagement.initiateTileClick(_:)))
but thisGameManagement.initiateTileClick(_:) looks like a class is calling a class method!The target should be the caller of method.self can't call GameManagement.initiateTileClick(_:).
When I open my view controller I have 4 gesture recognizers, when I tap twice this shout happen:
#IBOutlet var doubleTap: UITapGestureRecognizer!
#IBAction func doubleTapAction(sender: AnyObject) {
doubleTap.numberOfTapsRequired = 2
print("Button was 2x")
}
but when I press one of the 4 buttons for the first time(I pressed only one time.) it prints that I tapped twice. after the first time it works fine.
Can someone tell my how to fix this?
thanks for reading my question I hope someone can help me.
if you don't understand my question please comment.
This is because the first time the gesture recognizer is set to recognize one single tap. You change that to be two taps, but you set it in the wrong place, because you already tapped once, so it enters the action and executed the code there:
self.doubleTap.numberOfTapsRequired = 2 // This is wrong here!
print("Button was 2x")
So it changes the recognizer to recognize two taps, but it already recognized one (that's why that code gets executed) so it prints that text as well.
It makes no sense that you set that every time you tap, you just need to do it once. A good place to do this is viewDidLoad.
You could also set the number of the required taps in the interface builder.
code example:
#IBOutlet var doubleTap: UITapGestureRecognizer!
#IBAction func doubleTapAction(sender: AnyObject) {
print("doubleTap was 2x")
}
override func viewDidLoad() {
super.viewDidLoad()
self.doubleTap.numberOfTapsRequired = 2
}
I know how to set UIPanGesture events and recognizers and get the data I need when I create them programatically.
However I am trying to learn how to work with the storyboard and use IBOutlets.
I have dragged over an IBAction for 'Touch Drag Inside'.
#IBAction func dragButtonDragEvent(sender: AnyObject)
{
println(sender)
}
The problem is the sender object I get is just the button itself.
Is there a way to get the drag distance or drag event data when using an IBAction?
I tried
#IBAction func dragButtonDragEvent(sender: AnyObject)
{
var drag:UIPanGestureRecognizer = sender as UIPanGestureRecognizer
println(drag.state)
}
But I get a bad memory access error and the println is not executed as the sender is the button NOT the event.
Can you suggest how to use Touch Drag Inside events when connecting from a Storyboard?
Create the #IBAction function with an event parameter like this:
#IBAction func touchDragInsideAction(sender: AnyObject, event: UIEvent) {
// if the sender is a button
if let button = sender as? UIButton {
// get the touch inside the button
let touch = event.touchesForView(button)?.anyObject() as UITouch
// println the touch location
println(touch.locationInView(button))
}
}
This will println the user's touch location on the button as they drag their finger across the button. You can then use the first point and last point to get the distance.
Please note, if you're modifying the method signature of the existing function, then you'll have to reconnect it in the storyboard.
Change the arguments of IBAction to "Sender and Event".