Como iOS Developer, crear experiencias ricas y táctiles es una de las metas más gratificantes. Desde la introducción del Apple Pencil, los usuarios de iPad (y cada vez más, de iPhone a través de gestos táctiles) esperan poder interactuar con sus aplicaciones de forma fluida, tomando notas, dibujando o anotando documentos. Es aquí donde entra en juego una de las herramientas más potentes de la programación Swift: el framework PencilKit.
Si estás desarrollando en Xcode y has adoptado el paradigma declarativo, te habrás dado cuenta de que integrar frameworks basados en UIKit dentro de SwiftUI puede parecer un desafío inicial. Sin embargo, la interoperabilidad que ofrece Swift lo hace no solo posible, sino altamente eficiente.
En este tutorial detallado, exploraremos a fondo cómo implementar PencilKit en SwiftUI y iOS, creando un lienzo de dibujo funcional, gestionando la paleta de herramientas y guardando los trazos del usuario, todo optimizado para iOS y iPadOS.
1. ¿Qué es PencilKit y por qué deberías usarlo?
PencilKit es un framework nativo de Apple diseñado para facilitar a cualquier iOS Developer la incorporación de un entorno de dibujo de baja latencia en sus aplicaciones. Antes de PencilKit, crear una aplicación de dibujo requería complejos cálculos matemáticos con Core Graphics, OpenGL o Metal para interpretar los toques, calcular la presión del Apple Pencil y renderizar los trazos sin “lag”.
PencilKit resuelve todo esto ofreciendo:
- PKCanvasView: La vista principal donde ocurren los dibujos.
- PKToolPicker: La interfaz de usuario flotante (o anclada) que proporciona bolígrafos, marcadores, borradores y selectores de color.
- PKDrawing: El modelo de datos subyacente que almacena los vectores de los trazos, permitiendo guardar, cargar e incluso inspeccionar el dibujo.
2. El Desafío: Integrando PencilKit en SwiftUI
Actualmente, Apple no proporciona una vista nativa puramente escrita en SwiftUI para PencilKit. PKCanvasView es una subclase de UIScrollView (que a su vez hereda de UIView).
Por lo tanto, en la programación Swift moderna, necesitamos construir un “puente” utilizando el protocolo UIViewRepresentable. Este protocolo permite encapsular vistas de UIKit para que SwiftUI las trate como ciudadanos de primera clase dentro de su árbol de vistas.
3. Configurando el Proyecto en Xcode
- Abre Xcode y crea un nuevo proyecto seleccionando “App” bajo la pestaña de iOS.
- Nombra tu proyecto (por ejemplo, “PencilKitSwiftUITutorial”).
- Asegúrate de que la interfaz esté configurada en SwiftUI y el lenguaje en Swift.
- Es recomendable que en la configuración del proyecto (pestaña General), en la sección “Supported Destinations”, te asegures de tener marcado tanto iPhone como iPad, ya que PencilKit brilla especialmente en iPadOS.
4. Creando el Lienzo: Implementando UIViewRepresentable
Vamos a crear nuestro componente puente. Crea un nuevo archivo de Swift llamado PencilKitCanvas.swift. Aquí definiremos la estructura que envolverá nuestro PKCanvasView.
import SwiftUI
import PencilKit
struct PencilKitCanvas: UIViewRepresentable {
// Vinculamos el dibujo a un estado de SwiftUI para poder guardarlo/cargarlo
@Binding var canvasView: PKCanvasView
// makeUIView se llama una sola vez cuando SwiftUI crea la vista
func makeUIView(context: Context) -> PKCanvasView {
// Configuramos el lienzo para que acepte tanto Apple Pencil como el dedo
canvasView.drawingPolicy = .anyInput
// Asignamos el delegado para responder a cambios en el lienzo
canvasView.delegate = context.coordinator
// El fondo transparente permite componer el lienzo sobre otras vistas
canvasView.backgroundColor = .clear
canvasView.isOpaque = false
return canvasView
}
// updateUIView se llama cada vez que el estado de SwiftUI cambia
func updateUIView(_ uiView: PKCanvasView, context: Context) {
// Aquí podríamos actualizar propiedades del lienzo si el estado externo cambia
}
// El Coordinator actúa como puente para los delegados de UIKit (PKCanvasViewDelegate)
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, PKCanvasViewDelegate {
var parent: PencilKitCanvas
init(_ parent: PencilKitCanvas) {
self.parent = parent
}
// Método del delegado: se llama cada vez que el usuario dibuja algo
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
// Útil si necesitas notificar a SwiftUI que el dibujo cambió (ej. para auto-guardado)
}
}
}
Análisis del código para el iOS Developer:
@Binding var canvasView: Pasamos elPKCanvasViewdesde la vista padre en SwiftUI. Esto nos da el control para extraer elPKDrawing(el dibujo) y mostrar elPKToolPickerdesde la vista principal.drawingPolicy = .anyInput: Por defecto, en iPadOS, PencilKit asume que el usuario dibujará con el Apple Pencil y hará scroll con el dedo. Al cambiarlo a.anyInput, permitimos dibujar con el dedo en iOS (iPhone).- Coordinator: Es fundamental en la programación Swift cuando usamos
UIViewRepresentable. Nos permite conformar el protocoloPKCanvasViewDelegatepara reaccionar a eventos del usuario.
5. Añadiendo el PKToolPicker (Paleta de Herramientas)
El lienzo por sí solo solo dibuja con el pincel por defecto. Para ofrecer una experiencia completa, necesitamos mostrar la paleta de herramientas de Apple.
Crea un nuevo archivo llamado DrawingView.swift. Esta será la vista de SwiftUI que el usuario verá y que orquestará nuestro lienzo y las herramientas.
import SwiftUI
import PencilKit
struct DrawingView: View {
// Mantenemos la instancia del lienzo y el selector de herramientas como estado de la vista
@State private var canvasView = PKCanvasView()
@State private var toolPicker = PKToolPicker()
var body: some View {
ZStack {
// Fondo de la app
Color.white.ignoresSafeArea()
// Nuestro lienzo encapsulado
PencilKitCanvas(canvasView: $canvasView)
.ignoresSafeArea()
}
// Usamos onAppear para presentar el Tool Picker una vez que la vista está lista
.onAppear {
setupToolPicker()
}
}
private func setupToolPicker() {
// Vinculamos el Tool Picker a nuestro lienzo
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
// Hacemos que el lienzo se convierta en el "First Responder" para que el Tool Picker aparezca
canvasView.becomeFirstResponder()
}
}
¡Con solo esto, si ejecutas la app en tu simulador o dispositivo físico a través de Xcode, ya tienes una aplicación de dibujo completamente funcional! La paleta aparecerá flotando en el iPad y se ajustará perfectamente al teclado o al espacio inferior en el iPhone.
6. Guardando y Cargando el Dibujo (Persistencia)
Una aplicación que no guarda el trabajo del usuario no es muy útil. El poder de usar PencilKit en SwiftUI y iOS radica en la facilidad con la que podemos serializar el objeto PKDrawing.
Vamos a modificar nuestra DrawingView para incluir botones en una navigationBar que permitan limpiar el lienzo, guardar el dibujo en formato Data (ideal para UserDefaults, CoreData o SwiftData) y restaurarlo.
import SwiftUI
import PencilKit
struct DrawingAppView: View {
@State private var canvasView = PKCanvasView()
@State private var toolPicker = PKToolPicker()
// Variable para almacenar temporalmente los datos del dibujo en UserDefaults
@AppStorage("savedDrawing") private var savedDrawingData: Data = Data()
var body: some View {
NavigationView {
PencilKitCanvas(canvasView: $canvasView)
.ignoresSafeArea()
.onAppear {
setupToolPicker()
loadDrawing() // Cargar el dibujo al iniciar
}
.navigationTitle("PencilKit + SwiftUI")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: clearCanvas) {
Image(systemName: "trash")
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Guardar", action: saveDrawing)
}
}
}
}
private func setupToolPicker() {
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
canvasView.becomeFirstResponder()
}
private func saveDrawing() {
// PKCanvasView tiene una propiedad .drawing que podemos serializar a DataRepresentation
savedDrawingData = canvasView.drawing.dataRepresentation()
print("Dibujo guardado con éxito.")
}
private func loadDrawing() {
// Intentamos recrear el PKDrawing a partir de los datos almacenados
if !savedDrawingData.isEmpty {
do {
let drawing = try PKDrawing(data: savedDrawingData)
canvasView.drawing = drawing
} catch {
print("Error al cargar el dibujo: \(error.localizedDescription)")
}
}
}
private func clearCanvas() {
// Para limpiar, simplemente asignamos un nuevo dibujo vacío
canvasView.drawing = PKDrawing()
}
}
Aquí hemos utilizado el property wrapper @AppStorage, una excelente adición a SwiftUI, que guarda directamente los datos serializados en UserDefaults. Si bien esto es perfecto para un tutorial o prototipo ligero, como iOS Developer profesional, para aplicaciones en producción deberías considerar guardar estos archivos en el sistema de archivos (File Manager) si el dibujo es muy complejo o pesado.
7. Uso Avanzado: Analizando Trazos y Personalizando Herramientas
La programación Swift aplicada a PencilKit no se detiene en simplemente mostrar un lienzo. Desde iOS 14, Apple permitió a los desarrolladores inspeccionar los trazos individuales.
El objeto PKDrawing contiene un array de PKStroke. Cada trazo contiene información sobre la ruta (path), la tinta (ink), y la transformación.
Leyendo los trazos del usuario
Si deseas saber cuántos trazos ha dibujado el usuario o analizar su comportamiento (por ejemplo, para aplicaciones de reconocimiento de escritura a mano en Swift), puedes iterar sobre ellos:
func analyzeStrokes() {
let strokes = canvasView.drawing.strokes
print("El usuario ha realizado \(strokes.count) trazos.")
for stroke in strokes {
let path = stroke.path
let ink = stroke.ink
print("Trazo con color: \(ink.color) y tipo de herramienta: \(ink.inkType)")
}
}
Cambiando herramientas programáticamente
A veces no quieres usar el PKToolPicker. Imagina que estás creando un juego infantil en SwiftUI y quieres proveer tus propios botones de colores. Puedes modificar la herramienta del lienzo directamente:
// Cambiar a un bolígrafo rojo programáticamente
func useRedPen() {
canvasView.tool = PKInkingTool(.pen, color: .red, width: 5)
}
// Cambiar al borrador
func useEraser() {
canvasView.tool = PKEraserTool(.vector) // Borra por trazo completo
// canvasView.tool = PKEraserTool(.bitmap) // Borra por píxeles
}
8. Mejores Prácticas: iOS vs iPadOS
Al desarrollar aplicaciones con PencilKit en SwiftUI y iOS, un buen iOS Developer debe tener en cuenta el hardware del dispositivo que ejecuta la app, utilizando las capacidades condicionales que Xcode y Swift ofrecen.
- En iPadOS: El usuario espera que su Apple Pencil dibuje y que su dedo haga scroll en la página (piensa en la app Notas de Apple). Para lograr esto, configura
canvasView.drawingPolicy = .pencilOnly. Si usas unScrollViewenvolviendo el canvas, esto permitirá una navegación natural. - En iOS (iPhone): Como no hay Apple Pencil, debes usar
canvasView.drawingPolicy = .anyInput. Si no lo haces, el usuario tocará la pantalla y no pasará absolutamente nada, lo cual genera una pésima experiencia de usuario (UX).
Puedes detectar el dispositivo (idiom) en Swift de esta manera:
if UIDevice.current.userInterfaceIdiom == .pad {
canvasView.drawingPolicy = .pencilOnly // Opcional, dependiendo de tu diseño
} else {
canvasView.drawingPolicy = .anyInput
}
Además, asegúrate de gestionar el modo oscuro (Dark Mode). PencilKit es inteligente: si un usuario dibuja con tinta negra en modo claro, y el dispositivo cambia a modo oscuro, PencilKit automáticamente invierte ese negro a blanco para que siga siendo visible sobre el fondo oscuro. Si necesitas un color “absoluto” que no cambie (como una firma legal), deberás forzar el trait collection del canvas o usar colores personalizados definidos como fijos.
Conclusión
Integrar PencilKit en SwiftUI y iOS abre un mundo de posibilidades interactivas para cualquier iOS Developer. Aunque requiere dar un pequeño salto hacia UIKit utilizando UIViewRepresentable, la API de Xcode está tan pulida que el proceso resulta extremadamente limpio.
La programación Swift moderna nos permite combinar lo mejor de ambos mundos: la rapidez y el diseño declarativo de SwiftUI para construir nuestra interfaz de usuario (botones, navegación, menús), y la potencia gráfica de bajo nivel de UIKit para manejar la latencia casi nula del dibujo.
Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte . Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram










