Before we even touch the code, I’d like to point out a very nice way to debug your view hierarchy. Run your app in the simulator.
Click the Debug View Hierarchy icon on the debug bar.
Xcode lays out all your views and subviews, and when I drag the view, you can see them laid out in 3d.
On the left, all the views are listed in a hierarchy, and you can see how the SwiftUI views are hosted in a UIWindow. Open up the Object Inspector and you can see that the SwiftUI views are hosted by a UIHostingController.
If one of your views is showing up in the wrong place, you can use this debugger to visualize all your views and work out why they are misplaced.
This is the GeometryReader. Notice that unlike the Color view, it’s not full screen, which might cause a problem later on.
ContentView.swift
So we can fix that in ContentView.
GeometryReader {
...
}
🟩.ignoresSafeArea()
And run the app again. And click the Debug View Hierarchy icon. And the GeometryReader is now full screen. Let’s clean up this debug view… And I’ll show you something in our running app, here.
We’re going to replace this plain colored background with a custom grid.
SwiftUI has a nice ImagePaint structure to repeat an image so all we need to do is draw two straight lines into an image Renderer.
When we repeat these lines over the screen, they appear to be a grid, like this.
But the lines will be nice and straight!
GridView.swift
So, create a new SwiftUI View file called GridView.swift
First create a new method with an image renderer in it. Here we create an image renderer and we return the renderer’s image from the method. The image comes from any drawing we do inside this context.
func gridImage(size: CGSize) -> UIImage {
let width = size.width
let height = size.height
return UIGraphicsImageRenderer(size: size).image { context in
}
}
The size parameter is the size that each grid square should be, so we can make the grid larger and smaller. Inside the renderer’s closure, set the drawing color:
UIColor.systemTeal.setStroke()
I want to draw a Path for the lines, and UIKit has an equivalent class to SwiftUI’s Path called UIBezierPath. The API commands are very similar.
Create a UIBezierPath and draw the path. Just as we did creating SwiftUI paths, we move to the first point, and then add points to lines, and then stroke the path to draw it into the context.
let path = UIBezierPath()
path.move(to: CGPoint(x: width, y: 0))
path.addLine(to: CGPoint(x: width, y: height))
path.addLine(to: CGPoint(x: 0, y: height))
path.stroke()
We’ve created a simple image; now we can draw it into a SwiftUI Rectangle! In body, replace Text with a Rectangle to fill up the whole view.
Rectangle()
.ignoresSafeArea()
If you reload the preview, you’ll see we’ve got a black rectangle over the whole screen. At the top of body, create a SwiftUI Image from the gridImage() method.
let size = CGSize(width: 30, height: 30)
let image = Image(uiImage: gridImage(size: size))
...
Use this image as a fill for the Rectangle.
Rectangle()
.fill(ImagePaint(image: image))
ImagePaint repeats the image all over the background so we now have a grid just from our two straight lines and we can control the size of the grid by setting a new size for the image.
To give us more control over how this looks in dark mode as well, let’s wrap this in a ZStack. Add a tertiary system background color at the top so it will show behding the grid. And move the safe area modifier down so it applies to the whole stack.
ZStack {
Color(uiColor: .tertiarySystemBackground)
Rectangle()
.fill(ImagePaint(image: image))
}
.ignoresSafeArea()
BackgroundView.swift
Open up BackgroundView.swift, and change the color in the ZStack to be the GridView
GridView()
That looks a lot more professional!