
¿Cómo crear aplicaciones en tu iPad con Swift Playgrounds 4?
Con Swift Playgrounds 4 ya podemos crear aplicaciones desde nuestro iPad. Crea aplicaciones desde cualquier lugar solo con tu iPad. Hoy aprendemos a cómo usar UIImagePickerController para crear una app que saque fotos y aplique filtros y todo esto lo vamos hacer desde nuestro iPad
Tabla de contenido

Hoy en SwiftBeta vamos a aprender a crear otra app en nuestro iPad. Lo único que necesitamos es tener descargado Swift Playgrounds 4.
En nuestra app mostraremos la cámara de nuestro dispositivo, haremos una foto y podremos aplicar un filtro. Lo primero de todo que vamos hacer es crear nuestra vista ContentView. Va a ser muy sencilla:
import SwiftUI
struct ContentView: View {
@State var selectedImage: Image = Image("placeholder")
@State var showCamera: Bool = false
var body: some View {
VStack {
selectedImage
.resizable()
.scaledToFit()
.frame(width: 300, height: 300)
Button("Show Camera") {
// TODO:
}
}
}
}
La vista de placeholder puedes usar la que quieras, te dejo la que he usado por si quieres tener el mismo resultado:

Conformamos el protocolo UIViewControllerRepresentable
Lo siguiente que vamos hacer es usar una clase para poder acceder a la cámara de nuestro dispositivo, esta clase llamada UIImagePickerController pertenece a UIKit. Y para poderla usar, en SwiftUI debemos crear como una especie de Wrapper (de envoltorio, como un traductor de UIKit a SwiftUI). Para hacerlo creamos una struct llamada CameraView y conformamos el protocolo UIViewControllerRepresentable, al conformar los métodos de este protocolo podremos aprovechar y usar UIImagePickerController en SwiftUI. Vamos a ello:
struct CameraView: UIViewControllerRepresentable {
@Binding var selectedImage: Image
@Environment(\.dismiss) var dismiss
func makeUIViewController(context: Context) -> some UIViewController {
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = context.coordinator
return imagePickerController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// Empty
}
}
- El método makeUIViewController se encarga de crear el controlador (y la vista) UIImagePickerController. Fíjate que asignamos a la propiedad delegate un context.coordinator. Ahora entraremos en detalle con esta parte.
- El método updateUIViewController se encarga de actualizar la vista, en nuestro caso dejaremos este método vacío ya que no haremos ningún cambio después de instanciarla en makeUIViewController
Si te fijas, tenemos un error en nuestro código.
Cannot assign value of type 'Void' to type '(UIImagePickerControllerDelegate & UINavigationControllerDelegate)?'
Creamos nuestro Coordinator
Vamos a arreglarlo creando una nueva clase llamada Coordinator, esta clase va a conformar estos dos protocolos que se muestran en el error anterior, UIImagePickerControllerDelegate y UINavigationControllerDelegate. A parte, la clase Coordinator va a ser otro traductor, va a escuchar cambios que pasen en los delegados de la clase de UIKit UIImagePickerController y cambiará el estado de nuestra vista en SwiftUI. Es decir, cuando un user haga una foto, el método del delegado imagePickerController(_ picker:, didFinishPickingMediaWithInfo info:) se llamará automáticamente y asignará la nueva imagen a CameraView, y este cambio, al ser selectedImage un Binding de ContentView hará que se muestre también en nuestra Image de ContentView.
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var cameraView: CameraView
init(cameraView: CameraView) {
self.cameraView = cameraView
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
cameraView.selectedImage = Image(uiImage: image)
}
cameraView.dismiss()
}
}
La función del Coordinator es cambiar el estado de nuestra vista en SwiftUI de cambios que llegan de métodos como en este caso imagePickerController(_ picker:, didFinishPickingMediaWithInfo info:)
Una vez hemos creado el Coordinator vamos a añadir otro método en nuestra vista CameraView, vamos a conectar CameraView con nuestro Coordinator. De esta manera la vista se podrá comunicar con los métodos que se triggerean en el delagado de imagePickerController como acabamos de explicar, por ejemplo cuando un user ha realizado una foto:
struct CameraView: UIViewControllerRepresentable {
@Binding var selectedImage: Image
@Environment(\.dismiss) var dismiss
func makeUIViewController(context: Context) -> some UIViewController {
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = context.coordinator
imagePickerController.sourceType = .camera
return imagePickerController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// Empty
}
func makeCoordinator() -> Coordinator {
Coordinator(cameraView: self)
}
}
Ahora nos vamos a nuestra vista ContentView y vamos a añadir lógica para que cuando se pulse el Button aparezca la vista CameraView que hemos creado. Y así el user pueda hacer una foto.
import SwiftUI
struct ContentView: View {
@State var selectedImage: Image = Image("placeholder")
@State var showCamera: Bool = false
var body: some View {
VStack {
selectedImage
.resizable()
.scaledToFit()
.frame(width: 300, height: 300)
Button("Show Camera") {
showCamera.toggle()
}
.fullScreenCover(isPresented: $showCamera) {
CameraView(selectedImage: $selectedImage)
}
}
}
}
Compilamos nuestra app en el iPad
Si compilamos ahora, y pulsamos el Button de "Show Camera" nos aparece el siguiente alert. Si le damos a Ok, vemos como nuestra app muestra la cámara y por lo tanto podemos tomar una foto. Al tomar una foto, la imagen que teníamos en ContentView (la del placeholder) es sustituida por la nueva que acabamos de tomar.
Un dato curioso, si intentaramos usar el mismo código en Xcode nos saldría un error, nos crashearía nuestra aplicación con el siguiente mensaje:
This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.
¿Por qué ocurre? debemos especificar en nuestro Info.plist la key Privacy - Camera Usage Description y darle un mensaje al usuario de por qué queremos tener acceso a la cámara.
Ahora vamos con la segunda parte, una vez tenemos una imagen hecha con nuestra cámara vamos a probar dos filtros.
Añadimos un filtro a nuestra imagen
Una vez tenemos se muestra la imagen que acabamos de crear, vamos a crear un button nuevo para poder aplicar un filtro. En este caso usaremos un filtro sepia.
Creamos una struct llamada Filter:
struct Filter {
@Binding var selectedImage: Image
@Binding var selectedUIImage: UIImage
func apply() {
guard let cgImage = selectedUIImage.cgImage else {
return
}
let ciImage = CIImage(cgImage: cgImage)
let sepiaFilter = CIFilter(name:"CISepiaTone")
sepiaFilter?.setValue(ciImage, forKey: kCIInputImageKey)
guard let imageWithSepiaFilter = sepiaFilter?.outputImage,
let cgImageWithSepiaFilter = mapCIImageToCGImage(imageWithSepiaFilter) else {
return
}
let newImage = UIImage(cgImage: cgImageWithSepiaFilter,
scale: 1.0,
orientation: .right)
selectedImage = Image(uiImage: newImage)
}
private func mapCIImageToCGImage(_ ciImage: CIImage) -> CGImage? {
let context = CIContext()
if let cgImage = context.createCGImage(ciImage,
from: ciImage.extent) {
return cgImage
}
return nil
}
}
Y en CameraView creamos una nueva propiedad llamada:
@Binding var selectedUIImage: UIImage
Cuando un user haga una foto guardaremos la UIImage en esta propiedad, es decir, nuestro Coordinator quedaría de la siguiente manera:
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var cameraView: CameraView
init(cameraView: CameraView) {
self.cameraView = cameraView
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
cameraView.selectedImage = Image(uiImage: image)
cameraView.selectedUIImage = image
}
cameraView.dismiss()
}
}
Volvemos a nuestra ContentView y aquí vamos a añadir dos propiedades nuevas y un Button nuevo que llame al método de la struct Filter que acabamos de crear. Al llamar al método apply() se aplicará el filtro que hayamos hecho.
import SwiftUI
import CoreImage
struct ContentView: View {
@State var selectedImage: Image = Image("placeholder")
@State var selectedUIImage: UIImage = UIImage(named: "placeholder")!
@State var showCamera: Bool = false
@State var filter: Filter?
var body: some View {
VStack {
selectedImage
.resizable()
.scaledToFit()
.frame(width: 300, height: 300)
Button("Show Camera") {
showCamera.toggle()
}
Button("Apply Filter") {
filter = Filter(selectedImage: $selectedImage,
selectedUIImage: $selectedUIImage)
filter?.apply()
}
.fullScreenCover(isPresented: $showCamera) {
CameraView(selectedImage: $selectedImage,
selectedUIImage: $selectedUIImage)
}
}
}
}
Vamos a probarlo 🚀
Conclusión
Hoy hemos aprendido a cómo crear una aplicación para poder acceder a la cámara de nuestro iPad y una vez tomada una foto aplicar un filtro.