Aprende a utilizar el nuevo framework Charts para poder crear Charts en SwiftUI
Aprende a utilizar el nuevo framework Charts para poder crear Charts en SwiftUI

SwiftUI Charts | Crea Charts potentes para tus apps

SwiftUI 4 nos proporciona una nueva vista llamada Chart para crear gráficas muy potentes y así representar datos. En el post de hoy vemos todos los tipos de Charts disponible y sus modificadores. Vamos a explorar y crear múltiples Charts en Xcode

SwiftBeta

Tabla de contenido


👇 SÍGUEME PARA APRENDER SWIFTUI, SWIFT, XCODE, etc 👇
Aprende a crear charts en SwiftUI
Aprende a crear charts en SwiftUI

Hoy en SwiftBeta vamos crear nuestro primer Chart en SwiftUI. Apple introdujo este framework en la WWDC22, y hace cosas potentísimas que veremos en el video de hoy. Vamos a crear varios Charts:

  • El primero será a modo de introducción para entender cómo funcionan, y lo que haremos será mostrar las vistas de mi canal de Youtube y Blog por día (estos datos son ficticios, no accederemos a ninguna API)
  • El segundo Chart que haremos será muy chulo, accederemos a los datos de nuestro acelerómetro en tiempo real.
La segunda parte, es muy importante que entiendas que estos datos del acelerómetro solo funcionan en dispositivos reales, esto significa que deberás compilar directamente tu app en tu iPhone (o iPad) para poder ver las variaciones en los 3 ejes del acelerómetro de tu dispositivo.

¡Vamos a empezar! Pero antes de todo, si quieres apoyar el canal y que siga creando este tipo de contenido, suscríbete al canal!

Creamos el proyecto en Xcode

Lo primero de todo que vamos hacer es crear el proyecto en Xcode. Muy importante que cuando lo crees, selecciones SwiftUI como interfaz. Una vez creado el proyecto nos vamos a la vista ContentView.

Como hemos dicho, en nuestro primer Chart vamos a mostrar datos fictios de las vistas que he tenido durante la última semana, para ello generaremos un Array con una struct llamada Info. Esta struct Info tendrá dos propiedades, una será para el día de la semana, y la otra propiedad nos indicará las vistas recibidas en ese día, muy sencillo:

struct Info {
    let date: Date
    let views: Int
}
Struct Info que usaremos para representar los datos de nuetro Chart en SwiftUI

Lo siguiente que vamos hacer es generar los datos de la última semana, pero antes vamos a generar esta pequeña extensión de Date:

extension Date {
    func last(day: Int) -> Date {
        return Calendar.current.date(byAdding: .day, value: day, to: .now)!
    }
}
Extensión de Date para calcular fácilmente días anteriores a una fecha

Esta extensión nos permitirá crear una fecha, si le pasamos -1 creará la fecha de ayer, si le pasamos -7 creará la fecha de hace una semana.

Ahora sí, vamos a crear el Array de datos, lo vamos a llamar youtubeVideoModel:

let youtubeVideoModel: [Info] = [
    .init(date: Date().last(day: -1), views: 400),
    .init(date: .now.last(day: -2), views: 900),
    .init(date: .now.last(day: -3), views: 690),
    .init(date: .now.last(day: -4), views: 550),
    .init(date: .now.last(day: -5), views: 800),
    .init(date: .now.last(day: -6), views: 820),
    .init(date: .now.last(day: -7), views: 600)
]
Datos que representaremos en un Chart más adelante

Una vez tenemos los datos que queremos representar en un Chart, vamos a crear la vista en SwiftUI

struct ContentView: View {
    var body: some View {
        Chart(youtubeVideoModel) { data in
            // TODO:
        }
    }
}
Usamos por primera vez la vista Chart en SwiftUI

Para representar los datos dentro de un Chart, tenemos que conformar el protocolo Identifiable en nuestra struct Info:

struct Info: Identifiable {
    let id: String = UUID().uuidString
    let date: Date
    let views: Int
}
Conformamos el protocolo Identifiable en Chart

Una vez desaparece el error, vamos a añadir otra. vista dentro de Chart que va a representar visualmente nuestros datos, podemos utilizar varias, y veremos algunas de ellas, pero de momento vamos añadir BarMark

struct ContentView: View {
    var body: some View {
        Chart(youtubeVideoModel) { data in
            BarMark(x: .value("Date", data.date),
                     y: .value("Views", data.views))
        }
    }
}
Creamos nuestro primer BarMark Chart en SwiftUI

Al usar BarMark, esta vista espera dos parámetros. El eje X y el eje Y, vamos a añadir:

  • En el eje X el valor de la fecha de nuestro modelo Info
  • En el eje Y vamos a añadir el número de vistas

Si compilamos vamos a ver qué ocurre. Al hacerlo vemos un Chart que ocupa toda la pantalla, y las vistas para representar los datos son Bars.

Primer Chart que creamos con SwiftUI
Primer Chart que creamos con SwiftUI

Vamos hacer que el gráfico ocupe menos, para ello vamos a utilizar el modificador frame.

struct ContentView: View {
    var body: some View {
        Chart(youtubeVideoModel) { data in
            BarMark(x: .value("Date", data.date),
                     y: .value("Views", data.views))
        }
        .frame(height: 200)
    }
}
Usamos el modificador frame para darle un height a nuestro Chart en SwiftUI
Reducimos el Height de nuestro Chart
Reducimos el Height de nuestro Chart

¡Mucho mejor! vamos a continuar, ahora vamos a mejorar un poco el aspecto visual de nuestro Chart, para ello en el eje X vamos a pasarle un parámetro llamado unit, este parámetro espera un case de DateComponents.

struct ContentView: View {
    var body: some View {
        Chart(youtubeVideoModel) { data in
            BarMark(x: .value("Date", data.date, unit: .day),
                     y: .value("Views", data.views))
        }
        .frame(height: 200)
    }
}

Añadimos un parámetro nuevo llamado unit para visualizar nuestro Chart con unidades de día
Podemos pulsar CMD+Click para ver todo los tipos que espera este parámetro.
Mejoramos el aspecto de nuestro Chart añadiendo day como unit
Mejoramos el aspecto de nuestro Chart añadiendo day como unit

PointMark, LineMark, AreaMark y RectangleMark

¿Qué más podemos hacer? Como te comentaba antes, esta es solo una manera de visualizar los datos dentro del Chart. Vamos a utilizar otras vistas para representar nuestros datos dentro del Chart:

  • PointMark
  • LineMark
  • AreaMark
  • RectangleMark

Según el gráfico que quieras representar, puedes usar cualquiera de las anteriores. Y estoy seguro que con el tiempo Apple nos proporcionará de nuevas.

Antes de continuar, vamos a modificar el color de las bar de nuestro Chart. Para hacerlo tan solo debemos usar un modificador que ya hemos visto muchas veces en el canal:

.foregroundColor(.red)
Modificador foregroundColor en SwiftUI para modificar el color de BarMark
Cambiamos el color de nuestro Chart
Cambiamos el color de nuestro Chart

RuleMark

Vamos a continuar, y ahora vamos a trazar una línea en nuestro Chart que representa la media de views de nuestro canal de Youtube. Para hacerlo vamos a crear una propiedad llamada averageOfViews

var averageOfViews: Double = {
    let totalView = youtubeVideoModel.reduce(0, { result, info in
        return result + info.views
    })
    return Double(totalView/7)
}()
Creamos esta variable para calcular la media de vistas de la última semana

Una vez la hemos creado, nos vamos a nuestro Chart. Y justo debaho de nuestro BarMark vamos a añadir un RuleMark:

RuleMark(y: .value("Average", averageOfViews))
Añadimos la vista RuleMark para mostrar el número medio de vistas del canal de Youtube
Añadimo una vista llamada RuleMark a nuestro Chart
Añadimo una vista llamada RuleMark a nuestro Chart

super sencillo, verdad? lo que vamos hacer ahora es cambiar el color, de esta manero identificaremos mejor la información dentro del Chart. Para hacerlo vamos a usar el modificador foregroundStyle:

RuleMark(y: .value("Average", averageOfViews))
                .foregroundStyle(.orange)
Apicamos un color a la RuleMark usando el modificador .foregroundStyle
Modificamos el color de nuestra vista RuleMark
Modificamos el color de nuestra vista RuleMark

Vamos a ver qué más podemos añadir, molaría poder mostrar el average total en la vista RuleMark. Para hacerlo vamos a usar otro modificador llamado annotation:

RuleMark(y: .value("Average", averageOfViews))
     .foregroundStyle(.orange)
     .annotation {
         Text("\(Int(averageOfViews))")
}
Podemos añadir anotaciones para mostrar valores en nuestros Charts de SwiftUI

Y el resultado es el siguiente:

Añadimos el valor exacto de la media de views de nuestro canal de Youtube
Añadimos el valor exacto de la media de views de nuestro canal de Youtube

El modificador annotation nos permite poder posicionar este valor donde queramos:

RuleMark(y: .value("Average", averageOfViews))
    .foregroundStyle(.orange)
    .annotation(position: .top, alignment: .leading) {
        Label("\(Int(averageOfViews))", systemImage: "eye.fill")
            .foregroundColor(.blue)
    }
Cambiamos el aspecto de nuestra anotación de nuestro Chart. Añadimos un Label con un texto y una imagen

Fíjate que también hemos modificado la vista, hemos sustituido nuestro Text por un Label. Está quedando una gráfica muy potente. Y si, también puedes añadir anotaciones a la BarMark, tan solo tendríamos que usar el modificador annotation y dentro de su trailing closure especificar una vista con los datos que queremos mostrar:

BarMark(x: .value("Date", data.date, unit: .day),
        y: .value("Views", data.views))
.annotation(position: .top, alignment: .center) {
    Text("\(data.views)")
        .font(.footnote)
}
Aprovechamos y añadimos una anotación a nuestro BarMark para mostrar el resultado exacto del número de visitas (de cada día)
Sustituimos el Text por un Label en la vista RuleMark
Sustituimos el Text por un Label en la vista RuleMark

chartXAxis

Ahora vamos a modificar el eje X, por cada barra que aparece, vamos a mostrar su día (ahora mismo aparecen 4 valores, vamos hacer que se muestren 7). Para hacerlo, vamos a usar un modificador llamado chartXAxis en nuestro Chart.

Nuestro Chart quedaría de la siguiente manera:

struct ContentView: View {
    var body: some View {
        Chart(youtubeVideoModel) { data in
            BarMark(x: .value("Date", data.date, unit: .day),
                    y: .value("Views", data.views))
            .annotation(position: .top, alignment: .center) {
                Text("\(data.views)")
                    .font(.footnote)
            }
            RuleMark(y: .value("Average", averageOfViews))
                .foregroundStyle(.orange)
                .annotation(position: .top, alignment: .leading) {
                    Label("\(Int(averageOfViews))", systemImage: "eye.fill")
                        .foregroundColor(.blue)
                }
        }
        .chartXAxis {
            AxisMarks(values: .stride(by: .day)) { day in
                AxisValueLabel()
            }
        }
        .foregroundColor(.red)
        .frame(height: 200)
    }
}
Modificamos el eje X usando el modificador chartXAxis
Mostramos todos los días en el eje X de nuestro Chart
Mostramos todos los días en el eje X de nuestro Chart

Si por alguna casualidad no quieres mostrar el mes en tus datos, puedes especificarlo en tu AxisValueLabel:

       .chartXAxis {
            AxisMarks(values: .stride(by: .day)) { day in
                AxisValueLabel(format: .dateTime.day(.defaultDigits))
            }
        }
Eliminamos el mes y mostramos solo el día en nuestro Chart (eje X)
Eliminamos el mes y mostramos solo los días en nuestro eje X del Chart
Eliminamos el mes y mostramos solo los días en nuestro eje X del Chart

Incluso podemos añadir componentes visuales que aparezcan en el eje X.

        .chartXAxis {
            AxisMarks(values: .stride(by: .day)) { day in
                AxisValueLabel(format: .dateTime.day(.defaultDigits))
                AxisTick()
                        }
        }
Añadimos pequeños detalles a nuestro Chart (guiones en la parte inferior)
Añadimos un separados entre los días
Añadimos un separados entre los días

O podemos hacer que aparezca un grid vertical en nuestro Chart, fíjate ya que el cambio es muy sutil:

        .chartXAxis {
            AxisMarks(values: .stride(by: .day)) { day in
                AxisValueLabel(format: .dateTime.day(.defaultDigits))
                AxisTick()
                AxisGridLine()
                        }
        }
Añadimos pequeños detalles a nuestro Chart (guiones en la parte superior)
Añadimos un separados entre las barras que muestras las vistas del canal
Añadimos un separados entre las barras que muestras las vistas del canal

Antes de continuar, voy a modificar el Label del RuleMark:

Label("\(Int(averageOfViews))", systemImage: "eye.fill")
    .foregroundColor(.orange)
    .font(.footnote)
    .bold()
Cambiamos el aspecto de nuestra anotación de RuleMark
Modificamos la anotación de nuestra vista RuleMark
Modificamos la anotación de nuestra vista RuleMark

Mostrar vistas del blog superponiendo LineMark con BarMark

Ahora vamos a superponer los datos de las vistas de mi blog (con datos ficticios, tal y como hemos hecho con los datos de Youtube), para representar estos datos, vamos a copiar el array que hemos creado al inicio del video y vamos a modificar los valores de las views:

let webVideoModel: [Info] = [
    .init(date: Date().last(day: -1), views: 300),
    .init(date: .now.last(day: -2), views: 800),
    .init(date: .now.last(day: -3), views: 490),
    .init(date: .now.last(day: -4), views: 350),
    .init(date: .now.last(day: -5), views: 800),
    .init(date: .now.last(day: -6), views: 120),
    .init(date: .now.last(day: -7), views: 600)
]

struct Metric: Identifiable {
    let id: String = UUID().uuidString
    var source: String
    var data: [Info]
}

var metrics: [Metric] = [.init(source: "youtube", data: youtubeVideoModel),
                         .init(source: "web", data: webVideoModel)]
Creamos un array nuevo con los datos de nuestra web, y creamos una struct nueva para representar los datos en un Chart en SwiftUI

Una vez hemos creado el nuevo Array, llamado webVideoModel, creamos un nuevo modelo de datos llamado Metric, con tan solo dos propiedades (no cuento la de if ya que es obligatoria al conformar el protocolo Identifiable). Y a continuación creamos la última variable que será un Array de tipo Metric. Fíjate que estamos preparando nuestros datos para que nuestro Chart sea capaz de diferenciar las distintas fuentes de datos (en nuestro caso solo 2).

Para simplificar el código vamos a borrar el RuleMark, el modificador annotation y chartXAsxis:

struct ContentView: View {
    var body: some View {
        Chart(youtubeVideoModel) { data in
            BarMark(x: .value("Date", data.date, unit: .day),
                    y: .value("Views", data.views))
        }
        .foregroundColor(.red)
        .frame(height: 200)
    }
}
Eliminamos algunas vistas y modificadores para centranos en el nuevo Chart

Ahora vamos hacer unos cambios para poder tener estos dos datos, de youtube y web en nuestro Chart:

struct ContentView: View {
    var body: some View {
        Chart {
            ForEach(metrics) { metric in
                ForEach(metric.data) { data in
                    BarMark(x: .value("Date", data.date, unit: .day),
                            y: .value("Views", data.views))
                }
            }
        }
        .foregroundColor(.red)
        .frame(height: 200)
    }
}
Añadimos las dos fuentes de datos al nuevo Chart pero no se distinguen correctamente (tienen el mismo color)

Con este código si miramos el canvas vemos el siguiente resultado:

Simplificamos nuestro Chart para superponer otro Chart encima
Simplificamos nuestro Chart para superponer otro Chart encima

Al estar aplicando el mismo color, no podemos identificar los datos de las dos fuentes, vamos a usar un modificador llamado foregroundStyle y vamos a diferenciar por tipo de fuente de datos (por la propieda source nuestro Array de métricas):

struct ContentView: View {
    var body: some View {
        Chart {
            ForEach(metrics) { metric in
                ForEach(metric.data) { data in
                    BarMark(x: .value("Date", data.date, unit: .day),
                            y: .value("Views", data.views))
                }
                .foregroundStyle(by: .value("Axis", metric.source))

            }
        }
        .foregroundColor(.red)
        .frame(height: 200)
    }
}
Aplicamos colores diferentes a cada fuente de datos, para ello usamos el modificador .foregroundStyle

Podemos ver claramente ahora a qué color pertence cada fuente de datos:

Dos Chart superpuestos, youtube representa el color azul y web representa el color verde
Dos Chart superpuestos, youtube representa el color azul y web representa el color verde

Pero estaría bien poder tener dos representaciones visuales para cada fuente de datos. Vamos a diferenciar la fuente de datos, y si es youtube vamos a dejarlo con BarMark y si es de web vamos a utilizar LineMark.

struct ContentView: View {
    var body: some View {
        Chart {
            ForEach(metrics) { metric in
                ForEach(metric.data) { data in
                    if metric.source == "youtube" {
                        BarMark(x: .value("Date", data.date, unit: .day),
                                y: .value("Views", data.views))
                    } else {
                        LineMark(x: .value("Date", data.date, unit: .day),
                                 y: .value("Views", data.views))
                    }
                }
                .foregroundStyle(by: .value("Axis", metric.source))

            }
        }
        .foregroundColor(.red)
        .frame(height: 200)
    }
}
Modificamos la gráfica para mostrar los datos de Youtube con BarMark y los datos de web usamos LineMark

Y obtenemos el siguiente resultado en nuestro Chart:

Dos Charts superpuestos, uno usa BarMark y el otro LineMark
Dos Charts superpuestos, uno usa BarMark y el otro LineMark

Símbolos en nuestro Chart

Por último, me gustaría comentar un último modificador. Podemos añadir símbolos a nuestros Charts para que sea más visual, sea más fácil leer la información que aparece en él.

Vamos a borrar la condición if ya que vamos a representar las dos fuentes de datos de la misma manera. Ahora vamos a añadir el modificador .symbol y el resultado final quedaría de la siguiente manera:

struct ContentView: View {
    var body: some View {
        Chart {
            ForEach(metrics) { metric in
                ForEach(metric.data) { data in
                    LineMark(x: .value("Date", data.date, unit: .day),
                             y: .value("Views", data.views))
                }
                .foregroundStyle(by: .value("Axis", metric.source))
                .symbol(by: .value("Value", metric.source))

            }
        }
        .foregroundColor(.red)
        .frame(height: 200)
    }
}
Mostramos todos los datos con LineMark y usamos un nuevo modificador llamado .symbol

Fíjate que por cada fuente de datos se ha printado un color, y. también se ha asignado un símbolo diferente.

Dos Charts superpuestos, los dos son LineMark y usan Símbolos en sus valores
Dos Charts superpuestos, los dos son LineMark y usan Símbolos en sus valores

Conclusión

Hoy hemos aprendido a usar Charts en SwiftUI 4, esta nueva vista nos permite crear Charts de forma nativa, sin necesidad de utilizar otros frameworks de otros developers, o creando nosotros algo custom. Tal y como has seguido a lo largo del video, es muy fácil crear representaciones visuales de nuestros datos, hay muchos modificadores y vistas que nos permiten crear auténticas maravillas 🤩