Pre loader

iOS & macOS Custom Gesture Modifier

iOS & macOS charts - Examples

Please note! These examples are new to SciChart iOS v4 release! SciChart’s OpenGL ES and Metal iOS and Metal macOS Chart library ships with hundred of Objective-C and Swift iOS & macOS Chart Examples which you can browse, play with and view the source-code. All of this is possible with the new and improved SciChart iOS Examples Suite and demo application for Mac, which ships as part of the SciChart SDK.

Download Examples

This example demonstrates how to createa custom gestures-based chart modifier that you can attach to a chart in your iOS application to perfrom a specific behaviour with SciChart iOS API. Any time you want to do something to alter the behavior of any built-in gesture modifiers, you should be thinking about creating a custom gesture-based chart modifier to do it.

In this example, a single finger vertical pinch-zoom gesture-based chart modifier is implemented to zoom the chart on X-Axis. Also it is enabled by double-tap, similar to how it works in Google Maps App.

Learn more from SCIGestureModifierBase API Documentation

The Swift and Objective-C source code for the iOS and macOS Custom Gesture Modifierexample is included below (Scroll down!).

Did you know that we have the source code for all our example available for free on Github?

Clone the SciChart.iOS.Examples from Github.

Also the SciChart iOS and Scichart macOS Trials contain the full source for the examples (link below).

DOWNLOAD THE IOS CHART EXAMPLES

CustomGestureModifier.swift
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// CustomGestureModifier.swift is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

import SciChart.Protected.SCIGestureModifierBase

class CustomGestureModifier: SCIGestureModifierBase {
    private var initialLocation = CGPoint.zero
    private let scaleFactor: CGFloat = -50
    private var canPan = true
    
    #if os(iOS)
    private lazy var doubleTapGesture: DoubleTouchDownGestureRecognizer = {
        let gesture = DoubleTouchDownGestureRecognizer(target: self, action: #selector(handleDoubleTapGesture))
        
        return gesture
    }()
    
    override func attach(to services: ISCIServiceContainer) {
        super.attach(to: services)
        
        if let parentSurface = self.parentSurface as? SCIView {
            parentSurface.addGestureRecognizer(doubleTapGesture)
        }
        
        canPan = false
    }
    
    @objc private func handleDoubleTapGesture(_ gesture: DoubleTouchDownGestureRecognizer) {
        canPan = true
    }
    
    override func onGestureEnded(with args: SCIGestureModifierEventArgs) {
        canPan = false
    }
    
    override func onGestureCancelled(with args: SCIGestureModifierEventArgs) {
        canPan = false
    }
    #endif
    
    override func onGestureBegan(with args: SCIGestureModifierEventArgs) {
        guard canPan else { return }
        guard let gesture = args.gestureRecognizer as? SCIPanGestureRecognizer else { return }
        let parentView = self.parentSurface?.modifierSurface.view
        
        initialLocation = gesture.location(in: parentView)
    }
    
    override func onGestureChanged(with args: SCIGestureModifierEventArgs) {
        guard canPan else { return }
        guard let gesture = args.gestureRecognizer as? SCIPanGestureRecognizer else { return }
        let parentView = self.parentSurface?.modifierSurface.view
        
        let translationY = gesture.translation(in: parentView).y
        performZoom(point: initialLocation, yScaleFactor: translationY)
        gesture.setTranslation(.zero, in: parentView)
    }
    
    override func createGestureRecognizer() -> SCIGestureRecognizer {
        return SCIPanGestureRecognizer()
    }
    
    private func performZoom(point: CGPoint, yScaleFactor:CGFloat) {
        let fraction = yScaleFactor / scaleFactor
        for i in 0 ..< self.xAxes.count {
            growAxis(self.xAxes[i], at: point, by: fraction)
        }
    }
    
    private func growAxis(_ axis: ISCIAxis, at point: CGPoint, by fraction: CGFloat) {
        let width = axis.layoutSize.width
        let coord = width - point.x
        
        let minFraction = (coord / width) * fraction
        let maxFraction = (1 - coord / width) * fraction
        
        axis.zoom(byFractionMin: minFraction, max: maxFraction)
    }
}
CustomGestureModifierChartView.m
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2019. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// ImpulseChartView.m is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

#import "CustomGestureModifierChartView.h"
#import "SCDDataManager.h"
#import "SCDCustomGestureModifier.h"

@implementation CustomGestureModifierChartView

- (Class)associatedType { return SCIChartSurface.class; }

- (BOOL)showDefaultModifiersInToolbar { return NO; }

- (void)initExample {
    id<ISCIAxis> xAxis = [SCINumericAxis new];
    xAxis.growBy = [[SCIDoubleRange alloc] initWithMin:0.1 max:0.1];
    
    id<ISCIAxis> yAxis = [SCINumericAxis new];
    yAxis.growBy = [[SCIDoubleRange alloc] initWithMin:0.1 max:0.1];
    
    SCDDoubleSeries *ds1Points = [SCDDataManager getDampedSinewaveWithAmplitude:1.0 DampingFactor:0.05 PointCount:50 Freq:5];
    SCIXyDataSeries *dataSeries = [[SCIXyDataSeries alloc] initWithXType:SCIDataType_Double yType:SCIDataType_Double];
    [dataSeries appendValuesX:ds1Points.xValues y:ds1Points.yValues];
    
    SCIEllipsePointMarker *ellipsePointMarker = [SCIEllipsePointMarker new];
    ellipsePointMarker.strokeStyle = SCIPenStyle.TRANSPARENT;
    ellipsePointMarker.fillStyle = [[SCISolidBrushStyle alloc] initWithColorCode:0xFF47bde6];
    ellipsePointMarker.size = CGSizeMake(10, 10);
    
    SCIFastImpulseRenderableSeries *rSeries = [SCIFastImpulseRenderableSeries new];
    rSeries.dataSeries = dataSeries;
    rSeries.pointMarker = ellipsePointMarker;
    rSeries.strokeStyle = [[SCISolidPenStyle alloc] initWithColorCode:0xFF47bde6 thickness:1.0];
    
#if TARGET_OS_OSX
    NSString *zoomText = @"Pan vertically to Zoom In/Out.";
#elif TARGET_OS_IOS
    NSString *zoomText = @"Double Tap and pan vertically to Zoom In/Out.";
#endif
    
    SCITextAnnotation *annotation = [SCITextAnnotation new];
    annotation.text = [NSString stringWithFormat:@"%@ %@",zoomText, @"\nDouble tap to Zoom Extents."];
    annotation.fontStyle = [[SCIFontStyle alloc] initWithFontSize:16 andTextColor:SCIColor.whiteColor];
    annotation.x1 = @(0.5);
    annotation.y1 = @(0);
    annotation.coordinateMode = SCIAnnotationCoordinateMode_Relative;
    annotation.verticalAnchorPoint = SCIVerticalAnchorPoint_Top;
    annotation.horizontalAnchorPoint = SCIHorizontalAnchorPoint_Center;
    
    SCDCustomGestureModifier *customGestureModifier = [SCDCustomGestureModifier new];
    customGestureModifier.receiveHandledEvents = YES;
    
    [SCIUpdateSuspender usingWithSuspendable:self.surface withBlock:^{
        [self.surface.xAxes add:xAxis];
        [self.surface.yAxes add:yAxis];
        [self.surface.renderableSeries add:rSeries];
        [self.surface.annotations add:annotation];
        [self.surface.chartModifiers addAll:customGestureModifier, [SCIZoomExtentsModifier new], nil];

        [SCIAnimations waveSeries:rSeries duration:3.0 andEasingFunction:[SCICubicEase new]];
    }];
}

@end
CustomGestureModifierChartView.swift
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// CustomGestureModifierChartView.swift is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

class CustomGestureModifierChartView: SCDSingleChartViewController<SCIChartSurface> {
    
    override var associatedType: AnyClass { return SCIChartSurface.self }
    
    override var showDefaultModifiersInToolbar: Bool { return false }
    
    override func initExample() {
        let xAxis = SCINumericAxis()
        xAxis.growBy = SCIDoubleRange(min: 0.1, max: 0.1)
        
        let yAxis = SCINumericAxis()
        yAxis.growBy = SCIDoubleRange(min: 0.1, max: 0.1)
        
        let ds1points = SCDDataManager.getDampedSinewave(withAmplitude: 1.0, dampingFactor: 0.05, pointCount: 50, freq: 5)
        let dataSeries = SCIXyDataSeries(xType: .double, yType: .double)
        dataSeries.append(x: ds1points.xValues, y: ds1points.yValues)
        
        let ellipsePointMarker = SCIEllipsePointMarker()
        ellipsePointMarker.strokeStyle = SCIPenStyle.transparent
        ellipsePointMarker.fillStyle = SCISolidBrushStyle(color: 0xFF47bde6)
        ellipsePointMarker.size = CGSize(width: 10, height: 10)
        
        let rSeries = SCIFastImpulseRenderableSeries()
        rSeries.dataSeries = dataSeries
        rSeries.pointMarker = ellipsePointMarker
        rSeries.strokeStyle = SCISolidPenStyle(color: 0xFF47bde6, thickness: 1.0)
        
#if os(OSX)
        let zoomText = "Pan vertically to Zoom In/Out."
#elseif os(iOS)
        let zoomText = "Double Tap and pan vertically to Zoom In/Out."
#endif
        let annotation = SCITextAnnotation()
        annotation.text = zoomText + " \nDouble tap to Zoom Extents."
        annotation.fontStyle = SCIFontStyle(fontSize: 16, andTextColor: .white)
        annotation.set(x1: 0.5)
        annotation.set(y1: 0)
        annotation.coordinateMode = .relative
        annotation.verticalAnchorPoint = .top
        annotation.horizontalAnchorPoint = .center
        
        let customGestureModifier = CustomGestureModifier()
        customGestureModifier.receiveHandledEvents = true
        
        SCIUpdateSuspender.usingWith(surface) {
            self.surface.xAxes.add(xAxis)
            self.surface.yAxes.add(yAxis)
            self.surface.renderableSeries.add(rSeries)
            self.surface.annotations.add(annotation)
            self.surface.chartModifiers.add(items: customGestureModifier, SCIZoomExtentsModifier())
            
            SCIAnimations.wave(rSeries, duration: 3.0, andEasingFunction: SCICubicEase())
        }
    }
}
DoubleTouchDownGestureRecognizer.swift
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// DoubleTouchDownGestureRecognizer.swift is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

import Foundation

#if os(iOS)

class DoubleTouchDownGestureRecognizer: SCITapGestureRecognizer {
    
    override init(target: Any?, action: Selector?) {
        super.init(target: target, action: action)
        
        numberOfTapsRequired = 2
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        guard let firstTouch = touches.first, firstTouch.tapCount == 2 else { return }
        
        if self.state == .possible {
            self.state = .recognized
        }
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        self.state = .failed
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        self.state = .failed
    }
}

#endif
SCDCustomGestureModifier.m
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// SCDCustomGestureModifier.m is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

#import "SCDCustomGestureModifier.h"
#import <SciChart/SCIGestureModifierBase+Protected.h>
#import "SCDDoubleTouchDownGestureRecognizer.h"

@implementation SCDCustomGestureModifier {
#if TARGET_OS_IOS
    SCDDoubleTouchDownGestureRecognizer *_doubleTapGesture;
#endif
    CGPoint _initialLocation;
    CGFloat _scaleFactor;
    BOOL _canPan;
}

- (void)attachTo:(id<ISCIServiceContainer>)services {
    [super attachTo:services];
    
    _initialLocation = CGPointZero;
    _scaleFactor = -50.0;
#if TARGET_OS_OSX
    _canPan = YES;
#elif TARGET_OS_IOS
    _canPan = NO;
    _doubleTapGesture = [[SCDDoubleTouchDownGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTapGesture:)];
    
    if ([self.parentSurface isKindOfClass:[SCIView class]]) {
        SCIView *surfaceView = (SCIView *)self.parentSurface;
        [surfaceView addGestureRecognizer:_doubleTapGesture];
    }
#endif
}

#if TARGET_OS_IOS
- (void)onGestureEndedWithArgs:(SCIGestureModifierEventArgs *)args {
    _canPan = NO;
}

- (void)onGestureCancelledWithArgs:(SCIGestureModifierEventArgs *)args {
    _canPan = NO;
}

- (void)handleDoubleTapGesture:(SCDDoubleTouchDownGestureRecognizer *)gesture {
    _canPan = YES;
}
#endif

- (void)onGestureBeganWithArgs:(SCIGestureModifierEventArgs *)args {
    if (!_canPan) return;
    
    SCIPanGestureRecognizer *gesture = (SCIPanGestureRecognizer *)args.gestureRecognizer;
    SCIView *parentView = self.parentSurface.modifierSurface.view;
    
    _initialLocation = [gesture locationInView:parentView];
}

- (void)onGestureChangedWithArgs:(SCIGestureModifierEventArgs *)args {
    if (!_canPan) return;
    
    SCIPanGestureRecognizer *gesture = (SCIPanGestureRecognizer *)args.gestureRecognizer;
    SCIView *parentView = self.parentSurface.modifierSurface.view;
     
    CGFloat translationY = [gesture translationInView:parentView].y;
    [self p_SCD_performZoom:_initialLocation yScaleFactor:translationY];
    [gesture setTranslation:CGPointZero inView:parentView];
}

- (SCIGestureRecognizer *)createGestureRecognizer {
    return [SCIPanGestureRecognizer new];
}

- (void)p_SCD_performZoom:(CGPoint)point yScaleFactor:(CGFloat)yScaleFactor {
    CGFloat fraction = yScaleFactor / _scaleFactor;
    for (NSUInteger i = 0, count = self.xAxes.count; i < count; i++) {
        [self growAxis:self.xAxes[i] atPoint:point byFraction:fraction];
    }
}

- (void)growAxis:(id<ISCIAxis>)axis atPoint:(CGPoint)point byFraction:(CGFloat)fraction {
    CGFloat width = axis.layoutSize.width;
    CGFloat coord = width - point.x;
    
    double minFraction = (coord / width) * fraction;
    double maxFraction = (1 - coord / width) * fraction;
    
    [axis zoomByFractionMin:minFraction max:maxFraction];
}

@end
SCDDoubleTouchDownGestureRecognizer.m
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// SCDDoubleTouchDownGestureRecognizer.m is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

#import "SCDDoubleTouchDownGestureRecognizer.h"

#if TARGET_OS_IOS

@implementation SCDDoubleTouchDownGestureRecognizer

- (instancetype)initWithTarget:(id)target action:(SEL)action {
    self = [super initWithTarget:target action:action];
    if (self) {
        self.numberOfTapsRequired = 2;
    }
    return self;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSOrderedSet<UITouch *> *orderedTouches = [[NSOrderedSet alloc] initWithSet:touches];
    UITouch *firstTouch = [orderedTouches firstObject];
    
    if (firstTouch.tapCount != 2) return;
    
    if (self.state == UIGestureRecognizerStatePossible) {
        self.state = UIGestureRecognizerStateRecognized;
    }
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.state = UIGestureRecognizerStateFailed;
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.state = UIGestureRecognizerStateFailed;
}

@end

#endif
Back to iOS & macOS charts Examples