Add gesture recognizer to BezierPath created by PaintCode - ios

I have no idea about the following situation:
I had exported a NSObject from PaintCode and made a .swift file (someObject.swift).
public class PlanATrip: NSObject {
class func drawRectangle1(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height:100), resizing: ResizingBehavior = .aspectFit) {
....
}
I also overrode the draw() function in a UIView (someObjectView.swift).
So how can add a gesture recognizer to a bezierPath (for example, a rectangle1 = UIBezierPath(...) ) which is in the someObject.swift ?
I tried to add some functions like:
let tapGestureA:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(touchAction))
However, the scope confused me; like if I put the touchAction function out of the drawRectangle1 function, I will not be able to access rectangle1.
How can I modify to make such a gesture recognizer work?

You are trying to manipulate UIBezierPath objects generated by PaintCode directly, which isn't PaintCode's purpose.
What you are trying to achieve is possible, but not without some hacking.
For example: How to access a layer inside UIView custom class generate with Paintcode?
In my opinion, you are "misusing" PaintCode.
Instead, you would be much better adding the tapping logic inside your custom UIView. The thing you called someObjectView.swift.
For example: https://github.com/backslash-f/paint-code-ui-button
In case you really need to check if gestures occur inside the boundaries of a UIBezierPath then you need to create a public reference to it (e.g.: a var that points to it outside of the drawRectangle1 function) and finally use the code proposed by #Sparky.
The main problem with this approach is that your changes will be overwritten every time you use PaintCode's export feature. I wouldn't recommend it.

If I understand your question correctly, you'd like to add a UITapGestureRecognizer that will recognize a gesture within a portion of your view defined by a UIBezierPath.
In this case, assign the selector to the gesture recognizer as you have in your question, then in the body of touchAction(recognizer:), test whether the gesture lies within the UIBezierPath. The simple case where you only care if the gesture is inside the UIBezierPath might be handled as follows:
func touchAction(recognizer: UITapGestureRecognizer) -> Void {
guard rectangle1.contains(recognizer.location(in: self)) else { return }
// Execute your code for the gesture here
// ...
}

Related

What's the difference between UIView.animate() and UIButton.animate()?

I want to change the opacity of my button to half when pressed, and animate that change. So I was searching for how to do that and found this piece of code:
#IBAction func keyPressed(_ sender: UIButton) {
UIView.animate(withDuration: 0.3)
{
sender.alpha = 0.5
}
I got curious as to why we called the animate function on a UIView not on a UIButton, as what we want to animate is, specifically, a UIButton. So I tried UIButton.animate() and to my eyes it gives the same result with the animation.
So what's the difference? Is there a reason the person posting this code preferred using UIView.animate() over UIButton.animate()?
The animate function is a class level function of UIView so it is common to use it as UIView.animate....
Since UIButton ultimately extends UIView, using UIButton.animate... also works. You could even use UIScrollView.animate..., for example, in your UIButton code. Obviously that would be confusing but it would work.
In any code within a UIView subclass, using Self.animate... also works.
But the basic answer to your question is that people use UIView.animate... because animate is defined in the UIView class.

UITapGestureRecognizer not firing

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
}
}
}
}

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)

Swift - Initialize a subclass of UIView

I have an already setup view and want to wrap it in a subclass of UIView.
class ElementView: UIView {
var selected = false
}
The problem is that I cannot initialize ElementView with the already existing view.
Swift doesn't allow assigning to self too.
init(view: UIView) {
//here I would have to call super.init(coder: NSCoder?) or super.init(frame: CGRect)
//none of which actually properly initializes the object
}
Any ideas how to implement this?
Thanks :)
Clarification:
I will give you the larger context hoping it'd be more clear:
I am implementing a UIScrollView subclass. The scroll view contains an array of UIView objects which are added externally (from the user of the class).
In my custom UIScrollView class I want to implement a tap gesture recognizer for each object. That's already done:
let singleTap = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
singleTap.cancelsTouchesInView = false
addGestureRecognizer(singleTap)
and the handler:
func handleTap(recognizer: UITapGestureRecognizer) {
//some code to handle the tap
}
The problem arises when I want to handle the tap, I want to store the previous state of the view (was it tapped before or not) in order to toggle that state when the tap happens. I want to do different stuff to the UIView depending on its state.
recognizer.view returns the view to which the recognizer is attached to, which is what I need. But, this way I have no possibility of implementing a state for the UIView.
That's why I wanted to implement a custom wrapper for UIView which should contain the state information (which is also a problem). That's how I came up to asking this question...
In order to create a custom init for the UIView subclass you have to call the required init for the UIView superclass. Also while creating the custom init you have to send the Frame of the view to the superclass. Upon fulfilling these requirements you are free to pass on any arguments to the newly created init including the tap recognizer info.
Remember, if you are creating any variables - they have to be not nil upon the creation of the instance, thus in variable declaration you have to create some initial argument (for example - 0 for Int, etc.) for the initializer to work. Here is the example code:
var variableOne: Int = 0
var variableTwo: Int = 0
init(variableOne: Int, variableTwo: Int) {
self.variableOne = variableOne
self.variableTwo = variableTwo
super.init(frame: CGRectZero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
It sounds like you are trying to mimic a copy constructor, and it sounds like you are trying to build it in IB. The short answer is what you are doing doesn't make sense. It further sounds like that you wanted your code above to own or assume the identity of the argument view (your reference to not being able to assign to self). If this assumption is correct, your code would make even less sense - you would just need to assign to a variable.
Just create the view class, with the code that you have posted, you do not need to implement a constructor, since you have provided a default value for your selected variable. Your will with then be populated from IB via the coder based constructor.
If you are trying to clone or copy a given view, then refer to "Can UIView be copied"

Resources