UITapGestureRecognizer not firing - ios

Let's get the google results out of the way
.userInteractionEnabled IS true
The view IS hit (using a symbolic breakpoint with -[UIWindow sendEvent:] and po $arg3)
Now on to how I have this structured, which is an attempt to make models totally removed from view code.
The gist is that I have these classes:
class CarModel - pure data
class CarModelDisplayClass - a class that carries a Model, and can conform to Displayable and Tappable. This is the class that the later BuilderClass will deal with, it basically acts as a bridge between the Models and the Views.
protocol Displayable - To make a class return a view for the later BuilderClass to attach to a view/screen
protocol Tappable - The BuilderClass looks at conformance to attach a tap gesture to the view (which is returned from the Displayable protocol))
The Builder works like this:
Hardcode-build a bunch of CarModels
Hardcode-build a bunch of CarModelDisplayClass with the models
Send the list of CarModelDisplayClass into a method that translates the list into actual views and gesture recognizers (by looking at protocol conformance)
Attach these views to an actual UIViewController
Present the UIViewController
At this point it all works, except the UITapGestureRecognizer.
The CarModelDisplayClass to actual views+gestures looks like this.
for item in items {
let view = item.view() // Get the view from the Displayable protocol
superView.addSubview(view)
if let i = item as? Tappable { // Check Tappable conformance
let gesture = UITapGestureRecognizer(target: i, action: #selector(i.tapped))
view.addGestureRecognizer(gesture)
view.isUserInteractionEnabled = true
}
}
I am not sure if there is something obvious I am missing. I thought maybe there is something related to the target i is the issue, however I have tried directing it to item as well (if that would even matter I don't know).
Any pointers would help.
I have the real code here (it has different names though)
The Displayable and Tappable protocols
Pure model class
Example of a display class that takes a model and conforms to display to return a simple label, and conforms to tappable with a method
The view builder that converts display classes into actual views and adds gesture recognizers to them if needed
The high level builder that collects models, display classes and presents a VC

It seems that you will have to keep a strong reference to your Tappable items somewhere in code otherwise they will be removed from memory.
Based on the code in attached links - I would change TLAStackviewBuilder class to return UIScrollView, but with references to displayRows in it.
In code that you wrote above:
class Something {
let storedItems: [Any]!
func someFunc(items: Tappable) {
storedItems = items
for item in items {
let view = item.view() // Get the view from the Displayable protocol
superView.addSubview(view)
if let i = item as? Tappable { // Check Tappable conformance
let gesture = UITapGestureRecognizer(target: i, action: #selector(i.tapped))
view.addGestureRecognizer(gesture)
view.isUserInteractionEnabled = true
}
}
}
}

Related

How to pass data between views. When should I use what?

I have a View-Hierarchy like this:
UIViewController (SingleEventViewController)
UIScrollView (EventScrollView)
UIView (contentView)
3xUITableView (SurePeopleTV, MaybePeopleTV, NopePeopleTV (all inherited from the same UITableView)), & all other UI-Elements
The SingleEventViewController stores one Event (passed within the initializer). (All Events are stored in Core-Data).
The three UITableViews are there for displaying the users which are participating (or not or maybe) at the Event. My question is, what are the possibilities to fill the tableViews with the data and what would you recommend in which situation.
Currently I have a property parentVC: SingleEventViewController in all Subviews and get the data like this:
override func loadUsers() {
//class SurePeopleTV
guard let parentController = parentVC else { return }
users = (parentController.thisEvent.eventSureParticipants?.allObjects as! [User])
finishedLoading = true
super.loadUsers()
}
.
func applyDefaultValues() {
//class EventScrollView
guard let parent = parentVC else { return }
titleLabel.text = parent.eventName
}
I'm new to programming but I got a feeling that I should not create a parentVC reference in all of my classes.
An object should not (ideally) know about its parent - if it does they are "tightly coupled". If you change the object's parent, your code may break. In your case, your parent object must have a thisEvent property.
You want your objects to be "loosely coupled", so the object doesn't know about a specific parent object.
In Swift, the usual ways to pass information "back up the chain" is to use the delegate design pattern… https://developer.apple.com/documentation/swift/cocoa_design_patterns or to use closures.
See also https://www.andrewcbancroft.com/2015/04/08/how-delegation-works-a-swift-developer-guide/ for info on delegation
First of all, if you create a reference to the parent ViewController make sure it is weak, otherwise you can run into memory management issues.
Edit: As Ashley Mills said, delegates the way to handle this
The recommended way to pass data between ViewControllers is using something like this
Every time a segue is performed from the view controller this function is in this function is called. This code first checks what identifier the segue has, and if it is the one that you want, you can access a reference to the next view controller and pass data to it.

UIView subclass access ViewController methods swift

In a couple of my projects I think I'm not created a great structure in many cases.
It could be a game where I've created a game board (think about chess) with a grid of 8 * 8 cells. Each cell has a gesture recognizer that relies on a subclass (cell.swift), with the game logic in a parent ViewController.
For arguments sake, let us say we want to display to the user which square they have touched.
I've found out how to do this from the subclassed UIView (obvs. create the alert in the subclassed UIView / cell.swift in this example)
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
but it seems to break the structure of the app - but wouldn't it be the same accessing an action in the parent ViewController? What is the best way of approaching this>
Your rootViewController is the VC on the bottom of your stack. It's not a safe way to access the visible VC, and is rarely useful, in general (there are cases, but I doubt your app would find them useful).
What you likely want to use is a delegate pattern. Let's say the parent VC that displays your chess board (let's call this MyBoardViewController), conforms to a protocol like the following. MyView is whatever custom UIView class you're using for the chess squares:
protocol SquareAlertHandler {
func handleSquarePressed(sender : myView)
}
And add the following property to your MyView class:
weak var delegate : SquareAlertHandler?
And replace whatever event handler you're currently using, with the following (I'm assuming you're using a UIButton in IB to handle the press, and have arbitrarily named the outlet 'didPress:'):
#IBAction didPress(sender : UIButton) {
delegate?.handleSquarePressed(self)
}
Now, add the protocol to your MyBoardViewController, and define the method:
class MyBoardViewController : UIViewController, SquareAlertHandler {
... ... ...
func handleSquarePressed(sender : myView) {
// Do something to handle the press, here, like alert the user
}
... ... ...
}
And finally, wherever you create the MyView instances, assign the MyBoardViewController instance as the delegate, and you're good to go.
Depending on your Swift literacy, this may be confusing. Adding code, so that I can at least match up the class names, would help to clarify things.

How should a UIView access the data model to display the data (using Swift)?

This seems like it should have a simple answer, and probably does, but it's proving harder to find than I expected. As a specific example, let's say that I'm programming a chess game.
It seems like this is something I should be able to do just using CoreGraphics. It seems like using OpenGL or SpriteKit shouldn't be necessary.
I want to have a Board class that models the state of the board. Where should I declare my Board object? My impression is that it should be in the ViewController.
I want to have a view (actually a subview) that displays the current state of the board (by overloading drawRect). It should do this at the beginning, and should be updated when players make moves. How does the view access the data model to display the board state? Does giving the view a reference to the data violate MVC? If not, how would the reference be passed to the view? (I seem to just find lots of links about passing data between two ViewControllers.)
Should it instead be the ViewController "pushing" the data to the view whenever it needs to be drawn? My understanding, though, is that drawRect should not be called directly, and that instead setNeedsDisplay should be called, which will indirectly result in drawRect being called. This being the case, it's hard to see how the data would be passed.
Your code; your design decision. Nothing to comment on here.
You should have your model declaration in ViewController. True. That is how MVC works.
Having a reference of the data in a UIView DOES break MVC. Your view instance will not be independent anymore. Decoupling view and model is one of the main points of MVC and you are probably breaking it with this design.
What can you do about it?
Extending #Paulw11's comment, in your view controller you can declare a method that looks something like this :
func movePiece(somePiece : Piece, toSquare : Square) {
let pieceID = somePiece.id //I am just assuming the models structures
let pieceImageView = self.pieceImageViewFromID(id) //Assume that this method returns the reference of the image view. Assume that I am just working UIKit here.
let fromPoint : CGPoint = somePiece.origin
let toPoint : CGPoint = toSquare.coordinates
self.animateView(pieceImageView, fromPoint:fromPoint, toPoint:toPoint)
}
Note that in this design, the view is not holding any model references; the view controller will take care of setting its state and bring upon relevant animations.
If you are overriding drawRect:, then yes, for it be called, you should call setNeedsDisplay to update the changes. The view controller might call or you can add property observers to redraw itself based on a property change. One example for this could be:
class SillyView : UIView {
var drawPonies : Bool = false {
didSet {
if oldValue != drawPonies {
self.setNeedsDisplay()
}
}
}
override func drawRect(rect: CGRect) {
if drawPonies {
self.drawGoodLookingPony()
} else {
self.drawSomeOtherAnimal()
}
}
func drawGoodLookingPony() {
//Draw a good looking pony here
}
func drawSomeOtherAnimal() {
//Draw Something else
}
}
If your view controller decides to draw ponies all you have to do is, get the reference of the SillyView and set drawPonies to true.
self.sillyView.drawPonies = true
You are not passing your data model here, but important pieces of configuration information that will help the view redraw itself.

Why doesn't my View respond to a gesture using a gestureRecognizer?

Having just spent a day beating my head against the keyboard, I thought I'd share my diagnosis and solution.
Situation: You add a custom View of custom class CardView to an enclosing view myCards in your app and want each card to respond to a tap gesture (for example to indicate you want to discard the card). Typical code you start with:
In your ViewController:
class MyVC : UIViewController, UIGestureRecognizerDelegate {
...
func discardedCard(sender: UITapGestureRecognizer) {
let cv : CardView = sender.view! as! CardView
...
}
In your myCards construction:
cv = CardView(card: card)
myCards.addSubview(cv)
cv.userInteractionEnabled = true
...
let cvTap = UITapGestureRecognizer(target: self, action: Selector("discardedCard:"))
cvTap.delegate = self
cv.addGestureRecognizer(cvTap)
I found the arguments here very confusing and the documentation not at all helpful. It isn't clear that the target: argument refers to the class that implements discardedCard(sender: UITapGestureRecognizer) . If you're constructing the recognizer and cards in your ViewController that's going to be self. If you want to move the discardedCard into your custom View class (for example), then replace self with CardView in my example, including on the delegate line.
Testing the above code, I found that the discardedCard function was never called. What was going on?
So a day later, here's what I had to fix. I hope this checklist is useful to somebody else. I'm new to iOS (coming from Android), so it may be obvious to you veterans:
Make sure the touched view (cv in the example) has userInteractionEnabled=true . Note that if you use your own constructor it will be set false by default
Make sure all enclosing views also have userInteractionEnabled=true
Others have posted that the order of the delegate statement and addGestureRecognizer statement made a difference; I didn't find that using Xcode 7.2 and iOS 9.2
Most important: Make sure your touched view is fully within the bounds of the enclosing views. In my example, I was building a myCards container that didn't have the width set correctly and was cutting off the right-most cards (and since clipping is disabled by default, this wasn't visually obvious until I looked in the debugger at the View hierarchy)

passing parameters to a Selector in Swift

I am building an app for keeping track of reading assignments for a university course. Each ReadingAssignment has included a Bool value that indicates if the reader has finished reading the assignment. The ReadingAssignments are collected into WeeklyAssignment arrays. I want to have the user be able to touch a label and have a checkmark appear and show the assignment as completed. I would like this touch to also update the .checked property to true so I can persist the data.
So, I am trying to have the gestureRecognizer call the labelTicked() method. This works and prints to the console. However, when I try to pass in the assignment parameter, it compiles, but crashes on the touch with an "unrecognized selector" error. I have read every topic i can find here, and haven't found the solution. They all say ":" signifies a Selector with parameters, but still no go.
Can you see what I am doing wrong?
func configureCheckmark(cell: UITableViewCell, withWeeklyAssignment assignment: WeeklyAssignment) {
let checkLabel = cell.viewWithTag(1002) as! UILabel
checkLabel.userInteractionEnabled = true
let gestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("labelTicked:assignment"))
checkLabel.addGestureRecognizer(gestureRecognizer)
}
#objc func labelTicked(assignment: WeeklyAssignment) {
assignment.toggleCheckmark()
if assignment.checked {
label.text = "✔︎"
} else {
label.text = ""
}
}
I would also love to pass in the UILabel checkLabel so I can update it in the labelTicked() method.
Thanks for your help.
There are two distinct problems here:
The syntax for the selector is wrong; the : doesn't mark the beginning of a parameters part, it merely marks that this function takes parameters at all. So the tap recognizer should be initialized as UITapGestureRecognizer(target: self, action: "labelTicked:"). Easy fix. That is the good news.
The bad news: even with perfect syntax it will not work the way you set it up here. Your tap recognizer cannot pass a WeeklyAssignment object as a parameter. In fact, it cannot pass any custom parameter at all. At least, not like that.
However, you can pass it in the sender (which is usually the view the gesture recognizer is attached to). You can grab it by changing your method to
func labelTicked(sender: AnyObject) {
(note that AnyObject may be declared as a more specific type if you know exactly what to expect.)
Going through the sender, you could now theoretically infer which label it is that has been tapped, which data entity that labels corresponds to, and which state the checked property of that entity is in. I think this would become very convoluted very quickly.
Seemingly straightforward things becoming convoluted is usually a good sign that we should take a step back and look for a better solution.
I'd suggest dropping the whole GestureRecognizer approach at this point, and instead exploit the fact that each cell in a table view already comes with its own "tap recongizing" functionality out of the box: the didSelectRowAtIndexPath: method of the table view's delegate. There, you can easily use the supplied NSIndexPath to retrieve the corresponding model entity from the data source to read and modify its parameters as you see fit. Via cellForRowAtIndexPath: you can then get a reference to the correct cell and change its contents accordingly.
Update for Swift 3:
As the Swift language evolves and using String-based selectors (e.g. "labelTicked:") is now flagged as deprecated, I think it's appropriate to provide a small update to the answer.
Using more modern syntax, you could declare your function like this:
#objc func labelTicked(withSender sender: AnyObject) {
and initialize your gesture recognizer like this, using #selector:
UITapGestureRecognizer(target: self, action: #selector(labelTicked(withSender:)))
The correct selector for that function is labelTicked:. Furthermore, you can use a string directly as a selector. Thus:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: "labelTicked:")
But you can't arrange to pass an arbitrary object along to the method when the recognizer fires. A better way to do this is to create a subclass of UITableViewCell. Let's call it AssignmentCell. Give the subclass an assignment property, and move the labelTicked: method to AssignmentCell.
If you've designed the cell in your storyboard, you can add a tap recognizer to the label right in the storyboard, and wire the recognizer to the cell's labelTicked: method in the storyboard.
In your table view data source's tableView(_:cellForRowAtIndexPath:), after you've dequeued the cell, set its assignment property to the assignment for that row.

Resources