ShinobiCharts data labels/annotations/Tick Mark gesture recognizer - ios

Using shinobi charts
Looking for examples how to add gesture recognizers (on touch up) to Tick Marks and schart annoations
I see the documentation for interacting with a series data series, but I need to add GestureRecognizers to tick marks and annotation events
I tried this for the tickMark/datapoint labels with no luck:
func sChart(chart: ShinobiChart!, alterTickMark tickMark: SChartTickMark!, beforeAddingToAxis axis: SChartAxis!) {
if let label = tickMark.tickLabel {
//added a gesture recognizer here but it didn't work
}
For the SchartAnnotations no idea how to go about adding one there

I think you're nearly there with labels. I found I just needed to set userInteractionEnabled = true e.g.
func sChart(chart: ShinobiChart!, alterTickMark tickMark: SChartTickMark!, beforeAddingToAxis axis: SChartAxis!) {
if let label = tickMark.tickLabel {
let tapRecognizer = UITapGestureRecognizer(target: self, action: "labelTapped")
tapRecognizer.numberOfTapsRequired = 1
label.addGestureRecognizer(tapRecognizer)
label.userInteractionEnabled = true
}
}
Annotations are a little trickier, as they're on a view below SChartCanvasOverlay (responsible for listening for gestures). This results in the gestures being 'swallowed' before they get to the annotation.
It is possible however, but you'll need to add a UITapGestureRecognizer to your chart and then loop through the chart's annotations to check whether the touch point was inside an annotation. E.g.:
In viewDidLoad:
let chartTapRecognizer = UITapGestureRecognizer(target: self, action: "annotationTapped:")
chartTapRecognizer.numberOfTapsRequired = 1
chart.addGestureRecognizer(chartTapRecognizer)
And then the annotationTapped function:
func annotationTapped(recognizer: UITapGestureRecognizer) {
var touchPoint: CGPoint?
// Grab the first annotation so we can grab its superview for later use
if let firstAnnotation = chart.getAnnotations().first as? UIView {
// Convert touch point to position on annotation's superview
let glView = firstAnnotation.superview!
touchPoint = recognizer.locationInView(glView)
}
if let touchPoint = touchPoint {
// Loop through the annotations
for item in chart.getAnnotations() {
let annotation: SChartAnnotation = item as SChartAnnotation
if (CGRectContainsPoint(annotation.frame, touchPoint as CGPoint)) {
chart.removeAnnotation(annotation)
}
}
}
}

Related

Swift: Gesture Recognizer doesn't register Taps after changing alpha to 0 and back to 1

the UIView in question is headerView:
if isShown {
stack.alpha = 1.0
self.isStackShown = true
DispatchQueue.main.async {
self.headerView.isHidden = !isShown
self.stack.addArrangedSubview(self.headerView)
// add touch gesture recognizer to stack view header
let tapFind = UIGestureRecognizer(target: self, action: #selector(self.handleFindTap))
self.headerView.addGestureRecognizer(tapFind)
}
} else {
stack.alpha = 0.0
self.isStackShown = false
DispatchQueue.main.async {
self.headerView.isHidden = isShown
self.stack.removeArrangedSubview(self.headerView)
}
}
The tap gesture recognizer is not registering any taps
self.stack is the stack view which contains the headerView
The condition for either showing or hiding the headerView is being handled in a different method and just passes the boolean self.isStackShown to this method to show/hide accordingly.
You are using a UIGestureRecognizer. UIGestureRecognizer is a polymorphic base class and should really be subclassed. UITapGestureRecognizer is the concrete subclass for handling taps. Use it instead.
let tapFind = UITapGestureRecognizer(target: self, action: #selector(self.handleFindTap))
self.headerView.addGestureRecognizer(tapFind)
Your action is never getting called because UIGestureRecognizer has no inherent information about what kind of gesture to recognize. Only a concrete subclass of it does.
Looks like you are adding multiple gesture recognizers when changing back the alpha to 1.0 and they aren't able to recognize simultaneously. Remove all gesture recognizers when hiding and removing the headerView since you don't need one anymore and add one back when adding back the headerView and it should work. Or you can also let the gesture recognizer be when hiding the headerView since it won't work anyway and check if one exists before adding another.
if isShown {
stack.alpha = 1.0
self.isStackShown = true
DispatchQueue.main.async {
self.headerView.isHidden = !isShown
self.stack.addArrangedSubview(self.headerView)
// add touch gesture recognizer to stack view header
let tapFind = UIGestureRecognizer(target: self, action: #selector(self.handleFindTap))
self.headerView.addGestureRecognizer(tapFind)
}
} else {
stack.alpha = 0.0
self.isStackShown = false
DispatchQueue.main.async {
self.headerView.isHidden = isShown
self.headerView.gestureRecognizers?.forEach({self.headerView.removeGestureRecognizer($0)})
self.stack.removeArrangedSubview(self.headerView)
}
}
or
if isShown {
stack.alpha = 1.0
self.isStackShown = true
DispatchQueue.main.async {
self.headerView.isHidden = !isShown
self.stack.addArrangedSubview(self.headerView)
if self.headerView.gestureRecognizers?.isEmpty != false{
// add touch gesture recognizer to stack view header
let tapFind = UIGestureRecognizer(target: self, action: #selector(self.handleFindTap))
self.headerView.addGestureRecognizer(tapFind)
}
}
} else {
stack.alpha = 0.0
self.isStackShown = false
DispatchQueue.main.async {
self.headerView.isHidden = isShown
self.stack.removeArrangedSubview(self.headerView)
}
}

iOS: dragging the copy of a button

I'm not sure whether this has been asked or not, but I failed to find a solution. I'm implementing panning gesture on a button, but the idea is: the button is fixed to a position, and when the user drags it, a copy of the button is created and moving with the gesture; the original one stays at its initial place (so there'll be 2 buttons in the view). When the panning ends, the new button is used for some processing, and after that it should disappear (the original one stays as it is; so this whole process can repeat). Currently what I have is as below:
private func addPanGesture() {
for btn in self.selectors { //selectors is a list of buttons which needs this gesture
let pan = UIPanGestureRecognizer(target: self, action:#selector(self.panDetected(_:)))
pan.minimumNumberOfTouches = 1
pan.maximumNumberOfTouches = 1
btn.addGesturerecognizer(pan)
}
}
#objc private func panDetected(_ panGesture: UIPanGestureRecognizer) {
var translation = panGesture.translation(in: view)
panGesture.setTranslation(CGPoint(x: 0, y: 0), in: view)
var newButton = UIButton()
if let initButton = panGesture.view as? UIButton {
print ("Button recognized!") // this msg is printed out
newButton.center = CGPoint(x: initButton.center.x + translation.x, y: initButton.center.y + translation.y)
newButton.setImage(UIImage(named: "somename"), for: .normal)
}
if panGesture.state == UIGestureRecognizerState.began {
self.view.addSubview(newButton)
}
if panGesture.state == UIGestureRecognizerState.ended {
//some other processing
}
if panGesture.state == UIGestureRecognizerState.changed {
self.view.addSubview(newButton)
}
// printed-out msgs show began, ended, changed states have all been reached
}
But the new button doesn't show up in my view. May I know how to solve this?
You need to create and add the new button as a subview only on .began and remove it on .ended.
Therefore you need to keep a reference to the new button.
You are setting the new button's center but not it's size. You might set its .frame.
You do not need to set a translation to the pan gesture. When you get var translation = panGesture.translation(in: view) you get everything you need.
I have wrote the below code for only one button, but if you are going to allow simultaneous dragging of buttons, you would need to keep a list of moving buttons instead of var movingButton: UIButton?
private func addPanGesture() {
let pan = UIPanGestureRecognizer(target: self, action:#selector(self.panDetected(_:)))
pan.minimumNumberOfTouches = 1
pan.maximumNumberOfTouches = 1
btn.addGestureRecognizer(pan)
}
#objc private func panDetected(_ panGesture: UIPanGestureRecognizer) {
let translation = panGesture.translation(in: view)
let initButton = panGesture.view as! UIButton
if panGesture.state == UIGestureRecognizerState.began {
// this is just copying initial button
// this might be overkill
// just make sure you set the frame, title and image of the new button correctly
let initButtonData = NSKeyedArchiver.archivedData(withRootObject: initButton)
let newButton = NSKeyedUnarchiver.unarchiveObject(with: initButtonData) as! UIButton
// we store new button's reference since we will just move it while it is added to view
movingButton = newButton
self.view.addSubview(movingButton!)
}
if panGesture.state == UIGestureRecognizerState.ended {
//some other processing
// when we are done just we just remove it from superview
movingButton!.removeFromSuperview()
}
if panGesture.state == UIGestureRecognizerState.changed {
// at any change, all we need to do is update movingButton's frame
var buttonFrame = initButton.frame;
buttonFrame.origin = CGPoint(x: buttonFrame.origin.x + translation.x, y: buttonFrame.origin.y + translation.y)
movingButton!.frame = buttonFrame
}
}
Hard to say without debugging it, but a few things I see:
You create a new button every time through panDetected, and add it to the view each time. You should only create an add the button in the .began state.
You should use init(frame:) to create your button, and initialize it to the size of the image.
It looks like you're attaching the pan gestures to the buttons. Then you get the pan coordinates in the button's coordinate system, which doesn't make sense. You should be converting the pan gesture to the button's superview's coordinate system, and should not be calling setTranslation except when the pan gesture's state is .began.
You should be setting the button's coordinates to the new location of the pan gesture each time you get a 1st.changed` message.

How do I hide image on touch with an array of imageviews?

I have an array of image views.
var imageViewArray = [UIImageView(image: UIImage())]
I use a for loop to fill this array with images from urls. I want to make it so that when I touch one of these images it becomes hidden or alpha: 0. I tried this:
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(StoryVC.imageTapped))
newImage.userInteractionEnabled = true
newImage.addGestureRecognizer(tapGestureRecognizer)
And I tried adding a tag too but I can't figure out how to get the sender. I need to be able to run the function to hide the image and know which image to hide, that is the part i'm struggling with. Thanks in advance.
You get UITapGestureRecognizer object in your selector's parameter and it has a property view that gives you the view which has been tapped.
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.imageTapped(_:)))
func imageTapped(_ sender: UITapGestureRecognizer) {
guard let tappedImage = sender.view else { return }
}
The selector should be a function within your class. Here's an example:
// Setting up the tapGestureRecognizers
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(pressed:))
newImage.userInteractionEnabled = true
newImage.addGestureRecognizer(tapGestureRecognizer)
// The function that handles the tap event
func pressed(sender: UIImageView!) {
if sender.alpha == 0{
sender.alpha = 1
}
else{
self.alpha = 0
}
}
Also make sure to double check my syntax, I don't write with Swift often, so it may have some small errors.

SKTileMapNode: detecting touched Tile?

Imagine a game world that's nothing other than an SKTileMapNode with 10x10 tiles on the screen.
The user touches a tile.
Does SKTileMapNode provide a way to know which tile has been touched? Or do coordinate hunts need be done to determine which tile is in the location of the touch?
Or is there some other way that this should be done?
Using a UITapGestureRecognizer you can retrieve the touched tile using the tileDefinition function from SKTileMapNode.
func handleTapFrom(recognizer: UITapGestureRecognizer) {
    if recognizer.state != .ended {
        return
    }
    let recognizorLocation = recognizer.location(in: recognizer.view!)
    let location = self.convertPoint(fromView: recognizorLocation)
guard let map = childNode(withName: "background") as? SKTileMapNode else {
fatalError("Background node not loaded")
}
let column = map.tileColumnIndex(fromPosition: location)
let row = map.tileRowIndex(fromPosition: location)
let tile = map.tileDefinition(atColumn: column, row: row)
}
Then if you have added userData in the TilemapEditor, this can be retrieved. Values to include in userData might be cost to move through the tile etc.
let data = tile.userData?.value(forKey: "myKey")
The advantage of using Recognizers is that Tap, Pan and Long Press can be handled cleanly in separate functions that don't interfere with each other. You initialise the gesture recognizor in SKScene.
override func didMove(to view: SKView) {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapFrom(recognizer:)))
tapGestureRecognizer.numberOfTapsRequired = 1
view.addGestureRecognizer(tapGestureRecognizer)
}

How to detect tap gesture on chart marker?

I am using Charts to create custom graphs in an application. The one thing I am having trouble with is detecting touch events on the chart marker that is presented for the current value. I want to perform and action based on the marker that was tapped. The action should take in the data entry represented by the tapped marker.
I have read through Selectable marker view & images at Y axis, but still haven't been able to produce a viable solution.
It would be great if someone could provide a code sample, or more detailed explanation than found in the link above, to point me in the right direction.
Thanks
As per your requirement Charts library already have that method in its delegate.
Please check below code :
Assign delegate to your ChartView like below
lineChartView.delegate = self
Implement Delegate method in your class
public func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
}
Here you can write your code while user click on chart.
Hope this info will helps!
I ended up solving this by subclassing the ChartView of my choice and overriding the existing tap gesture recognizer with my own.
Example
final class CustomChartView: BarChartView {
...
private func initialize() {
...
let tap = UITapGestureRecognizer(target: self, action: #selector(tapRecognized))
self.addGestureRecognizer(tap)
}
#objc func tapRecognized(_ recognizer: UITapGestureRecognizer) {
guard data !== nil else { return }
switch recognizer.state {
case .ended:
// Detect whether or not the touch was inside a marker that was being presented
// Otherwise, add/remove highlight as necessary
if let marker = self.marker as? BalloonMarker {
let location = recognizer.location(in: self)
if !highlighted.isEmpty && marker.rect.contains(location) {
// In my case, I created custom property 'vertex' on BalloonMarker for easy reference to get `xValue`
let xValue = self.getTransformer(forAxis: .left).valueForTouchPoint(marker.vertex).x
// Do what you need to here with tap (call function, create custom delegate and trigger it, etc)
// In my case, my chart has a marker tap delegate
// ex, something like: `markerTapDelegate?.tappedMarker()`
return
}
}
// Default tap handling
guard isHighLightPerTapEnabled else { return }
let h = getHighlightByTouchPoint(recognizer.location(in: self))
if h === nil || h == self.lastHighlighted {
lastHighlighted = nil
highlightValue(nil, callDelegate: true)
} else {
lastHighlighted = h
highlightValue(h, callDelegate: true)
}
default:
break
}
}

Resources