← Blog

Exploring Apple's Metal Framework: A Beginner's Guide

Get started with Apple's Metal framework in a Swift playground. Create the device with MTLCreateSystemDefaultDevice, set up an MTKView and render your first frame.

Apple’s Metal framework has been a game-changer in the world of 3D graphics on iOS and MacOS platforms. Since its introduction in 2014 and the subsequent deprecation of OpenGL, Metal has become the go-to framework for optimized graphics on Apple devices, especially with the advent of Apple Silicon and VisionOS. In this tutorial, we’ll dive into the basics of Metal using Swift in Xcode.

Getting Started with Xcode and Metal

Xcode is Apple’s integrated development environment (IDE) for creating applications on its various platforms. We’ll begin by setting up a playground in Xcode to experiment with Metal.

Creating a New Playground

  1. Open Xcode
  2. Go to File menu
  3. Select ‘New’ > ‘Playground’

We’ll be using Swift, Apple’s powerful and intuitive programming language. Don’t worry if you’re new to Swift; I’ll guide you through the tricky parts.

Importing Necessary Modules

To start, we need to import the necessary modules - PlaygroundSupport and MetalKit.

import PlaygroundSupport
import MetalKit

Accessing the GPU with Metal

Creating a Metal Device

guard let device = MTLCreateSystemDefaultDevice() else {
    fatalError("GPU not available")
}

This code attempts to create a default Metal device. If unsuccessful, it triggers a fatal error, stopping execution.

Setting Up the Metal Kit View

let frame = CGRect(x: 0, y: 0, width: 480, height: 270)
let view = MTKView(frame: frame, device: device)

view.clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1)

PlaygroundPage.current.liveView = view

Even though we set the clear color, it’s not visible on the view. That’s because we haven’t set any rendering commands yet.

Rendering with Metal

Command Queue and Buffers

We need a command queue to send work to the GPU. Add the code below after setting the clear color, ensuring the line PlaygroundPage.current.liveView = view always stays at the end of the source file.

guard let commandQueue = device.makeCommandQueue() else {
    fatalError("Could not create a command queue")
}

Configuring the Render

To render, we require a command buffer and a drawable object:

guard let commandBuffer = commandQueue.makeCommandBuffer(),
      let drawable = view.currentDrawable else {
    fatalError("Failed to create command buffer or drawable")
}

Finalizing the Render

Render Command Encoder is what encodes a drawing command into the command buffer. We don’t need to add any commands to the encoder, because we have no geometry to render. The encoder will clear the view with the selected clear color by default. That’s why we call endEncoding() immediately after creating the object.

if let renderPassDescriptor = view.currentRenderPassDescriptor,
   let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) {
    renderEncoder.endEncoding()
}

Then it’s just a matter of commiting our buffer:

commandBuffer.present(drawable)
commandBuffer.commit()

Running this will now show the correct clear color.

Full Code

This tutorial introduced the basics of using Metal in Swift. We set up a simple Metal environment and understood how rendering works in Metal. Here’s the full code you can copy and paste to your Xcode Playground:

import PlaygroundSupport
import MetalKit

guard let device = MTLCreateSystemDefaultDevice() else {
    fatalError("No GPU?")
}

// setting up the MetalKit View
let frame = CGRect(x: 0, y: 0, width: 450, height: 450)
let view = MTKView(frame: frame, device: device)

view.clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1)

// queue, buffer, and render pass
guard let commandQueue = device.makeCommandQueue(),
      let commandBuffer = commandQueue.makeCommandBuffer() else {
    fatalError("Could not create a command buffer or queue")
}

guard let renderPassDescriptor = view.currentRenderPassDescriptor else {
    fatalError("No default render pass descriptor")
}

// the rendering commands will go to the encoder
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
    fatalError("Could not create a render encoder")
}

renderEncoder.endEncoding() // endEncoding() marks the end of the rendering command for the render pass

// selecting the destination to draw to
guard let drawable = view.currentDrawable else {
    fatalError("Cannot select drawing destination")
}

commandBuffer.present(drawable)
commandBuffer.commit() // this sends the commands to the GPU and draws everything

PlaygroundPage.current.liveView = view

But wait, there’s more!

Check out my video tutorial, where I show how to set up this basic playground step-by-step:

Basic Apple Metal Play video: Basic Apple Metal

If you want even more, here’s the full playlist with all my FREE tutorials related to Apple Metal:

All My Metal Tutorials 🍿

GitHub Source Code

And don’t forget to subscribe to my YouTube channel for more!

Written by Luke. I build custom software for businesses, made for the way your team works. Fixed price, delivered in days or weeks. I've been shipping software since 2008, co-founded Codahead, a software house in Kraków, and I've published C++ tutorials on YouTube for six years.

Recent posts

All
Your First Triangle in Apple Metal: Shaders and Vertex Buffers Oct 2026Draw a triangle with Apple Metal in a Swift playground: write the vertex and fragment shaders, build the render pipeline, send vertices with setVertexBytes or an MTLBuffer, and get the memory alignment right. Your Business Deserves a Native App Oct 2026Many of your team's apps are web pages in disguise, and you pay for it in waiting. When Shopify rebuilt its shopping app as native, it started opening twice as fast on Android. Here's what native could do for your business. XFCE on 4K Displays: Correct Scaling in Five Settings Oct 2026XFCE's Window Scaling option only scales GTK apps. Here are the five settings that make a 4K display look right in GTK, Qt, Firefox and Emacs, plus fractional scaling.