Gesture recognizer is not working on UIView - swift

I'm trying to get a few gesture recognisers to work on a UIView. The UIview is in this case a retrieved SVG image, the library that I use is SwiftSVG.
But the actual added image is a UIView, so I think that is not the problem?
override func viewDidLoad() {
super.viewDidLoad()
let svgURL = URL(string: "https://openclipart.org/download/181651/manhammock.svg")!
let hammock = UIView(SVGURL: svgURL) { (svgLayer) in
svgLayer.fillColor = UIColor(red:0.8, green:0.16, blue:0.32, alpha:1.00).cgColor
svgLayer.resizeToFit(self.v2imageview.bounds)
}
hammock.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
hammock.isUserInteractionEnabled = true;
hammock.addGestureRecognizer(tap)
self.view.addSubview(hammock)
}
// function which is triggered when handleTap is called
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("Hello World")
}
How can I make the recognizer work?
Thanks

You need to set a frame
hammock.frame = ///
or constraints

Related

Custom MapView Class Swift

I'm having some major issues and could use some help. I'm using some code from the company What3Words. They created a custom class:
open class MapView: MKMapView, MKMapViewDelegate, UIGestureRecognizerDelegate, CLLocationManagerDelegate {
//all their code is here
}
My goal is to use a tap gesture in order to acquire a lat/long from the map. I can do this incredibly easily when using a MapViewKit drop in and I make the object conform to MKMapView!
#IBOutlet weak var map: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func tapped(_ sender: UITapGestureRecognizer) {
print("hey")
let tap = sender
let coord = tap.location(in: map)
print(coord)
print(tap.location(in: self))
print(tap.location(in: map))
map.convert(coord, to: map)
print(map.convert(coord, to: map))
}
}
But, if I conform it MKMapView! I can't access any of the specialty code within the open class.
and if I conform it to the open class I don't know how to use my tap gesture.
I'm supposed to call this code below in whatever VC I want the map to appear in:
super.viewDidLoad()
let map = MapViewController(frame: view.frame)
map.set(api: api)
map.set(center: "school.truck.lunch", latitudeSpan: 0.05, longitudeSpan: 0.05)
map.showsUserLocation = true
map.onError = { error in
self.showError(error: error)
}
self.view = map
}
// MARK: Show an Error
/// display an error using a UIAlertController, error messages conform to CustomStringConvertible
func showError(error: Error) {
DispatchQueue.main.async {
let alert = UIAlertController(title: "Error", message: String(describing: error), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
}
Any idea how I can get a tap gesture to work while still using this special class? I feel like I've tried everything. UGH!! Please assist.
The simplest way to do it is to add the following to your ViewController:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let location = touches.first?.location(in: map) {
map.screenCoordsToWords(point: location) { square in
print(square.words ?? "")
}
}
}
And in your MapView class add this:
func screenCoordsToWords(point: CGPoint, completion: #escaping (W3WSquare) -> () ) {
let coordinates = convert(point, toCoordinateFrom: self)
self.api?.convertTo3wa(coordinates: coordinates, language: "en") { square, error in
if let s = square {
completion(s)
}
}
}
But doing it this way can lead to confusion between MKMapView's touch event handler and your view controller. Depending on what you're trying to accomplish this might be okay, or not okay.
If it's an issue, you can instead attach a gesture recognizer inside your MapView, but there is a trick to prevent overriding the MKMapView's built in double tap recogniser:
func addGesture() {
/// when the user taps the map this is called and it gets the square info
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
tap.numberOfTapsRequired = 1
tap.numberOfTouchesRequired = 1
// A kind of tricky thing to make sure double tap doesn't trigger single tap in MKMapView
let doubleTap = UITapGestureRecognizer(target: self, action:nil)
doubleTap.numberOfTapsRequired = 2
addGestureRecognizer(doubleTap)
tap.require(toFail: doubleTap)
tap.delegate = self
addGestureRecognizer(tap)
}
#objc func tapped(_ gestureRecognizer : UITapGestureRecognizer) {
let location = gestureRecognizer.location(in: self)
let coordinates = convert(location, toCoordinateFrom: self)
api?.convertTo3wa(coordinates: coordinates, language: "en") { square, error in
if let s = square {
print(s.words ?? "")
}
}
}
Then in your ViewController's viewDidLoad, set it up:
map.addGesture()

iOS SwiftUI: How to detect location of UITapGesture on view?

Using Mapbox, the map struct conforms to the UIViewRepresentable protocol. In my makeUIView() function, I create a tap gesture recognizer and add it to the map view.
struct Map: UIViewRepresentable {
private let mapView: MGLMapView = MGLMapView(frame: .zero, styleURL: MGLStyle.streetsStyleURL)
func makeUIView(context: UIViewRepresentableContext<Map>) -> MGLMapView {
mapView.delegate = context.coordinator
let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator, action: Selector("tappedMap"))
mapView.addGestureRecognizer(gestureRecognizer)
return mapView
}
func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<Map>) {
print("Just updated the mapView")
}
func makeCoordinator() -> Map.Coordinator {
Coordinator(appState: appState, self)
}
// other functions that make the struct have functions required by Mapbox
func makeCoordinator() -> Map.Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, MGLMapViewDelegate {
var control: Map
//a bunch of delegate functions
#objc func tappedMap(sender: UITapGestureRecognizer) {
let locationInMap = sender.location(in: control)
let coordinateSet = sender.convert(locationInMap, toCoordinateFrom: control)
}
}
}
Neither of the lines in the tappedMap function compile properly...also, when I have 'sender: UITapGestureRecognizer' in the parameters of tappedMap, I causes the application to crash when I tap the mapView--If I remove the parameter, then the function is at least called properly without crashing. Please help
OK, the first problem is your selector definition in the tapGesture declaration. Change it to this:
let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.tappedMap(sender:)))
Also, you need to manage the priority of gesture recognizers, since MGLMapView includes UIGestureRecognizer logic internally. I found a discussion of here:
Adding your own gesture recognizer to MGLMapView will block the
corresponding gesture recognizer built into MGLMapView. To avoid
conflicts, define which gesture takes precedence.
You can try this code in your project (pretty much just copied from the linked page above) EDIT: I needed to change the order of lines from my original answer:
let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator, action: Selector("tappedMap"))
// HERE'S THE NEW STUFF:
for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
gestureRecognizer.require(toFail: recognizer)
}
mapView.addGestureRecognizer(gestureRecognizer)
EDIT: I was able to test this.
I'm a little confused by the logic inside your tappedMap selector: maybe you meant something like this?
#objc func tappedMap(sender: UITapGestureRecognizer) {
let locationInMap = sender.location(in: control.mapView)
let coordinateSet = sender.view?.convert(locationInMap, to: control.mapView)
}

why is bringSubviewToFront() only working for panning, not tapping?

view.bringSubviewToFront(tappedView)
is working when I drag (pan) views but not when I tap them. My issue is that when I have one view layered over the other, I want the bottom view to come to the front when tapped. Currently it will only come to the front when dragged. Do I need some additional code to do this? Thanks.
Here's an excerpt from my code for more context:
#objc func didPan(_ recognizer: UIPanGestureRecognizer) {
let location = recognizer.location(in: self.view)
switch recognizer.state {
case .began:
currentTappedView = moviePieceView.filter { pieceView -> Bool in
let convertedLocation = view.convert(location, to: pieceView)
return pieceView.point(inside: convertedLocation, with: nil)
}.first
currentTargetView = movieTargetView.filter { $0.pieceView == currentTappedView }.first
case .changed:
guard let tappedView = currentTappedView else { return }
let translation = recognizer.translation(in: self.view)
tappedView.center = CGPoint(x: tappedView.center.x + translation.x, y: tappedView.center.y + translation.y)
recognizer.setTranslation(.zero, in: view)
view.bringSubviewToFront(tappedView)
```
I had this same problem, I managed to solve it this way:
Add a gestureRecognizer to your view and put this in your code. Don't forget to set the delegate as well when attaching this to the code!
#IBAction func tappedView1(recognizer: UITapGestureRecognizer) {
view.bringSubviewToFront(myView)
}
Put this in your viewDidLoad:
let tap = UITapGestureRecognizer(target: self, action: #selector(tappedView1))
tap.cancelsTouchesInView = false
self.view.addGestureRecognizer(tap)
If you want to do that from your panGesture check my question.
Thank you all. It worked when I wrote a second function in addition to didPan().
This is the additional code:
override func viewDidLoad() {
super.viewDidLoad()
configureLabel()
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan(_:)))
view.addGestureRecognizer(panGestureRecognizer)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTap(_:)))
view.addGestureRecognizer(tapGestureRecognizer)
}
#objc func didTap(_ recognizer: UITapGestureRecognizer) {
let location = recognizer.location(in: self.view)
currentTappedView = moviePieceView.filter { pieceView -> Bool in
let convertedLocation = view.convert(location, to: pieceView)
return pieceView.point(inside: convertedLocation, with: nil)
}.first
guard let tappedView = currentTappedView else { return }
view.bringSubviewToFront(tappedView)
}
HOWEVER, I found out you can also just tick the "User Interaction Enabled" box in the Image View if you don't want to do it programmatically.

selector with argument gestureRecognizer

Argument of '#selector' does not refer to an '#objc' method, property,
or initializer
Problem : Above Error when i try to pass argument with selector
code snippet:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(labelPressed(i: 1)))
func labelPressed(i: Int){
print(i)
}
You cannot pass a parameter to a function like that. Actions - which this is, only pass the sender, which in this case is the gesture recognizer. What you want to do is get the UIView you attached the gesture to:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(labelPressed())
func labelPressed(_ recognizer:UITapGestureRecognizer){
let viewTapped = recognizer.view
}
A few more notes:
(1) You may only attach a single view to a recognizer.
(2) You might want to use both the `tag` property along with the `hitTest()` method to know which subview was hit. For example:
let view1 = UIView()
let view2 = UIView()
// add code to place things, probably using auto layout
view1.tag = 1
view2.tag = 2
mainView.addSubview(view1)
mainView.addSubview(view2)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(mainViewTapped())
func mainViewTapped(_ recognizer:UITapGestureRecognizer){
// get the CGPoint of the tap
let p = recognizer.location(in: self)
let viewTapped:UIView!
// there are many (better) ways to do this, but this works
for view in self.subviews as [UIView] {
if view.layer.hitTest(p) != nil {
viewTapped = view
}
}
// if viewTapped != nil, you have your subview
}
You just should declare function like this:
#objc func labelPressed(i: Int){ print(i) }
Update for Swift 3:
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:)))

AVPlayercontroller - Adding Overlay Subviews

Depends upon the PlaybackControl Visibity How to show and hide the Custom Subview added in contentOverlayView?
I want to do like Youtube TVOS app did.
I tried with UIPressesEvent but it is not giving me the exact touch events. It is giving me these:
override func pressesBegan(presses: Set<UIPress>, withEvent event: UIPressesEvent?) {
for item in presses {
switch item.type {
case .Menu:
self.customViews.alpha = 0
case .PlayPause:
self.player?.pause()
self.customViews.alpha = 0
default:
self.setVisibilityToPreviewView()
}
}
}
func setVisibilityToPreviewView () { //This wont work in all cases.
if self.previewView.alpha == 1 {
self.previewView.alpha = 0
} else {
self.previewView.alpha = 1
}
}
But with this Touch events i can only show and hide the subviews.
It should be hidden when the playbackcontrol is Hidden.
If I get the PlayBackControl Visibility values I don't need to worry about hiding these subviews.
Apple is Using AVNowPlayingPlaybackControlsViewController. It is not open for developers.
So I need to find some other better way to do this.
Please guide me how to do it.
You can register a tapGesture recognizer and then set its allowPressTypes property to UIPressType.Select, something like this
let tapRecognizer = UITapGestureRecognizer(target: self, action: "onSelect:")
tapRecognizer.allowedPressTypes = [NSNumber(integer: UIPressType.Select.rawValue)];
self.view.addGestureRecognizer(tapRecognizer)
And inside your action button show or hide custom overlays.
Example: Add this code inside a view controller and on tap (selecting at empty area on front of remote, touch area) you will see a message on console.
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: "onSelect")
tapGesture.allowedPressTypes = [NSNumber(integer: UIPressType.Select.rawValue)]
self.view.addGestureRecognizer(tapGesture);
}
func onSelect(){
print("this is select gesture handler method");
}
Update: Below is the code which will create AVPlayerController and will register tapGestureRecognizer to playervc.view.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
playContent()
}
func playContent(){
let urlString = "<contentURL>"
guard let url = NSURL(string: urlString) else{
return
}
let avPlayer = AVPlayer(URL: url)
let playerVC = AVPlayerViewController()
playerVC.player = avPlayer
self.playerObj = playerVC
let tapGesture = UITapGestureRecognizer(target: self, action: "onSelect")
tapGesture.allowedPressTypes = [NSNumber(integer: UIPressType.Select.rawValue)]
self.view.addGestureRecognizer(tapGesture);
playerVC.view.addGestureRecognizer(tapGesture)
self.presentViewController(playerVC, animated: true, completion: nil);
}
Give a try to this, I think it should work fine.