Recently, I shared a post that demonstrated how to integrate CorePlot into your Swift-based iOS project. That post covered the basics of how to get started, but there’s an issue with using CorePlot which can cause problems pretty quickly once you get into building your graphs.
CorePlot makes heavy use of the NSDecimal type in Objective-C, which is a special C struct used to store decimal numbers. Swift doesn’t directly support NSDecimal, so any methods that use it in CorePlot APIs are simply missing when you try to access them from Swift.
If you haven’t checked out the previous post, you’ll want to do that first. Starting from that app, we’ll replace all of the graph drawing code with some new code to build a scatter plot. We’ll just have some mock data, with four records, each with the x and y values set to the same number (1 through 4):
You should get something like this:
As you can see, there’s nothing there. This is because the range on our plot isn’t big enough to show the graph. We need to set the length values for the x and y ranges on the default plot space. Our approach will be like this:
- Get the default plot space for the graph
- Get the x range of the plot space
- Create a mutable copy of the x range
- Set the x range’s length
- Set the x range on the plot space to our new mutable copy
- Repeat for the y range
Everything goes fine until you try to set the length of the x range. The length property on the CPTMutablePlotRange is represented as an NSDecimal, so it isn’t bridged into Swift.
Luckily, there’s a pretty easy workaround. By adding a Category for the classes you are using, you can add wrapper methods which use a supported Swift data type. In our case, we’ll be wrapping the CPTMutablePlotRange object in CorePlot, so that we can set the area displayed by our graph.
- In XCode, select File->New->File
- Select iOS->Source->Objective-C File
- For the File Type, select Category, then fill it in like this:
This will create a file called CPTMutablePlotRange+SwiftCompat.h/m. All of the methods we add in the category will be accessible each time we encounter a CPTMutablePlotRange in our app. We’ll just add a method to set the length as a float instead of an NSDecimal:
The last step is to make sure to import CPTMutablePlotRange+SwiftCompat.h in your bridging header (see the previous post for how to set up your bridging header). Once you do that, you should be able to access the new setLengthFloat method any time you encounter a CPTMutablePlotRange.
So, let’s add some more code to the ViewController.swift to set the lengths:
Once this is added, we run the app and get a graph that looks like this:
Cool. It’s not very pretty, but it’s showing all of our data now. As you come across other methods/properties that need an NSDecimal, you can create/add to your categories to wrap the functions.
Eventually, I imagine this will be solved either via Swift adding a way to deal with NSDecimal types, or by CorePlot switching to another data type, but for now it’ll get you by. There’s some discussion on the CorePlot github, which you may want to keep an eye on:
The full code for this is available in the sample app for the previous post, under the nsdecimal-workaround branch here: