Thursday, February 9, 2023
HomeiOS DevelopmentWhy Conditional View Modifiers are a Dangerous Thought · objc.io

Why Conditional View Modifiers are a Dangerous Thought · objc.io


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

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

							

There are lots of weblog posts on the market with comparable modifiers. I believe 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’ve got a single state property myState. When it adjustments 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, rework: { $0.body(width: 100) })
        }
        
    }
}

							

Curiously, when operating this code, the animation doesn’t look clean in any respect. When you look carefully, you’ll be able to see that it fades between the “earlier than” and “after” state:

Here 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 quite a bit 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 adjustments.

In SwiftUI, views are structs — worth varieties — which implies that they do not have id. For SwiftUI to animate adjustments, 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 know the distinction in habits between the 2 examples, let us take a look at their varieties. Here is the kind of our Rectangle().applyIf(...):

								_ConditionalContent<ModifiedContent<Rectangle, _FrameLayout>, Rectangle>

							

The outermost sort is a _ConditionalContent. That is an enum that may both include the worth from executing the if department, or the worth from executing the else department. When situation adjustments, SwiftUI can not interpolate between the outdated and the brand new worth, as they’ve differing kinds. In SwiftUI, when you’ve 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>

							

Once we animate adjustments to the body properties, there aren’t any branches for SwiftUI to think about. It could actually 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’ll be able to add a conditional foreground colour by utilizing an optionally available colour, you’ll be able to add conditional padding by utilizing both 0 or a price, and so forth.

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

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

							

While 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 yet one more downside past animations. While you use applyIf with a view that incorporates a @State property, all state might be misplaced when the situation adjustments. The reminiscence of @State properties is managed by SwiftUI, primarily based on the place of the view within the view tree. For instance, take into account the next view:

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

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

							

Once we change flag, the applyIf department adjustments, 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 downside additionally occurs with @StateObject.

The tough half about all of that is that you just won’t see any of those points when constructing your view. Your views look high quality, however perhaps your animations are just a little funky, otherwise you typically lose state. Particularly when the situation does not change all that usually, you won’t 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 under no circumstances apparent, and I’ve sadly seen a bunch of people that have simply copied this into their code bases and have been very pleased with it (till it grew to become 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 means too simple to unintentionally break animations or state.

When you’re thinking about understanding how SwiftUI works, you possibly can learn our e-book Considering in SwiftUI, watch our SwiftUI movies on Swift Discuss, or attend one among our workshops.

RELATED ARTICLES

Most Popular