Per què els modificadors de visualització condicional són una mala idea · objc.io

A la comunitat SwiftUI, molta gent presenta la seva pròpia versió de modificador de vista condicional
. Permet prendre una vista i aplicar un modificador de vista només quan es compleixi la condició. Normalment té el següent aspecte:

								
extension View {
    @ViewBuilder
    func applyIfM: View>(condition: Bool, transform: (Self) -> M) -> some View {
        if condition {
            transform(self)
        } else {
            self
        }
    }
}

							

Hi ha moltes publicacions de bloc amb modificadors similars. Crec que totes aquestes publicacions del bloc haurien d’incloure un senyal d’advertència enorme. Per què és problemàtic el codi anterior? Vegem un exemple.

Al següent codi tenim una propietat de l’estat myState
. En canviar entre true
i false
, volem aplicar condicionalment un marc:

								struct ContentView: View {
    @State var myState = false
    var body: some View {
        VStack {
            Toggle("Toggle", isOn: $myState.animation())
            Rectangle()
                .applyIf(condition: myState, transform: { $0.frame(width: 100) })
        }
        
    }
}

							

Curiosament, quan s’executa aquest codi, l’animació no es veu gens fluida. Si us fixeu bé, veureu que s’esvaeix entre els estats “abans” i “després”:

Aquí hi ha el mateix exemple, però escrit sense applyIf
:

								struct ContentView: View {
    @State var myState = false
    var body: some View {
        VStack {
            Toggle("Toggle", isOn: $myState.animation())
            Rectangle()
                .frame(width: myState ? 100 : nil)
        }
        
    }
}

							

I amb el codi anterior, la nostra animació funciona com s’esperava:

Per què és applyIf
versió trencada? La resposta ens ensenya molt sobre com funciona SwiftUI. A UIKit, les vistes són objectes i els objectes són inherents identitat
. Això vol dir que dos objectes són iguals si són el mateix objecte. UIKit es basa en la identitat d’un objecte per animar els canvis.

A SwiftUI, les vistes són estructures (tipus de valor), cosa que significa que no tenen identitat. Per tal que SwiftUI animi els canvis, ha de comparar el valor de la vista abans
ha començat l’animació i el valor de la vista després
s’acaba l’animació. A continuació, SwiftUI interpola entre els dos valors.

Per entendre la diferència de comportament entre els dos exemples, vegem-ne els tipus. Aquí teniu el nostre tipus Rectangle().applyIf(...)
:

								_ConditionalContent<ModifiedContent<Rectangle, _FrameLayout>, Rectangle>

							

El tipus més extern és un _ConditionalContent
. Aquesta és una llista que sí o bé
conté el valor de l’execució de if
clonar, o bé
el valor de la implementació de else
clonar. Quan les condicions canvien, SwiftUI no pot interpolar entre els valors antic i nou perquè tenen diferents tipus. A SwiftUI, quan ho tingueu if/else
amb un estat canviant, a transició
succeeix: s’elimina la vista d’una branca i s’insereix la vista de l’altra branca. Per defecte, la transició s’esvaeix i això és exactament el que veiem applyIf
exemple.

En canvi, aquest és el tipus Rectangle().frame(...)
:

								ModifiedContent<Rectangle, _FrameLayout>

							

Quan animem canvis a les propietats del marc, no hi ha cap branca que tingui en compte SwiftUI. Simplement pot interpolar entre el valor antic i el nou i tot funciona com s’esperava.

IN Rectangle().frame(...)
per exemple, vam condicionar el modificador de visualització proporcionant nil
valor d’amplada. Això és compatible amb gairebé tots els modificadors d’aspecte. Per exemple, podeu afegir un color condicional de primer pla mitjançant un color opcional, podeu afegir un farciment condicional mitjançant 0 o un valor, etc.

Tingues en compte que applyIf
(o realment, if/else
) també interromp les vostres animacions quan feu les coses des de “dins”.

								Rectangle()
    .frame(width: myState ? 100 : nil)
    .applyIf(condition) { $0.border(Color.red) }

							

Quan animes condition
, la vora no s’animarà ni el marc. Com creu SwiftUI if/else
les branques separen les vistes, en lloc d’això es produirà una transició (esvaïda).

Hi ha un altre problema fora de l’animació. Quan s’utilitza applyIf
amb una vista que conté un fitxer @State
propietat, la totalitat de la condició es perdrà quan la condició canviï. El record de @State
SwiftUI gestiona les propietats en funció de la posició de la vista a l’arbre de visualització. Per exemple, tingueu en compte la visualització següent:

								struct Stateful: View {
    @State var input: String = ""
    var body: some View {
        TextField("My Field", text: $input)
    }
}

struct Sample: View {
    var flag: Bool
    var body: some View {
        Stateful().applyIf(condition: flag) {
            $0.background(Color.red)
        }
    }
}

							

Quan canviem flag
,, applyIf
canvis clonals i Stateful()
vista té una nova posició (es va moure a l’altra branca d’un fitxer _ConditionalContent
). Això provoca @State
una propietat que s’ha de restablir al seu valor original (ja que, com per SwiftUI, s’ha afegit una nova vista a la jerarquia) i es perd el text de l’usuari. El mateix problema passa amb @StateObject
.

El més difícil de tot això és que és possible que no vegeu cap d’aquests problemes a l’hora de construir la vostra visió. Les vostres publicacions tenen bon aspecte, però potser les vostres animacions són una mica divertides o de vegades perdeu l’estat. Sobretot quan la condició no canvia tan sovint, és possible que ni tan sols ho noteu.

Diria que a totes les publicacions del bloc que ofereixen un modificador els agrada applyIf
hi ha d’haver un gran senyal d’advertència. Els desavantatges de applyIf
i les seves variants no són gens evidents i, per desgràcia, he vist un munt de persones que acaben de copiar això a les seves bases de codis i n’han quedat molt contents (fins que setmanes després es va convertir en una font de problemes). De fet, ho diria cap base de codi ha de tenir aquesta funció
. Simplement, és massa fàcil trencar accidentalment animacions o estats.

Si esteu interessats en conèixer com funciona SwiftUI, podeu llegir el nostre llibre Pensant a SwiftUI
, mireu el nostre Vídeos de SwiftUI
a Swift Talk o visiteu un dels nostres tallers
.

Add a Comment

Your email address will not be published. Required fields are marked *