# iOS development SwiftUI experiment - building custom chart

SwiftUI is the future of **iOS development**, right? We are all excited about how it will influence our mobile development process. At the same time, we are wondering if SwiftUI is ready to shine right now. We are going to find out! In the upcoming blog posts, we’ll try to build a completely custom line chart with extraordinary animations and Hollywood like effects ✨ This part is all about building a simple chart with a background grid.

# How to build SwiftUI new chart for your iOS app

But let’s start simple. You need to define some kind of data model for our new chart. Let’s start with a raw Point** **model, nothing too sophisticated just simple X/Y coordinates. You can extend its capabilities in the future if needed.

```
struct Point {
let x: CGFloat
let y: CGFloat
}
```

Use **CGFloat** instead of **Double** to get rid of conversion (the drawing system of SwiftUI is based on CGFloat).

You need some temporary mock data as well:

```
let data: [Point] = [
.init(x: 1, y: 5),
.init(x: 2, y: 4),
.init(x: 3, y: 15),
.init(x: 4, y: 6),
.init(x: 5, y: 9),
.init(x: 6, y: 12),
.init(x: 7, y: 14),
.init(x: 8, y: 11)
]
```

OK, data is set up. It's time for actual chart views. You need some kind of chart grid to better visualize our data. But first of all, you should start by defining **ChartView** itself.

```
struct ChartView: View {
let xStepValue: CGFloat
let yStepValue: CGFloat
let data: [Point]
private var maxYValue: CGFloat {
data.max { $0.y < $1.y }?.y ?? 0
}
private var maxXValue: CGFloat {
data.max { $0.x < $1.x }?.x ?? 0
}
private var xStepsCount: Int {
Int(self.maxXValue / self.xStepValue)
}
private var yStepsCount: Int {
Int(self.maxYValue / self.yStepValue)
}
var body: some View {
}
}
```

We initialize our ChartView with an array of Point models and values of the X/Y axis step. Add some computed properties which will be handy in the grid/chart calculations. Finally, let’s get our hands dirty in the actual drawing. You have to calculate line positions relative to view size. But how do you get the size of view in SwiftUI? Fortunately, here comes a **GeometryReader** to the rescue**. **It** **is a container view that gives you access to information about its size and coordinates. You are going to use it to calculate the position of the grid lines.

```
var body: some View {
ZStack {
gridBody
}
}
private var gridBody: some View {
GeometryReader { geometry in
Path { path in
let xStepWidth = geometry.size.width / CGFloat(self.xStepsCount)
let yStepWidth = geometry.size.height / CGFloat(self.yStepsCount)
// Y axis lines
(1...self.yStepsCount).forEach { index in
let y = CGFloat(index) * yStepWidth
path.move(to: .init(x: 0, y: y))
path.addLine(to: .init(x: geometry.size.width, y: y))
}
// X axis lines
(1...self.xStepsCount).forEach { index in
let x = CGFloat(index) * xStepWidth
path.move(to: .init(x: x, y: 0))
path.addLine(to: .init(x: x, y: geometry.size.height))
}
}
.stroke(Color.gray)
}
}
```

Pretty straightforward, calculate X/Y step width then iterate over steps count drawing vertical/horizontal lines. It’s a bit better but still, there is no chart at all ☹️ Let's add one more variable:

```
private var chartBody: some View {
GeometryReader { geometry in
Path { path in
path.move(to: .init(x: 0, y: geometry.size.height))
self.data.forEach { point in
let x = (point.x / self.maxXValue) * geometry.size.width
let y = geometry.size.height - (point.y / self.maxYValue) * geometry.size.height
path.addLine(to: .init(x: x, y: y))
}
}
.stroke(
Color.black,
style: StrokeStyle(lineWidth: 3)
)
}
}
```

Here it is! Your first very own chart! Nothing too fancy, once more you have used **GeometryReader** to get the size of the view. With the size in the hand, you have to calculate coordinates of the next point. To do so you have to get a proportion of the x/y value of the point to the max values. The result is a part of the whole available width/height. Keep in mind that **.zero** point of view is in the top left corner while you are drawing from the bottom left corner. So to get the correct value of y you have to subtract the result from the view height. Then you simply draw a line to the calculated point.

# How to modify your iOS app new chart with SwiftUI

Your chart is almost ready but it looks a bit too… sharp.

What about making it a bit more curved? Let’s modify the **chartBody** like this:

```
private var chartBody: some View {
GeometryReader { geometry in
Path { path in
path.move(to: .init(x: 0, y: geometry.size.height))
// 1
var previousPoint = Point(x: 0, y: geometry.size.height)
self.data.forEach { point in
let x = (point.x / self.maxXValue) * geometry.size.width
let y = geometry.size.height - (point.y / self.maxYValue) * geometry.size.height
// 2
let deltaX = x - previousPoint.x
let curveXOffset = deltaX * self.lineRadius
// 3
path.addCurve(to: .init(x: x, y: y),
control1: .init(x: previousPoint.x + curveXOffset, y: previousPoint.y),
control2: .init(x: x - curveXOffset, y: y ))
previousPoint = .init(x: x, y: y)
}
}
.stroke(
Color.black,
style: StrokeStyle(lineWidth: 3)
)
}
}
```

Here's what is happening. First of all, you have to store the previous **Point** which will be used to calculate the position of curve points.

Now you can calculate X delta between current and previous **Point**. To get an offset value for curve points you have to multiply the delta with a new property **lineRadius. **It tells how curved the chart should be.** **The** lineRadius** has to be a value within 0...1 range (where 0 means no curve at all).Finally, instead of **addLine **use the **addCurve** function. This lets you add two curves on the line between two points. We can achieve that by specifying two control points. The first one is moved to the right side by the calculated offset and the second one to the left side. This gives us a nice effect of a curved line ⎰.

Now it's way better! I can't wait to build the rest of the functionalities on top of it. It's a great start but there is a lot of work ahead of you. In the next part of the **iOS development** SwiftUI experiment, you’ll try to add some gradients and transition animation.

I hope you enjoy the post. Thanks for reading and see you soon! 👋