I am drawing a pie chart using the CPTXYGraph in Core Plot. I have used the delegate method
-(void)pieChart:(CPTPieChart *)plot sliceWasSelectedAtRecordIndex:(NSUInteger)index
to perform some actions when a particular slice of a pie is clicked. But when I touch the piechart I need to have a feedback or some type of image change to be shown to signify that particular slice is touched just like the button events.
Are there any delegate methods to achieve the same.
There'll be some work to do.
First of all, Implement this into Pie chart delegate function.
-(void)pieChart:(CPTPieChart *)plot sliceWasSelectedAtRecordIndex:(NSUInteger)index
{
indexOfPieGraphPlot = index; //gives index of pie which is selected.
isPieGraphPlot = YES; //boolean. if set to yes, will call rootcontroller function which will add pop up.
}
Then add the CPTPlotSpaceDelegate in .h file
then use this function
- (BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceUpEvent:(id)event atPoint:(CGPoint)point
{
if(isPieGraphPlot) //if yes call showpopup of rootcontroller
{
isPieGraphPlot = NO;
[rootController showPopUpView:point indexForPlot:indexOfPieGraphPlot];
// point gives the x,y of the pie over which you want a pop up.
}
else // if no then call remove popup from rootcontroller. this, incase user clicks anywhere else in graph.
{
[rootController removePopUp];
}
return YES;
}
Implement the -sliceFillForPieChart:recordIndex: method in your datasource. Keep track of the selected slice (if any) and use that to decide what fill to apply to each slice. Call -reloadData on the plot any time the selection changes to force it to load new slice fills.
Related
I am working with MKMapView. When the user scrolls the map in a way that blue dot indicating user's current location disappears from his sight, tapping a button on mapView brings him back to the portion that contains that blue dot. I am doing it simply by setting
self.mapView.centerCoordinate = self.mapView.userLocation.coordinate;
on button's action method.
But I have to change button's image once he scrolls away from blue dot. So, how would I know if blue dot is not in the portions of the map that is currently visible to the user.
You can add a delegate to the map view, mapView:regionDidChangeAnimated:, so that you are informed when a scroll event has occurred.
In your implementation of that method you can get the visibleMapRect and use MKCoordinateRegionForMapRect to get the associated real world coordinates of the map view. You can then determine if the current user location is visible (this requires a simple calculation, I don't think there's a built in method).
You can use the built in boolean flag self.mapView.userLocationVisible
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated{
//do something here
if(self.mapView.userLocationVisible){
//Blue dot visible
}
else{
//Blue dot hidden
}
//do something here
}
For those interested in code:
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated{
if (MKMapRectContainsPoint(self.mapView.visibleMapRect, MKMapPointForCoordinate(self.mapView.userLocation.coordinate))) {
NSLog(#"blue dot visible");
}else{
NSLog(#"blue dot not visible");
}
}
I have a feature to place an annotation to a map by performing a long press at a point on the map. Then user can move the annotation to other position by dragging.
I was able to achieve that using RouteMe, but now I want to replace RouteMe with Skobbler.
Is there any property to enable dragging for annotations/markers on map using Skobbler? If yes, please help me achieve it.
Don't you wan't to do this :
Setting the annotation delegate, then
- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate {
coordinate = newCoordinate;
if (self.delegate respondsToSelector:#selector(mapAnnotationCoordinateHaveChanged))
[self.delegate performSelector(#selector(mapAnnotationCoordinateHaveChanged))];
}
In your delegate :
- (void) mapAnnotationCoordinateHaveChanged{
//some coordinates update code
}
I working around with map app, I want to ask how to change the polyline color without remove and add it again, I found this topic https://stackoverflow.com/questions/24226290/mkpolylinerenderer-change-color-without-removing-overlay in stackoverflow but this is not involve with my question, I did not touch the line, so no need to do with -[MKMapViewDelegate mapView:didSelectAnnotationView:]
So is it possible to do that?
EDIT: What I want to do is change the polyline color smoothly (by shading a color - sound like an animation) If you have any idea on how to animate this polyline please also tell me too. Thanks
Complex animations or shading/gradients will probably require creating a custom overlay renderer class.
These other answers give ideas about how to draw gradient polylines and animations will most like require a custom overlay renderer as well:
how to customize MKPolyLineView to draw different style lines
Gradient Polyline with MapKit ios
Draw CAGradient within MKPolyLineView
Apple's Breadcrumb sample app also has an example of a custom renderer which you may find useful.
However, if you just want to update the line's color (say from blue to red), then you may be able to do that as follows:
Get a reference to the MKPolyline you want to change.
Get a reference to the MKPolylineRenderer for the polyline obtained in step 1. This can be done by calling the map view's rendererForOverlay: instance method (not the same as the mapView:rendererForOverlay: delegate method.
Update the renderer's strokeColor.
Call invalidatePath on the renderer.
Not sure what you want but you may be able to "animate" the color going from blue to red by changing the color and calling invalidatePath gradually in timed steps.
Another important thing is to make sure the rendererForOverlay delegate method also uses the line's "current" color in case the map view calls the delegate method after you've changed the renderer's strokeColor directly.
Otherwise, after panning or zooming the map, the polyline's color will change back to whatever's set in the delegate method.
You could keep the line's current color in a class-level variable and use that in both the delegate method and the place where you want to change the line's color.
An alternative to a class-level variable (and probably better) is to either use the MKPolyline's title property to hold its color or a custom polyline overlay class (not renderer) with a color property.
Example:
#property (nonatomic, strong) UIColor *lineColor;
//If you need to keep track of multiple overlays,
//try using a NSMutableDictionary where the keys are the
//overlay titles and the value is the UIColor.
-(void)methodWhereYouOriginallyCreateAndAddTheOverlay
{
self.lineColor = [UIColor blueColor]; //line starts as blue
MKPolyline *pl = [MKPolyline polylineWithCoordinates:coordinates count:count];
pl.title = #"test";
[mapView addOverlay:pl];
}
-(void)methodWhereYouWantToChangeLineColor
{
self.lineColor = theNewColor;
//Get reference to MKPolyline (example assumes you have ONE overlay)...
MKPolyline *pl = [mapView.overlays objectAtIndex:0];
//Get reference to polyline's renderer...
MKPolylineRenderer *pr = (MKPolylineRenderer *)[mapView rendererForOverlay:pl];
pr.strokeColor = self.lineColor;
[pr invalidatePath];
}
-(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKPolyline class]]) {
MKPolylineRenderer *pr = [[MKPolylineRenderer alloc] initWithPolyline:overlay];
pr.strokeColor = self.lineColor;
pr.lineWidth = 5;
return pr;
}
return nil;
}
Yous hould look at MKOverlayPathRenderer method
- invalidatePath.
From the doc, it says:
Call this method when a change in the path information would require
you to recreate the overlay’s path. This method sets the path property
to nil and tells the overlay renderer to redisplay its contents.
So, at this moment, you should be able to change your drawing color.
I'm using CPTScatterPlot to display data, also I have data labels above all plot symbols. I need to detect user touches and using - plot:dataLabelWasSelectedAtRecordIndex: to detect it. But if I click on the data label and try to scroll, scrolling is not working.
Is there any way to detect if user touches and yet not interrupt scrolling?
We've made some changes to the event handling and related delegate methods on the release-2.0 branch of the Core Plot code. Get the latest code from GitHub and switch to the release-2.0 branch to get the updated code. Note that this update requires the Accelerate framework and raises the minimum system requirements from the 1.x releases on the master branch.
I fix this problem in my code, using graph plot space delegate instead of CPTPlot delegate.
-(BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceDownEvent:(CPTNativeEvent *)event atPoint:(CGPoint)point {
_lastPlotSpaceDownPoint=point;
return YES;
}
-(BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceUpEvent:(CPTNativeEvent *)event atPoint:(CGPoint)point {
CGFloat distance = hypotf((_lastPlotSpaceDownPoint.x - point.x), (_lastPlotSpaceDownPoint.y - point.y));
if (abs(distance) < 5) {
NSUInteger recordIndex=[self.linePlot indexOfVisiblePointClosestToPlotAreaPoint:point];
// your code here
}
return YES;
}
I have a floor plan with many exhibitor stands. When a UIView loads, a UIImage is displayed with the floor plan and a database is checked for a list of exhibitors and their locations. The locations are loaded into an array and a UIButton is created for each exhibitor and placed over the floor plan where their stand is. When tapped, this button will show information about that exhibitor.
Here is a screenshot of the floor plan with boxes where the buttons are rendered.
This works fine as it is BUT I need the buttons to be irregular shapes (triangles, pentagons, circles etc). So I need a way of drawing these shapes and having them clickable in the same way the buttons were.
I have created a test class which generates a UIView which contains the shape and added it to my original UIView. I get the feeling this may not be the correct way to do this as I will need to have many buttons on the screen and this would mean many views stacked on each other. I don't know how I could check which shape was tapped as the UIViews would overlap each other.
Can all the shapes be drawn on one view and then the view added? What is the best approach to this?
In terms of UI the cleanest thing to do is to use actual buttons. Set their type to custom, and set their image property to the image you want to display. That way the buttons handle highlighting correctly, and manage IBActions just like regular buttons. You can set all the buttons to point to the same action, and use tag values to figure out which button is which.
You can create these buttons either from code or in IB - whichever fits your design better.
You could also do this with custom views or a single view that has drawing on it. IF you use views for each booth, you would need to attach a tap gesture recognizer to each view, and set it's userInteractionEnabled flag to YES.
If you want to use a drawing for the entire floor plan, you would need to add a tap gesture recognizer to the drawing view, and then interpret the coordinates of the tap to figure out which image it lands on.
override
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
method of your map representer view. So you will be able to compare current touch location with all of your shapes, and calculate in which shape your touch point is laying. There different approach for different shapes (e.g is point inside a rectangle?)
ole has a great project: OBShapedButtons. He achieves it by checking the alpha value of the touched pixel and overwriting -pointInside:withEvent:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// Return NO if even super returns NO (i.e., if point lies outside our bounds)
BOOL superResult = [super pointInside:point withEvent:event];
if (!superResult) {
return superResult;
}
// Don't check again if we just queried the same point
// (because pointInside:withEvent: gets often called multiple times)
if (CGPointEqualToPoint(point, self.previousTouchPoint)) {
return self.previousTouchHitTestResponse;
} else {
self.previousTouchPoint = point;
}
BOOL response = NO;
if (self.buttonImage == nil && self.buttonBackground == nil) {
response = YES;
}
else if (self.buttonImage != nil && self.buttonBackground == nil) {
response = [self isAlphaVisibleAtPoint:point forImage:self.buttonImage];
}
else if (self.buttonImage == nil && self.buttonBackground != nil) {
response = [self isAlphaVisibleAtPoint:point forImage:self.buttonBackground];
}
else {
if ([self isAlphaVisibleAtPoint:point forImage:self.buttonImage]) {
response = YES;
} else {
response = [self isAlphaVisibleAtPoint:point forImage:self.buttonBackground];
}
}
self.previousTouchHitTestResponse = response;
return response;
}
Another sample code that test if the point is within a layer mask, maybe you can adapt that more easily:
#implementation MyView
//
// ...
//
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint p = [self convertPoint:point toView:[self superview]];
if(self.layer.mask){
if (CGPathContainsPoint([(CAShapeLayer *)self.layer.mask path], NULL, p, YES) )
return YES;
}else {
if(CGRectContainsPoint(self.layer.frame, p))
return YES;
}
return NO;
}
#end
The full article: http://blog.vikingosegundo.de/2013/10/01/hittesting-done-right/
In the end this is what I did:
Looped through all of the stand shapes I needed to have and created a dictionary with UIBezierpath of their coordinates on the floorplan image and the standNo for the shape.
I then added these dictionaries to an array.
When the screen was tapped I would note the X and Y of the tap position and looped through the array of dictionaries and it checked if the UIBezierpath contained a point made up of the X and Y tapped coordinates.
If a shape was found that had the X and Y coordinates within its bounds I would draw a CAShapelayer using the UIBezierpath and adding a fillColor so it showed up on the map. An alertView was then displayed which showed more information about the exhibitor.
This method seemed WAY more efficient than actually drawing out hundreds of UIButtons or even CAShaplayers and even with 300+ areas on the floorplan to check through the process appears instant.