iOS & macOS Charting Documentation - SciChart iOS & macOS Charts SDK v4.x

SciChart iOS Tutorial - Accessibility Hooks

SciChart for iOS provides accessibility hooks to enhance usability for users relying on assistive technologies like VoiceOver. This guide shows how to enable and customize accessibility on a SCIChartSurface.

Use-cases:

  • VoiceOver announces axis range when the visible range changes.
  • VoiceOver reads out the x/y values when a user selects a data point.
  • Tick labels respect Dynamic Type settings.
  • Themes adapt to system appearance (Dark/Light Mode).

Accessibility Support for Chart Surfaces

you can enable and configure accessibility properties on the chart surface. This allows users to understand the purpose and data content of the chart through descriptive labels, hints, and value summaries.

// Enable accessibility on the chart surface surface.isAccessibilityElement = YES; // Provide a concise description of the chart surface.accessibilityLabel = @“Sine Wave Line Chart”; // Offer a hint explaining what the chart shows surface.accessibilityHint = @“Displays a sine wave with 1000 data points from X equals 0 to 10.”; // Add value details for additional context surface.accessibilityValue = @“Y values range from negative one to one, with smooth curve transitions.”;
// Enable accessibility on the chart surface surface.isAccessibilityElement = true  // Provide a concise description of the chart surface.accessibilityLabel = “Sine Wave Line Chart” // Offer a hint explaining what the chart shows surface.accessibilityHint = “Displays a sine wave with 1000 data points from X equals 0 to 10.” // Add value details for additional context surface.accessibilityValue = “Y values range from negative one to one, with smooth curve transitions.”

Announcing Axis Range Changes

When the user interacts with the chart and the visible range of an axis changes, a VoiceOver announcement is triggered.

xAxis.visibleRangeChangeListener = ^(id axis, id oldRange, id newRange, BOOL isAnimating) { NSString *strRange = [NSString stringWithFormat:@“%.2f to %.2f”, newRange.minAsDouble, newRange.maxAsDouble]; NSString *announcement = [NSString stringWithFormat:@“X Axis range changed, now it’s %@”, strRange]; UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, announcement); };
xAxis.visibleRangeChangeListener = { (_, _, range, _) in let strRange = String.init(format: “%0.2f to %0.2f”, range.minAsDouble, range.maxAsDouble) let announcement = “X Axis range changed, now it’s \(strRange)” UIAccessibility.post(notification: .announcement, argument: announcement) }

Making Data Points Accessible

Data points can be exposed to VoiceOver by creating UIAccessibilityElements and assigning them to the chart surface.

[self.surface setRenderedListener:^(id surface, id _Nullable unused) { SCIChartSurface *chartSurface = (SCIChartSurface*)surface; if (![dataSeries isKindOfClass:[SCIXyDataSeries class]]) return; NSMutableArray *accessibilityElements = [NSMutableArray array]; id xCoordCalc = xAxis.currentCoordinateCalculator; for (int i = 0; i < dataSeries.count; i++) { double xValue = [[dataSeries.xValues valueAt:i] toDouble]; double yValue = [[dataSeries.yValues valueAt:i] toDouble]; double xCoord = [xCoordCalc getCoordinateFrom:xValue]; UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:chartSurface.renderSurface.view]; NSString *label = [NSString stringWithFormat:@"x value is %.2f and y value is %.2f", xValue, yValue]; accessibilityElement.accessibilityLabel = label; CGRect frame = CGRectMake(xCoord - 15.0, 0.0, 30.0, chartSurface.renderableSeriesArea.view.frame.size.height); CGRect screenFrame = [chartSurface.view convertRect:frame toView:nil]; accessibilityElement.accessibilityFrame = screenFrame; [accessibilityElements addObject:accessibilityElement]; } chartSurface.renderSurface.view.accessibilityElements = accessibilityElements; }];
surface.setRenderedListener { (surface, _) in guard let surface = surface else { return } var accessibilityElements = [UIAccessibilityElement]() let xCoordCalc = xAxis.currentCoordinateCalculator for i in 0 ..< dataSeries.count { let xValue = dataSeries.xValues.value(at: i).toDouble() let yValue = dataSeries.yValues.value(at: i).toDouble() let xCoord = xCoordCalc.getCoordinate(xValue) let accessibilityElement = UIAccessibilityElement(accessibilityContainer: surface.renderSurface.view) let strRange = String.init(format: "x value is %0.2f and y value is %0.2f", xValue, yValue) accessibilityElement.accessibilityLabel = strRange let frame = CGRect( x: Double(xCoord - 15), y: 0, width: 30.0, height: Double(surface.renderableSeriesArea.view.frame.size.height) ) accessibilityElement.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(frame, in: surface.view) accessibilityElements.append(accessibilityElement) } surface.renderSurface.view.accessibilityElements = accessibilityElements }

Custom Actions

- (void)viewDidLoad { … UIAccessibilityCustomAction *zoomExtentsAction = [[UIAccessibilityCustomAction alloc] initWithName:@“Zoom to Extents” target:self selector:@selector(zoomExtentsCustomAction)]; self.accessibilityCustomActions = @[zoomExtentsAction]; } - (BOOL)zoomExtentsCustomAction { [self.surface zoomExtents]; return YES; }
override var accessibilityCustomActions: [UIAccessibilityCustomAction]? { get { return [ UIAccessibilityCustomAction( name: “Zoom to Extents”, target: self, selector: #selector(zoomExtentsCustomAction) ) ] } set { super.accessibilityCustomActions = newValue } } @objc func zoomExtentsCustomAction() -> Bool { surface.zoomExtents() return true }

Supporting Dynamic Type

Tick label fonts scale automatically with system text size preferences.

- (void)viewDidLoad { [super viewDidLoad]; … [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleContentSizeCategoryChange) name:UIContentSizeCategoryDidChangeNotification object:nil]; } - (void)handleContentSizeCategoryChange { id primaryXAxis = self.surface.xAxes.primaryAxis; if (primaryXAxis) { primaryXAxis.tickLabelStyle = [self scaledFontStyleFor:primaryXAxis.tickLabelStyle]; } id primaryYAxis = self.surface.yAxes.primaryAxis; if (primaryYAxis) { primaryYAxis.tickLabelStyle = [self scaledFontStyleFor:primaryYAxis.tickLabelStyle]; } } - (SCIFontStyle _)scaledFontStyleFor:(SCIFontStyle _)fontStyle { UIFontDescriptor *fontDescriptor = fontStyle.fontDescriptor; UIFontMetrics *fontMetrics = [UIFontMetrics metricsForTextStyle:UIFontTextStyleBody]; UIFont \*scaledFont = [fontMetrics scaledFontForFont:[UIFont fontWithDescriptor:fontDescriptor size:fontDescriptor.pointSize]]; return [[SCIFontStyle alloc] initWithFontDescriptor:scaledFont.fontDescriptor andTextColor:fontStyle.color]; }
override func viewDidLoad() { … NotificationCenter.default.addObserver( self, selector: #selector(handleContentSizeCategoryChange), name: UIContentSizeCategory.didChangeNotification, object: nil ) } @objc func handleContentSizeCategoryChange() { // Update fonts if necessary if let primaryXAxis = surface.xAxes.primaryAxis { primaryXAxis.tickLabelStyle = scaledFontStyle(for: primaryXAxis.tickLabelStyle) } if let primaryYAxis = surface.yAxes.primaryAxis { primaryYAxis.tickLabelStyle = scaledFontStyle(for: primaryYAxis.tickLabelStyle) } } func scaledFontStyle(for fontStyle: SCIFontStyle) -> SCIFontStyle { let fontDescriptor = fontStyle.fontDescriptor let fontMetrics = UIFontMetrics(forTextStyle: UIFont.TextStyle.body) let font = fontMetrics.scaledFont(for: UIFont(descriptor: fontDescriptor, size: fontDescriptor.pointSize)) return SCIFontStyle(fontDescriptor: font.fontDescriptor, andTextColor: fontStyle.color) }

Adapting to System Theme (Dark/Light Mode)

Change chart theme based on system appearance:

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) { if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { [SCIThemeManager applyTheme:SCIChartThemeV4Dark toThemeable:self.surface]; } else { [SCIThemeManager applyTheme:SCIChartThemeBrightSpark toThemeable:self.surface]; } } }
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { if traitCollection.userInterfaceStyle == .dark { SCIThemeManager.applyTheme(.v4Dark, to: surface) } else { SCIThemeManager.applyTheme(.brightSpark, to: surface) } } }

Where to Go From Here?

You can download the final project from our GitHub Repository:

Also, you can found next tutorial from this series here - SciChart iOS Tutorial - ChartModifier To ViewModel

Of course, this is not the limit of what you can achieve with the SciChart iOS. You might want to read some of the following articles:

Finally, start exploring. The SciChart iOS is quite extensive. You can look into our SciChart iOS Examples Suite which are full of 2D and 3D examples, which are also available on our GitHub.