Thursday, April 17, 2025
HomeiOS DevelopmentWhy Conditional View Modifiers are a Unhealthy Concept · objc.io

Why Conditional View Modifiers are a Unhealthy Concept · objc.io

[ad_1]

Within the SwiftUI group, many individuals give you their very own model of a conditional view modifier
. It permits you to take a view, and solely apply a view modifier when the situation holds. It sometimes seems to be one thing like this:

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

							

There are numerous weblog posts on the market with comparable modifiers. I feel all these weblog posts ought to include an enormous warning signal. Why is the above code problematic? Let’s take a look at a pattern.

Within the following code, we now have a single state property myState
. When it modifications between true
and false
, we wish to conditionally apply a body:

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

							

Apparently, when working this code, the animation doesn’t look easy in any respect. If you happen to look intently, you may see that it fades between the “earlier than” and “after” state:

This is the identical instance, however written with out applyIf
:

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

							

And with the code above, our animation works as anticipated:

Why is the applyIf
model damaged? The reply teaches us so much about how SwiftUI works. In UIKit, views are objects, and objects have inherent id
. Which means that two objects are equal if they’re the identical object. UIKit depends on the id of an object to animate modifications.

In SwiftUI, views are structs — worth varieties — which implies that they do not have id. For SwiftUI to animate modifications, it wants to check the worth of the view earlier than
the animation began and the worth of the view after
the animation ends. SwiftUI then interpolates between the 2 values.

To grasp the distinction in habits between the 2 examples, let’s take a look at their varieties. This is the kind of our Rectangle().applyIf(...)
:

								_ConditionalContent<ModifiedContent<Rectangle, _FrameLayout>, Rectangle>

							

The outermost sort is a _ConditionalContent
. That is an enum that can both
include the worth from executing the if
department, or
the worth from executing the else
department. When situation modifications, SwiftUI can not interpolate between the outdated and the brand new worth, as they’ve differing types. In SwiftUI, when you could have an if/else
with a altering situation, a transition
occurs: the view from the one department is eliminated and the view for the opposite department is inserted. By default, the transition is a fade, and that is precisely what we’re seeing within the applyIf
instance.

In distinction, that is the kind of Rectangle().body(...)
:

								ModifiedContent<Rectangle, _FrameLayout>

							

After we animate modifications to the body properties, there aren’t any branches for SwiftUI to contemplate. It could simply interpolate between the outdated and new worth and every little thing works as anticipated.

Within the Rectangle().body(...)
instance, we made the view modifier conditional by offering a nil
worth for the width. That is one thing that just about each view modifier assist. For instance, you may add a conditional foreground shade by utilizing an elective shade, you may add conditional padding by utilizing both 0 or a worth, and so forth.

Be aware that applyIf
(or actually, if/else
) additionally breaks your animations when you find yourself doing issues accurately on the “inside”.

								Rectangle()
    .body(width: myState ? 100 : nil)
    .applyIf(situation) { $0.border(Coloration.purple) }

							

If you animate situation
, the border is not going to animate, and neither will the body. As a result of SwiftUI considers the if/else
branches separate views, a (fade) transition will occur as a substitute.

There’s one more drawback past animations. If you use applyIf
with a view that comprises a @State
property, all state can be misplaced when the situation modifications. The reminiscence of @State
properties is managed by SwiftUI, based mostly on the place of the view within the view tree. For instance, think about the next view:

								struct Stateful: View {
    @State var enter: String = ""
    var physique: some View {
        TextField("My Area", textual content: $enter)
    }
}

struct Pattern: View {
    var flag: Bool
    var physique: some View {
        Stateful().applyIf(situation: flag) {
            $0.background(Coloration.purple)
        }
    }
}

							

After we change flag
, the applyIf
department modifications, and the Stateful()
view has a brand new place (it moved to the opposite department of a _ConditionalContent
). This causes the @State
property to be reset to its preliminary worth (as a result of so far as SwiftUI is anxious, a brand new view was added to the hierarchy), and the person’s textual content is misplaced. The identical drawback additionally occurs with @StateObject
.

The difficult half about all of that is that you just may not see any of those points when constructing your view. Your views look high quality, however possibly your animations are slightly funky, otherwise you generally lose state. Particularly when the situation would not change all that usually, you may not even discover.

I’d argue that all the weblog posts that counsel a modifier like applyIf
ought to have an enormous warning signal. The downsides of applyIf
and its variants are by no means apparent, and I’ve sadly seen a bunch of people that have simply copied this into their code bases and had been very pleased with it (till it turned a supply of issues weeks later). In actual fact, I’d argue that no code base ought to have this operate
. It simply makes it approach too simple to by chance break animations or state.

If you happen to’re fascinated with understanding how SwiftUI works, you can learn our e book Pondering in SwiftUI
, watch our SwiftUI movies
on Swift Discuss, or attend considered one of our workshops
.

[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments