Saltar al contenido

Recetas SwiftUI del libro de cocina personal

Recetas SwiftUI del libro de cocina personal

Alexander, el desarrollador senior de iOS de tecnologiapc, comparte una de las recetas de cocina de SwiftUI.

A medida que comienza a investigar un tema amplio en programación, la cantidad de proyectos descargados y borradores creados gradualmente excede todos los límites imaginables e inconcebibles. Y luego todo se confunde y se pierde. Creo recordar que trabajaste en eso, pero ¿dónde, cuándo? Traté de trabajar en Playgrounds, pero en mi opinión no son tan estables como un proyecto normal, el resaltado cae, no hay forma de depurar normalmente. Recientemente, comencé un solo proyecto de investigación en SwiftUI, y arrojo todas las pequeñas cosas allí. Esto ayuda a mantener todo en un solo lugar, además, la búsqueda dentro del proyecto es mucho más conveniente. Si bien SwiftUI proporciona una vista previa para una vista rápida de la vista e incluso le permite depurarla, no siempre es suficiente. También me gustaría comprobar el dispositivo. Y si mantiene todas estas vistas dentro de un proyecto, cuando crea una nueva vista, debe establecerla como la principal en SceneDelegate, que rápidamente comienza a cansarse. Sería genial si pudiéramos ver todas nuestras vistas de prueba cuando lanzamos la aplicación y pudiéramos elegir con qué trabajar. Genial, dices? Para nada 🙂

Creo que el problema se puede resolver de más de una forma. De repente: atornillar Sourcery, pero fue interesante arreglarlo sin herramientas auxiliares.

Entonces, ¿qué es la visualización y su vista previa?

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, World!")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Como podemos ver, la vista previa de la Vista la proporciona el marco que implementa PreviewProvider; si alguien no lo sabía, incluso puede crear tantas estructuras / clases como desee dentro de un archivo que implementará PreviewProvider, y todas se mostrarán en el área de vista previa. Puede ser útil si queremos dividir nuestro ContentView_Previews en varios con diferentes configuraciones (aunque puede hacer lo mismo dentro de un marco que implementa PreviewProvider, pero no es así).

¿Qué es PreviewProvider? Este es el protocolo

/// Produces view previews in Xcode.
///
/// Xcode statically discovers types that conform to `PreviewProvider` and
/// generates previews in the canvas for each provider it discovers.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol PreviewProvider : _PreviewProvider {

    /// The type of the previews variable.
    associatedtype Previews : View

    /// Generates a collection of previews.
    ///
    /// Example:
    ///
    ///     struct MyPreviews : PreviewProvider {
    ///         static var previews: some View {
    ///             return Group {
    ///                 GreetingView("Hello"),
    ///                 GreetingView("Guten Tag"),
    ///
    ///                 ForEach(otherGreetings, id: .self) {
    ///                     GreetingView($0)
    ///                 }
    ///             }
    ///             .previewDevice("iPhone X")
    ///         }
    ///     }
    static var previews: Self.Previews { get }

    /// Returns which platform to run the provider on.
    ///
    /// When `nil`, Xcode infers the platform based on the file the
    /// `PreviewProvider` is defined in. This should only be provided when the
    /// file is in targets that support multiple platforms.
    static var platform: PreviewPlatform? { get }
}

Lo principal que se puede aprender del código no es un simple protocolo, sino PAT: Protocolo con Tipo Asociado, lo que inmediatamente complica el asunto. He probado muchas opciones para obtener la funcionalidad que quiero con un mínimo esfuerzo.

Comencemos con ¿cómo puede hacer estas cosas en tiempo real? En Objective-C, podríamos hacer cualquier cosa con la reflexión: obtener una lista de todas las clases, examinar sus propiedades. En Swift, todo esto está muy limitado y Mirror no nos dará todo lo que necesitamos. Por lo tanto, tuve que mirar hacia objc_getClassList: este es un método del tiempo de ejecución de Obj-C que le permite obtener una lista de todas las clases. Desafortunadamente, este no es el caso con las estructuras Swift, por lo que todo lo que quedaba era arreglárselas con lo que se dio.

Analicemos la solución en partes.

No funcionará normalmente con el protocolo del sistema PreviewProvider debido al hecho de que es PAT, por lo que crearemos una versión clara de este protocolo:

import SwiftUI

protocol PreviewHolder {
    static var anyPreviews: AnyView { get }
    static var name: String { get }
    static var starred: Bool { get }
}

extension PreviewHolder where Self: PreviewProvider {
    static var anyPreviews: AnyView {
        AnyView(previews)
    }

    static var name: String {
        String(describing: self).replacingOccurrences(of: "_Previews", with: "")
    }

    static var starred: Bool { false }
}

1. Como puede ver, he eliminado el tipo de Previews, creando un contenedor anyPreviews que devolverá AnyView. Realmente no me gustan estas cosas, la posible pérdida de rendimiento, pero como no se trata de código de producción, puedes cerrar los ojos.

2. nombre: una propiedad que devuelve el nombre de nuestra vista, tal como se mostrará en la lista; considerando que todas las vistas previas han generado automáticamente nombres ViewName_Previews, puede cortar _Previews.

3. Agregué la propiedad especial porque la cantidad de vistas continuará aumentando y cuando empiece a trabajar con un nuevo código, quiero verlo en la parte superior de la lista. Esto se puede hacer anulando esta propiedad en la vista previa de la nueva vista, devolviendo verdadero.

La lista en sí parece bastante simple.

import SwiftUI

struct PreviewsList: View {

    @State private var starred: [PreviewHolder.Type] = []

    @State private var general: [PreviewHolder.Type] = []

    var body: some View {
        NavigationView {
            List {
                Section(header: Text("Starred")) {
                    SubList(elements: starred)
                }
                Section(header: Text("General")) {
                    SubList(elements: general)
                }
            }.navigationBarTitle("Catalog")
                .onAppear {
                    let sorted = PreviewUtils.parse().sorted(by: { (lhs, rhs) -> Bool in
                        lhs.name < rhs.name
                    })
                    self.starred = sorted.filter { $0.starred }
                    self.general = sorted.filter { !$0.starred }
            }
        }
    }
}

private struct SubList: View {
    var elements: [PreviewHolder.Type]
    var body: some View {
        ForEach(0..

Todas las vistas están ordenadas por nombre y divididas en 2 listas, especial y regular. Todo se verá así:

tecnologiapc SwiftUI

Bueno, en SceneDelegate solo cambiamos la vista principal

let contentView = PreviewsList()

Hay un punto más: cómo hacer que nuestras vistas previas aparezcan en esta lista.

- Cambio de estructura en el aula,

- agregar soporte para PreviewHolder.

Aquellos. en vez de

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

se convertirá

class ContentView_Previews: PreviewProvider, PreviewHolder {
    static var previews: some View {
        ContentView()
    }
}

Opcionalmente, puede reemplazar el nombre y la estrella.

Esta solución se escribió en un par de horas para dar una prueba rápida de la idea. Si lo desea, puede rotarlo al máximo, etiquetar, fecha de creación para Vista previa, mostrar la lista no en la ventana principal, sino en la ventana Depurar, lo que le permitirá usarlo también en un proyecto de producción (don ' No olvide deshabilitarlo en la versión de compilación). En general, todo depende de tu imaginación 🙂

Puedes descargar el proyecto con la implementación básica aquí: Github

Si encuentra un error, seleccione un fragmento de texto y presione Ctrl + Entrar...