Disable DoubleTapGesture Zoom out on MKMapView - ios

I have observed MKMapView zoom-out on Double tap gesture, I didn't find any way to disable it. I tried to add own Double Tap Gesture to catch Double tap action but it still zoom out. Any thought?

There is no API to disable or change double-tap behavior with MKMapView.
But, if you really want to do so, one approach would be to find and remove the double-tap gesture recognizer from the MKMapView object.
In the project you shared, you could do that in makeUIView in your UIMapView class:
func makeUIView(context: UIViewRepresentableContext<UIMapView>) -> UIViewType {
self.configureView(mapView, context: context)
setRegion(to: palce)
if let v = mapView.subviews.first,
let ga1 = v.gestureRecognizers
{
let ga2: [UITapGestureRecognizer] = ga1.compactMap { $0 as? UITapGestureRecognizer } .filter { ($0.numberOfTapsRequired == 2) }
for g in ga2 {
v.removeGestureRecognizer(g)
}
}
return mapView
}
I wouldn't necessarily suggest that you do so, however.
Apple may change the MKMapView object in the future, which could then break this.
User's tend to prefer that common UI elements behave in expected ways.
Personally, I get rather annoyed when using an app and the developer has changed standard functionality of UI elements. For example, if I see a table view row with a disclosure indicator (the right-arrow / chevron), I expect that tapping the row will "push" to another screen related to that row. I've seen apps that do not follow that pattern, and it just gets confusing.

Related

MapBox Maps iOS SDK: Is there a way to animate PointAnnotation when it's being selected

I migrated from old Mapbox Maps SDKs for iOS and macOS v6.x.x to mapbox-maps-ios v10.1.0.
A lot of API got changed.
I was able to restore same functionality using new SDK, however I could not find a way to animate annotations upon selection.
Before I used MGLAnnotationView, or MGLAnnotationImage for displaying annotations, and I could easily animate MGLAnnotationView by applying animation block with transformations I needed.
In a new SDK it's not the same. Annotations are now represented by struct PointAnnotation, therefore animation API is not accessible, as it's not a UIView subclass anymore.
What I'm looking for is a simple scale animation that should happen when user tap on the annotation.
Animation scales pin for 1.1 factor and return to 1.0, with duration of 350ms. It is not repeating animation, suppose to happen only once when user tap on the annotation.
I checked an example projects and from what I understand animation is possible by manipulating layers, but I'm not sure how to do that exactly.
Looking for help
As of Mapbox v10.4.3, the solution that works for me is by using View Annotations. You can add custom gesture recognizers, animations, etc inside your custom annotation view. https://docs.mapbox.com/ios/maps/guides/annotations/view-annotations/#create-a-view-annotation
let sampleCoordinate = CLLocationCoordinate2D(latitude: 39.7128, longitude: -75.0060)
let options = ViewAnnotationOptions(geometry: Point(sampleCoordinate), allowOverlap: true, anchor: .center)
let annotationView = CustomAnnotationView()
annotationView.rx.tapGesture().when(.recognized).subscribe(onNext: { [weak self] _ in
guard let self = self else { return }
self.viewModel.input.tappedAnnotation()
/// You can animate annotation view resize here when tapping specific annotation view, or you can also add it inside the custom annotation view
}).disposed(by: self.disposeBag)
try? self.mapView.viewAnnotations.add(annotationView, options: options)
class CustomAnnotationView: UIView {
// Do your customizations here
}

Detecting when a SKNode is tapped on Apple Watch

I'm writing an app for Apple Watch using SpriteKit, so I don't have access to functions like touchesBegan and I have to use a WKTapGestureRecognizer to detect taps, no big deal, but I have issues detecting taps on a node.
In my InterfaceController I have:
#IBAction func handleTap(tapGestureRecognizer: WKTapGestureRecognizer){
scene?.didTap(tapGesture: tapGestureRecognizer)
}
And in my Scene file I have
func didTap(tapGesture:WKTapGestureRecognizer) {
let position = tapGesture.locationInObject()
let hitNodes = self.nodes(at: position)
if hitNodes.contains(labelNode) {
labelNode.text = "tapped!"
}
Problem is the Tap Gesture Recognizer gives me the absolute coordinates of the touch point (for example 11.0, 5,0) while my node is positioned relatively to the center of the screen (so its position is -0.99,-11.29 even though is at the center of the screen) therefore the tap is hitting the node not when actually tapping it, but when I tap on the top left of the screen. I searched everywhere and it looks like this is the way to do it yet I don't find people having the same issues. The node has been added via the editor. What am I doing wrong?
So you have the right idea. You are getting this wrong because hitNodes is an array of SKNodes. Those are newly created. So when you use hitNodes.contains the addresses of the labelNode and the address of the newly created SKNode that is being compared would be completely different. Therefore it would never be tapped.
Here's what I would do. This would be in my Scene File. Your InterfaceController class is correct.
func didTap(tapGesture:WKTapGestureRecognizer) {
let position = tapGesture.locationInObject()
if labelNode.contains(position) {
labelNode.text = "tapped!"
}
}
OR another way would be this. I like this way because you only have one function which would be in the WKInterfaceControlller And you would need no functions in your Scene File.
#IBAction func tapOnScreenAct(_ sender: WKGestureRecognizer) {
if scene.labelNode.contains(sender.locationInObject()) {
scene.labelNode.text = "tapped!"
}
}
Either way, both should work. Let me know if you have any more questions or clarifications.

iOS 14 UISlider ignoring additional GestureRecognizer (PanGesture, LongTapGesture, TapGesture)

I have a view controller in which a user can move around UIButton, UISlider, and a custom UIView based control by panning the control around the screen. This view controller is used to create custom layout of control by the user. This is all done by adding PanGestureRecognizer to the UIControl to move the position of the control relative to user's finger location.
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(pan))
panRecognizer.delegate = self;
myslider.addGestureRecognizer(panRecognizer)
~~~~~~~~~~~~
//pan handler method
#objc func pan(_ gestureRecognizer: UIPanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: view)
guard let gestureView = gestureRecognizer.view else {
return
}
//Move the center to user's finger location
gestureView.center = CGPoint(x: gestureView.center.x + translation.x, y: gestureView.center.y + translation.y);
gestureRecognizer.setTranslation(.zero, in: view);
if (gestureRecognizer.state == .ended) {
//Save the new location inside data. Not relevant here.
}
}
This worked fine in iOS 13 and below for all the control i mentioned above (with the UISlider being a bit glitchy but it still responded to the pan gesture and i don't need the value of the uislider anyway so it's safe to ignore). However testing the app in iOS 14 reveals that UISlider completely ignore the PanGesture (proven by adding breakpoint that never got called inside the pan gesture handling method).
I have looked at apple's documentation regarding UISlider and found no change at all related to gesture handling so this must be done deliberately in deeper lever. My question is: is there any way to "force" my custom gesture to be executed instead of UISlider's gesture without the need to create transparent button overlay (which i don't know will work or not) / creating dummy slider just for this ?
Additionally i also added UILongPressGestureRecognizer and UITapGestureRecognizer to the control. Which worked fine on other UIButton but completely ignored by the UISlider in iOS14 (in iOS 13 everything worked fine).
Thanks.
Okay.. I found the answer myself after some more digging and getting cue from "claude31" about making a new clean project and testing from there.
The problem was with the overriden beginTracking function. This UISlider of mine is actually subclassed into a custom class and in there the beginTracking function is overriden, as per code below:
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let percent = Float(touch.location(in: self).x / bounds.size.width)
let delta = percent * (maximumValue - minimumValue)
let newValue = minimumValue + delta
self.setValue(newValue, animated: false)
super.sendActions(for: UIControl.Event.valueChanged)
return true
}
This is to make the slider move immediately to the user finger location if the touch is inside the boundaries of the UISlider (without the need to first touch the thumbTrack and sliding it to the position the user wants).
In iOS 13 this function does not block the gestureRecognizer from getting recognized. However in iOS 14B4, overriding this function, with sendActions(for:) method inside it cause the added gestureRecognizer to be ignored completely, be it pan gesture, long press gesture, or even tap gesture.
For me, the solution is to simply add a state to check whether the pan gesture is required in this view controller or not. Because, luckily, i only need the added gestureRecognizer in view controller that does not require the beginTouch function to be customized and vice versa.
Edit:
Originally i wrote that the cause of the problem is due to always true return value, however, I just reread the documentation and the default return value for this function is also true. So i think the root causes of this problem is the sendActions(for:) method, causing the added gestureRecognizer to be ignored. My Answer above has been edited to reflect this.

UIPageViewController Swipe Conditional On UIScrollView

I've seen a similar question here but with no working solution.
I have a UIScrollView that is embedded in a UIPageViewController. I would like to have the user able to scroll the UIScrollView normally. Then, if one end of the UIScrollView is reached (all the way to the left, for example) then swiping left (swiping right, but you get what I mean) would trigger the UIPageViewController to shift left to another page.
The default behavior is a bit sketchy. It seems like the gesture recognizers compete and in some cases the UIScrollView does scroll, and in other cases the PageView just shifts to the next page. I'd like to be able to tie this behavior to the content offset of the UIScrollView, or something.
I'll be trying some experiments with the requireFail methods on the various gesture recognizers, but it seems like all of the PageView's gesture recognizer properties are nil'd out because I'm using the scroll transition type. So...walking the hierarchy?
Anyway, I'd appreciate it if anyone knows an easy solution to this.
The way I ended up solving this was by using a replacement for UIPageViewController: EMPageViewController. Because I had direct access to the UIScrollView, I was able to subclass it.
The solution basically came in subclassing and adding toggle flags to the UIScrollViews (the one FOR the EMPageViewController and the one INSIDE it) to control when they should be active or not, based on various factors (such as the content offset of one of the scroll views). Then I was able to override gestureRecognizerShouldBegin:gestureRecognizer in different ways in the UIScrollView subclasses, checking the toggle flags and the velocity of the panning gesture recognizer.
Something like this (code out of context):
import UIKit
class CustomScrollView: UIScrollView {
// these toggles get set by some senior ViewController
var lockDown = false
var lockUp = false
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
print("= gesture starting")
if let panner = gestureRecognizer as? UIPanGestureRecognizer {
if panner.velocityInView(self).y < 0.0 {
print("== pulling down, lockDown: \(lockDown)")
if lockDown {
print("=== cancelling paging scroll")
return false
}
}
if panner.velocityInView(self).y > 0.0 {
print("== pulling up, lockUp: \(lockUp)")
if lockUp {
print("=== cancelling paging scroll")
return false
}
}
}
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
}
Anyway, I did get it all working slickly in the end. Everyone would probably have a slightly different use case, but the bottom line is that you need to subclass UIScrollView and so UIPageViewController is not an option.

Using iOS pan gesture to highlight buttons in grid

So far I have a grid of buttons and have attached a pan gesture recognizer to the view. I can track the events and get the location of the finger as it moves but there doesn't seem to be the equivalent of a "mouseEnter" message to use to get info about or control properties (such as the highlighting) of the other buttons I pass over.
Am I missing something? How can I accomplish, say, highlighting the buttons under the users' fingers as they pan over them? Does cocoa touch support this or must something else be done?
Thanks.
You are right, there is no such event. Also UIButton events won't help you either, because those require to actually start gesture inside. What you can do instead is to get location of the point you are currently dragging:
func panDetected(sender : MoreInformativeGestureRecognizer) {
let touchPoint = sender.locationInView(self.view)
}
And now, when you have the point, you can iterate on all the buttons you have and check if the point is inside the button:
let buttons = [UIButton]
let lastActiveButton = UIButton?
...
// Iterate through all the buttons
for button in buttons {
// Check area of the button against your touch
if CGRectContainsPoint(button.frame, touchPoint) {
// You are inside the touch area of the button
// Here, you can for example perform some action if you want, store information
// about the button so you don't do it multiple times etc.. your call :)
self.lastActiveButton = button
}
}
This way you can detect then you go in and out and do whatever you want with events. Hope it helps!
UIButton inherits from UIControl which has a number of events
Have you tried that? You can add a listener to those events, either through nib/storyboard or through code (look under discussion to see how to do this)

Resources