diegoRodicio

Está documentación está a túa disposición sin ningún custo económico. Sen embargo, para a súa elaboración dedico moito tempo e recursos, polo que agradecería unha colaboración co que consideres oportuno. Gracias.

Property Wrappers

SwiftUl segue un enfoque baseado en datos para o desenvolvemento de aplicacións mediante o cal as vistas na interface de usuario actualízanse en resposta aos cambios nos datos sen a necesidade de escribir código de manexo.

En SwiftUI, os property wrappers son unha forma de encapsular a lóxica de negocio e as características comúns ao redor dunha propiedade dunha vista. Estes envolven unha propiedade e proporcionan unha interface para modificala de maneira segura e controlada.

SwiftUI ten varios property wrappers incorporados como @State, @Binding, @ObservedObject, @StateObject e @EnvironmentObject, que se utilizan para manexar diferentes tipos de estado nunha aplicación.

  • @State é utilizado para manexar o estado interno dunha vista.
  • @Binding é utilizado para pasar un estado dunha vista a outra.
  • @ObservedObject e @StateObject son utilizados para manexar o estado compartido entre varias vistas.
  • @EnvironmentObject é utilizado para acceder ao estado compartido na contorna da aplicación.

Os property wrappers proporcionan o estado que impulsa a forma en que aparece e compórtase a interface de usuario.

En SwiftUl, as vistas que compoñen un deseño da interface de usuario nunca se actualizan directamente dentro do código. No seu lugar, as vistas actualízanse automaticamente en función dos obxectos de estado aos que se vincularon a medida que cambian co tempo.

@State #

As propiedades de estado ou @State utilízanse exclusivamente para almacenar o estado que é local nun deseño de vista.

As propiedades de estado @State utilízanse para almacenar tipos de datos simples como unha cadea ou un valor Int e decláranse utilizando o envoltorio de propiedades @State, por exemplo:

struct ContentView: View {
    @State private var contador = 0
    
    var body: some View {
        ...
    }
}

As propiedades de estado ou @State son locais á vista, polo que deben declararse como propiedades privadas, tal e como se ve no exemplo anterior

Funcionamento: #

  • Cada cambio nun valor de propiedade de estado fai que as vista dentro da cal se declara a propiedade @State se volvan a debuxar (renderizar).
  • Isto, á súa vez, ten o efecto de garantir que calquera vista que se base na propiedade dalgunha maneira actualícese para reflectir o último valor.
  • Unha vez declarada a propiedade @State, pódense establecer ligazóns entre as propiedades @State e as vistas contidas no deseño.
  • Os cambios dentro das vistas que fan referencia á ligazón reflíctense automaticamente na propiedade @State correspondente.

Poderíase establecer unha ligazón, por exemplo, entre unha vista de Texto e a propiedade @State contador.

Cada vez que cambie o valor da variable @State contador, SwiftUI actualizará automaticamente a propiedade de Texto para que teña o valor da variable @State contador.

No seguinte exemplo:

  • Declaramos unha variable @State private var contador = 0
  • Debuxamos unha vista Text que ligamos á propiedade anterior. Cando cambie o valor da propiedade, volverase a debuxar o Texto co seu novo valor
  • Debuxamos unha vista de botón que incrementa o valor da propiedade de estado en un. Ao pulsar o botón, como varia o valor da propiedade de estado, automáticamente se debuxa a vista de texto ligada a dita propiedade
struct ContentView: View {
    @State private var contador = 0
    
    var body: some View {
        VStack {
            Text("Contador: \(contador)")
            Button ("Contador +1"){
                contador += 1
            }
        }
    }
}

$ #

Unha ligazón a unha propiedade de estado @State impleméntase prefixando o nome da propiedade cun signo «$«.

No seguinte exemplo:

  • Unha vista TextField establece unha ligazón á propiedade @State userName para usar como almacenamento para o texto introducido polo usuario.
  • Unha vista Text está ligada á mesma propiedade. Este texto actualizase cada vez que introducimos un texto no TextField, xa que están ligados á mesma propiedade
struct ContentView: View {
    @State private var userName = ""
    
    var body: some View {            
      VStack {
        TextField("Introduce o teu nome", text: $userName)
        Text(userName)
       }
    }
}

Outro exemplo #

No seguinte exemplo:

  • Unha vista Toogle establece unha ligazón á propiedade booleana @State wifiOn para usar como almacenamento para activar ou desactivar unha wifi.
  • Cando a propiedade @State wifiOn == true: Visualizamos unha Image dun wifi activado
  • Cando a propiedade @State wifiOn == false: Visualizamos unha Image dun wifi desactivado
struct Exemplo03: View {
    @State private var wifiOn = false

    var body: some View {
        VStack {
            
            Toggle(isOn: $wifiOn) {
                Text("Pulsa para activar a WIFI:")
            }
            
            if(wifiOn) {
                Image(systemName: "wifi")
            }
            else {
                Image(systemName: "wifi.slash")
            }
        }
    }
}

@Binding #

Unha propiedade de estado ou @State é local á vista na que se declara.

Poden ocorrer situacións nas que unha vista quere acceder a algunha propiedade @State definidas noutras vistas diferentes:

Como vedes na imaxen anterior, ImaxeWifiView (liña 33) necesita acceso á propiedade de estado wifiOn (liña 19).

A propiedade wifiOn é unha variable indefinida dentro de ImaxeWifiView:

  • Xa que está definida como @State dentro de outra vista distinta (ExemploBinding01)
  • Non podemos acceder á propiedade dende outra vista.

Este problema pódese resolver declarando a propiedade como @Binding da seguinte maneira:

Ten en conta que, agora:

  • Cando chamamos a ImaxeWifiView , hai que pasar unha ligazón á propiedade de estado $wifiOn (liña7)
  • Dentro de ImaxeWifiView definimos a propiedade @Binding wifiOn: Bool (liña 34). OLLO! 👀 :
    • Non se pode inicializar
    • Non pode ser privada

Clases e estruturas: @ObservedObject e @StateObject #

  • As propiedades de estado @State proporcionan unha forma de almacenar localmente o estado dunha vista, están dispoñibles só para a vista local e, como tales, non poden ser accedidos por outras vistas a menos que sexan subvistas e impleméntese a ligazón de estado @Binding.
  • As propiedades do estado @State e @Binding tamén son transitorias, xa que cando a vista principal desaparece, o estado tamén se perde.

Os obxectos observables, doutra banda, utilízanse para representar datos persistentes que son á vez externos e accesibles a múltiples vistas.

Un obxecto observable toma a forma dunha clase ou estrutura que se axusta ao protocolo ObservableObiect. Por exemplo:

class Demostracion:ObservableObject {
...
}

struct OutraDemostracion:ObservableObject {
...
}

  • O obxecto observable publica os valores de datos dos que é responsable como propiedades @Published.
  • Os obxectos observables forman parte do framework Combine (polo que se queremos empregalo temos que importalo previamente), que se introduciu por primeira vez con iOS 13 para facilitar o establecemento de relacións entre editores e subscritores.

A seguinte declaración de estrutura amosa unha simple declaración de obxecto observable con unha propiedade publicada @Published:

import Foundation
import Combine

class Demostracion: ObservableObject{
    @Published var nomeUsuario = ""
    
    init() {
        //Código pra inicializar
    }
}

@ObservedObject@StateObject #

Para empregar unha clase observable nunha vista, utilizamos o envoltorio de propiedades @ObservedObject ou @StateObject.

Agora, esa vista e calquera das súas vistas secundarias acceden ás propiedades publicadas @Published utilizando as mesmas técnicas utilizadas coas propiedades de estado vistas anteriormente nestes apuntes:

No anterior exemplo temos dous arquivos:

  • Demostracion.swift: Aquí definimos a clase Demostracion: ObservableObject (liña 11) coa súa propiedade @Published var nomeUsuario (liña 12)
  • ContentView: É onde definimos a vista da app.
    • Aquí empregamos a clase definida no arquivo anterior: @ObservedObject demostracion... (liña12)
    • Accedemos a propiedade @Published var nomeUsuario obxecto mediante demo.nomeUsuario (liña 15)

@StateObject vs @ObservedObject #

Un resumo simple:

  • @StateObject equivale a @State pero empregado para obxectos.
  • @ObservedObject equivale a @Binding pero empregado para obxectos.

A principal diferenza entre @StateObject e @ObservedObject é como se inician e actualizan eses dato que almacenan, así:

  • @StateObject é unha propiedade que se inicializa unha soa vez e o seu valor mantense ao longo da vida da vista. É adecuado para o uso con obxectos que deben ser creados e destruídos coa vista, como os obxectos de navegación ou de animación.
  • @ObservedObject é unha propiedade que se inicializa cun obxecto externo e o seu valor actualízase cando o obxecto cambia. É adecuado para o uso con obxectos compartidos que deben ser actualizados por varias vistas, como os obxectos de datos ou de modelos.

Así, @StateObject é adecuado para obxectos que deben ser creados e destruídos coa vista, mentres que @ObservedObject é adecuado para obxectos compartidos que deben ser actualizados por varias vistas.

@EnvironmentObject #

Cando unha vista navega a outra vista que necesita acceso ao mesmo obxecto @ObservedObject/@StateObject, a vista de orixe terá que pasar unha referencia ao obxecto observado á vista de destino durante a navegación. Vexamos o seguinte código:

@StateObject var demo: Demostracion = Demostracion()
...
...
NavigationLink(destination: OutraView(demo)) {
  Text("Seguinte Pantalla")
}

Na declaración anterior, utilízase unha ligazón de navegación para navegar a outra vista chamada OutraView, pasando por unha referencia ao obxecto observado @StateObject demo.

Aínda que esta técnica é aceptable para moitas situacións, pode volverse complexa cando moitas vistas dentro dunha aplicación necesitan acceso ao mesmo obxecto observado. Nesta situación, pode ter máis sentido usar un obxecto de contorna ou @EnvironmentObject.

Un obxecto de contorna @EnvironmentObject declárase da mesma maneira que un obxecto observable @ObservedObject/@StateObject (no sentido de que debe axustarse ao protocolo ObservableObject e débense publicar as propiedades apropiadas). A diferencia é que o obxecto @EnvironmentObject se almacena na contorna da vista na que se declara e, como tal, pódese acceder a todas as vistas secundarias sen necesidade de pasar dunha vista a outra.

Vexamos o seguinte exemplo de declaración de obxecto observable:

class VelocidadeObxecto: ObservableObject {
  @Published var velocidade = 0.0
}

As vistas que necesitan subscribirse a un obxecto de contorna simplemente fan referencia ao obxecto utilizando @EnvironmentObject en lugar de @StateObject ou @ObservedObject. Por exemplo:

struct VelocidadeControlView: View {
    @EnvironmentObject var velocidadeAxuste: VelocidadeAxuste
    
    var body: some View {
        Slider(value: $velocidadeAxuste.velocidade, in: 0...100)
    }
}

struct VelocidadeDisplayView: View {
    @EnvironmentObject var velocidadeAxuste: VelocidadeAxuste
    
    var body: some View {
        Text("Velocidade: \(velocidadeAxuste.velocidade)")
    }
}

Neste punto temos un obxecto observable @ObservableObject chamado VelocidadeAxuste e dúas vistas que fan referencia a un obxecto de dese tipo, pero aínda non inicializamos unha instancia do obxecto observable. O lugar lóxico para realizar esta tarefa está dentro da vista principal das subvistas anteriores:

struct ContentView: View {
    let velocidadeAxuste = VelocidadeAxuste()
    
    var body: some View {
        VStack {
            VelocidadeControlView()
            VelocidadeDisplayView()
        }
    }
}

Si facemos o exemplo tal cual, o visualizador de Xcode dará un erro. O problema é que, aínda que creamos unha instancia do obxecto observable dentro da declaración de vista de contido, aínda non a inserimos na xerarquía da vista. Isto lógrase utilizando o modificador .environmentObject() tal e como vemos na liña 9:

struct ContentView: View {
    let velocidadeAxuste = VelocidadeAxuste()
    
    var body: some View {
        VStack {
            VelocidadeControlView()
            VelocidadeDisplayView()
        }
        .environmentObject(velocidadeAxuste)
    }
}

A continuación, a imaxe do exemplo anterior en Xcode:

Share Your Valuable Opinions