Post

ZStack personalizado

Para dar un estilo único a las vistas del proyecto personal en el que estoy trabajando he creado un Stack personalizado. 

El cual incluye un degradado, opcionalmente una imagen y un título que hacen de background.

Comienza con la ActivitiesListView, que abstrae el ContentView y su toolbar mediante una extensión.

1. ActivitiesListView

struct ActivitiesListView: View {
    @StateObject private var vm = ViewModel()
    var body: some View {
        ZStackBackground(
            alignment: .topTrailing, image: "activity", title: "Activities") {
                ContentView()
                    .toolbarContent()
            }
            .errorAlert(isPresented: $vm.showError,error: vm.errorMessage)
    }
}

Agrupa el ContentView, de esta manera puedo añadir toolbar, alerts, tasks, acciones (onChange, onAppear) en un solo lugar.

2. Toolbar

extension ActivitiesListView.ContentView {
    func toolbarContent() -> some View {
        self
            .navigationTitle("Activities")
            .toolbar {
                ToolbarItem(placement: .primaryAction) {
                    EditButton()
                }
            }
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button { } label: {
                        HStack {
                            Image(systemName: "plus")
                                .fontStyle(.headline, color:.red)
                            Text("Add New")
                                .fontStyle(.headline, color:.white)
                        }
                    }
                }
            }
    }
}

Hay componentes que son únicos para esta vista, como lo es la toolbar, por lo que la añado como extensión, y que solo ActivitiesListView tenga acceso a ella. 

3. ZStackBackground

struct ZStackBackground<Content: View>: View {
    let color: CCColor
    let alignment: Alignment
    let image: String?
    let title: String?
    @ViewBuilder let content: Content
    
    init(
        color: CCColor = .background,
        alignment: Alignment = .center,
        image: String? = nil,
        title: String? = nil,
        @ViewBuilder content: () -> Content
    ) {
            self.color = color
            self.alignment = alignment
            self.content = content()
            self.image = image
            self.title = title
        }
    
    var body: some View {
        ZStack(alignment: alignment) {
            ZStack {
                LinearGradient
                    .ccBackground()
                    .ignoresSafeArea()
                buildIf(image != nil) {
                    VStack {
                        Image(image ?? "")
                            .circular()
                        Text(title ?? "")
                            .fontStyle(.title)
                    }
                    .opacity(0.1)
                }
            }
            content
        }
        .dynamicTypeSize(.xSmall ... .xxLarge)
    }
}

El ZStackBackground es una View que implementa un @ViewBuilder que es el contenido (en forma de closure). Con un inicializador con valores por defecto, útil para la mayoría de las vistas.

La parametrización me permite elegir entre ciertos colores, dar alineación al contenido y colocar una imagen de fondo. 

El parámetro color se sirve de un enum que define los colores que pueden ser utilizados

4. Linear Gradient, Image

extension LinearGradient {
    static func ccBackground(color: CCColor = .background) -> some View {
        LinearGradient(
            colors: [color.selection, color.selection.opacity(0.7)],
            startPoint: .top,
            endPoint: .bottom
        )
    }
}

extension Image {
    func circular() -> some View {
        self
        .resizable()
        .scaledToFit()
        .clipShape(Circle())
    }
}

El componente LinearGradient tiene una extensión con una función estática de define sus valores. De la misma manera Image que define parámetros y devuelve una View. Y el Text que se ayuda de un ViewModifier y una extensión de la View para agrupar fuente, color y bold.