{"id":9740,"date":"2023-02-27T13:05:00","date_gmt":"2023-02-27T10:05:00","guid":{"rendered":"https:\/\/handoli.com\/index.php\/2023\/02\/27\/swiftui-views-versus-modifiers\/"},"modified":"2023-02-27T13:05:00","modified_gmt":"2023-02-27T10:05:00","slug":"swiftui-views-versus-modifiers","status":"publish","type":"post","link":"https:\/\/handoli.com\/index.php\/2023\/02\/27\/swiftui-views-versus-modifiers\/","title":{"rendered":"SwiftUI views versus modifiers"},"content":{"rendered":"<p>One of the most interesting aspects of SwiftUI, at least from an architectural perspective, is how it essentially treats views as data. After all, a SwiftUI view isn\u2019t a direct representation of the pixels that are being rendered on the screen, but rather a <em>description<\/em> of how a given piece of UI should work, look, and behave.<\/p>\n<p>That very data-driven approach gives us a ton of flexibility when it comes to how we structure our view code \u2014 to the point where one might even start to question what the difference actually is between defining a piece of UI as a view type, versus implementing that same code as a modifier instead.<\/p>\n<p>Take the following <code>FeaturedLabel<\/code> view as an example \u2014 it adds a leading star image to a given text, and also applies a specific foreground color and font to make that text stand out as being \u201cfeatured\u201d:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> FeaturedLabel: <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">var<\/span> text: <span class=\"s-type\">String<\/span>\n\n    <span class=\"s-keyword\">var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">HStack<\/span> {\n            <span class=\"s-type\">Image<\/span>(systemName: <span class=\"s-string\">\"star\"<\/span>)\n            <span class=\"s-type\">Text<\/span>(text)\n        }\n        .<span class=\"s-call\">foregroundColor<\/span>(.<span class=\"s-dotAccess\">orange<\/span>)\n        .<span class=\"s-call\">font<\/span>(.<span class=\"s-dotAccess\">headline<\/span>)\n    }\n}<\/code><\/pre>\n<p>While the above may look like a typical custom view, the exact same rendered UI could just as easily be achieved using a \u201cmodifier-like\u201d <code>View<\/code> protocol extension instead \u2014 like this:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">extension<\/span> <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">func<\/span> featured() -&gt; <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">HStack<\/span> {\n            <span class=\"s-type\">Image<\/span>(systemName: <span class=\"s-string\">\"star\"<\/span>)\n            <span class=\"s-keyword\">self<\/span>\n        }\n        .<span class=\"s-call\">foregroundColor<\/span>(.<span class=\"s-dotAccess\">orange<\/span>)\n        .<span class=\"s-call\">font<\/span>(.<span class=\"s-dotAccess\">headline<\/span>)\n    }\n}<\/code><\/pre>\n<p>Here\u2019s what those two different solutions look like side-by-side when placed within an example <code>ContentView<\/code>:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> ContentView: <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">VStack<\/span> {\n            <span class=\"s-comment\">\/\/ View-based version:<\/span>\n            <span class=\"s-type\">FeaturedLabel<\/span>(text: <span class=\"s-string\">\"Hello, world!\"<\/span>)\n\n            <span class=\"s-comment\">\/\/ Modifier-based version:<\/span>\n            <span class=\"s-type\">Text<\/span>(<span class=\"s-string\">\"Hello, world!\"<\/span>).<span class=\"s-call\">featured<\/span>()\n        }\n    }\n}<\/code><\/pre>\n<p>One key difference between our two solutions, though, is that the latter can be applied to <em>any view<\/em>, while the former only enables us to create <code>String<\/code>-based featured labels. That\u2019s something that we could address, though, by turning our <code>FeaturedLabel<\/code> into a custom container view that accepts any kind of <code>View<\/code>-conforming content, rather than just a plain string:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> FeaturedLabel&lt;Content: <span class=\"s-type\">View<\/span>&gt;: <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">@ViewBuilder var<\/span> content: () -&gt; <span class=\"s-type\">Content<\/span>\n\n    <span class=\"s-keyword\">var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">HStack<\/span> {\n            <span class=\"s-type\">Image<\/span>(systemName: <span class=\"s-string\">\"star\"<\/span>)\n            <span class=\"s-call\">content<\/span>()\n        }\n        .<span class=\"s-call\">foregroundColor<\/span>(.<span class=\"s-dotAccess\">orange<\/span>)\n        .<span class=\"s-call\">font<\/span>(.<span class=\"s-dotAccess\">headline<\/span>)\n    }\n}<\/code><\/pre>\n<p class=\"info\">Above we\u2019re adding the <code>ViewBuilder<\/code> attribute to our <code>content<\/code> closure in order to enable the full power of SwiftUI\u2019s view building API to be used at each call site (which, for example, lets us use <code>if<\/code> and <code>switch<\/code> statements when building the content for each <code>FeaturedLabel<\/code>).<\/p>\n<p>We might still want to make it easy to initialize a <code>FeaturedLabel<\/code> instance with a string, though, rather than always having to pass a closure containing a <code>Text<\/code> view. Thankfully, that\u2019s something that we can easily make possible using a type-constrained extension:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">extension<\/span> <span class=\"s-type\">FeaturedLabel<\/span> <span class=\"s-keyword\">where<\/span> <span class=\"s-type\">Content<\/span> == <span class=\"s-type\">Text<\/span> {\n    <span class=\"s-keyword\">init<\/span>(<span class=\"s-keyword\">_<\/span> text: <span class=\"s-type\">String<\/span>) {\n        <span class=\"s-keyword\">self<\/span>.<span class=\"s-keyword\">init<\/span> {\n            <span class=\"s-type\">Text<\/span>(text)\n        }\n    }\n}<\/code><\/pre>\n<p class=\"info\">Above we\u2019re using an underscore to remove the external parameter label for <code>text<\/code>, to mimic the way SwiftUI\u2019s own, built-in convenience APIs work for types like <code>Button<\/code> and <code>NavigationLink<\/code>.<\/p>\n<p>With those changes in place, both of our two solutions now have the exact same amount of flexibility, and can easily be used to create both text-based labels, as well as labels that render any kind of SwiftUI view that we want:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> ContentView: <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">@State private var<\/span> isToggleOn = <span class=\"s-keyword\">false\n\n    var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">VStack<\/span> {\n            <span class=\"s-comment\">\/\/ Using texts:<\/span>\n            <span class=\"s-type\">Group<\/span> {\n                <span class=\"s-comment\">\/\/ View-based version:<\/span>\n                <span class=\"s-type\">FeaturedLabel<\/span>(<span class=\"s-string\">\"Hello, world!\"<\/span>)\n\n                <span class=\"s-comment\">\/\/ Modifier-based version:<\/span>\n                <span class=\"s-type\">Text<\/span>(<span class=\"s-string\">\"Hello, world!\"<\/span>).<span class=\"s-call\">featured<\/span>()\n            }\n\n            <span class=\"s-comment\">\/\/ Using toggles:<\/span>\n            <span class=\"s-type\">Group<\/span> {\n                <span class=\"s-comment\">\/\/ View-based version:<\/span>\n                <span class=\"s-type\">FeaturedLabel<\/span> {\n                    <span class=\"s-type\">Toggle<\/span>(<span class=\"s-string\">\"Toggle\"<\/span>, isOn: <span class=\"s-property\">$isToggleOn<\/span>)\n                }\n\n                <span class=\"s-comment\">\/\/ Modifier-based version:<\/span>\n                <span class=\"s-type\">Toggle<\/span>(<span class=\"s-string\">\"Toggle\"<\/span>, isOn: <span class=\"s-property\">$isToggleOn<\/span>).<span class=\"s-call\">featured<\/span>()\n            }\n        }\n    }\n}<\/code><\/pre>\n<p>So at this point, we might <em>really<\/em> start to ask ourselves \u2014 what exactly is the difference between defining a piece of UI as a view versus a modifier? Is there really any practical differences at all, besides code style and structure?<\/p>\n<p>Well, what about state? Let\u2019s say that we wanted to make our new featured labels automatically fade in when they first appear? That would require us to define something like a <code>@State<\/code>-marked <code>opacity<\/code> property that we\u2019d then animate over using an <code>onAppear<\/code> closure \u2014 for example like this:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> FeaturedLabel&lt;Content: <span class=\"s-type\">View<\/span>&gt;: <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">@ViewBuilder var<\/span> content: () -&gt; <span class=\"s-type\">Content<\/span>\n    <span class=\"s-keyword\">@State private var<\/span> opacity = <span class=\"s-number\">0.0<\/span>\n\n    <span class=\"s-keyword\">var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">HStack<\/span> {\n            <span class=\"s-type\">Image<\/span>(systemName: <span class=\"s-string\">\"star\"<\/span>)\n            <span class=\"s-call\">content<\/span>()\n        }\n        .<span class=\"s-call\">foregroundColor<\/span>(.<span class=\"s-dotAccess\">orange<\/span>)\n        .<span class=\"s-call\">font<\/span>(.<span class=\"s-dotAccess\">headline<\/span>)\n        .<span class=\"s-call\">opacity<\/span>(opacity)\n        .<span class=\"s-call\">onAppear<\/span> {\n            <span class=\"s-call\">withAnimation<\/span> {\n                opacity = <span class=\"s-number\">1<\/span>\n            }\n        }\n    }\n}<\/code><\/pre>\n<p>At first, participating in the SwiftUI state management system might seem like something that only proper view types can do, but it turns out that modifiers have the exact same capability \u2014 as long as we define such a modifier as a proper <code>ViewModifier<\/code>-conforming type, rather than just using a <code>View<\/code> protocol extension:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> FeaturedModifier: <span class=\"s-type\">ViewModifier<\/span> {\n    <span class=\"s-keyword\">@State private var<\/span> opacity = <span class=\"s-number\">0.0<\/span>\n\n    <span class=\"s-keyword\">func<\/span> body(content: <span class=\"s-type\">Content<\/span>) -&gt; <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">HStack<\/span> {\n            <span class=\"s-type\">Image<\/span>(systemName: <span class=\"s-string\">\"star\"<\/span>)\n            content\n        }\n        .<span class=\"s-call\">foregroundColor<\/span>(.<span class=\"s-dotAccess\">orange<\/span>)\n        .<span class=\"s-call\">font<\/span>(.<span class=\"s-dotAccess\">headline<\/span>)\n        .<span class=\"s-call\">opacity<\/span>(opacity)\n        .<span class=\"s-call\">onAppear<\/span> {\n            <span class=\"s-call\">withAnimation<\/span> {\n                opacity = <span class=\"s-number\">1<\/span>\n            }\n        }\n    }\n}<\/code><\/pre>\n<p>With the above in place, we can now replace our previous <code>featured<\/code> method implementation with a call to add our new <code>FeaturedModifier<\/code> to the current view \u2014 and both of our two featured label approaches will once again have the exact same end result:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">extension<\/span> <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">func<\/span> featured() -&gt; <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-call\">modifier<\/span>(<span class=\"s-type\">FeaturedModifier<\/span>())\n    }\n}<\/code><\/pre>\n<p class=\"info\">Also worth noting is that when wrapping our code within a <code>ViewModifier<\/code> type, that code is lazily evaluated when needed, rather than being executed up-front when the modifier is first added, which could make a difference performance-wise in certain situations.<\/p>\n<p>So, regardless of whether we want to change the styles or structure of a view, or introduce a new piece of state, it\u2019s starting to become clear that SwiftUI views and modifiers have the exact same capabilities. But that brings us to the next question \u2014 if there are no practical differences between the two approaches, how do we choose between them?<\/p>\n<p>At least to me, it all comes down to the <em>structure<\/em> of the resulting view hierarchy. Although we were, technically, changing the view hierarchy when wrapping one of our featured labels within an <code>HStack<\/code> in order to add our star image, conceptually, that was more about <em>styling<\/em> than it was about structure. When applying the <code>featured<\/code> modifier to a view, its layout or placement within the view hierarchy didn\u2019t really change in any meaningful way \u2014 it still just remained a single view with the exact same kind of overall layout, at least from a high-level perspective.<\/p>\n<p>That\u2019s not always the case, though. So let\u2019s take a look at another example which should illustrate the potential structural differences between views and modifiers a bit more clearly.<\/p>\n<p>Here we\u2019ve written a <code>SplitView<\/code> container, which takes two views \u2014 one leading, and one trailing \u2014 and then renders them side-by-side with a divider between them, while also maximizing their frames so that they\u2019ll end up splitting the available space equally:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> SplitView&lt;Leading: <span class=\"s-type\">View<\/span>, Trailing: <span class=\"s-type\">View<\/span>&gt;: <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">@ViewBuilder var<\/span> leading: () -&gt; <span class=\"s-type\">Leading<\/span>\n    <span class=\"s-keyword\">@ViewBuilder var<\/span> trailing: () -&gt; <span class=\"s-type\">Trailing<\/span>\n\n    <span class=\"s-keyword\">var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">HStack<\/span> {\n            <span class=\"s-call\">prepareSubview<\/span>(<span class=\"s-call\">leading<\/span>())\n            <span class=\"s-type\">Divider<\/span>()\n            <span class=\"s-call\">prepareSubview<\/span>(<span class=\"s-call\">trailing<\/span>())\n        }\n    }\n\n    <span class=\"s-keyword\">private func<\/span> prepareSubview(<span class=\"s-keyword\">_<\/span> view: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span>) -&gt; <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        view.<span class=\"s-call\">frame<\/span>(maxWidth: .<span class=\"s-dotAccess\">infinity<\/span>, maxHeight: .<span class=\"s-dotAccess\">infinity<\/span>)\n    }\n}<\/code><\/pre>\n<p>Just like before, we could definitely achieve the exact same result using a modifier-based approach instead \u2014 which could look like this:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">extension<\/span> <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">func<\/span> split(with trailingView: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span>) -&gt; <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">HStack<\/span> {\n            <span class=\"s-call\">maximize<\/span>()\n            <span class=\"s-type\">Divider<\/span>()\n            trailingView.<span class=\"s-call\">maximize<\/span>()\n        }\n    }\n\n    <span class=\"s-keyword\">func<\/span> maximize() -&gt; <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-call\">frame<\/span>(maxWidth: .<span class=\"s-dotAccess\">infinity<\/span>, maxHeight: .<span class=\"s-dotAccess\">infinity<\/span>)\n    }\n}<\/code><\/pre>\n<p>However, if we once again put our two solutions next to each other within the same example <code>ContentView<\/code>, then we can start to see that this time the two approaches do look quite different in terms of structure and clarity:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> ContentView: <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">VStack<\/span> {\n            <span class=\"s-comment\">\/\/ View-based version:<\/span>\n            <span class=\"s-type\">SplitView<\/span>(leading: {\n                <span class=\"s-type\">Text<\/span>(<span class=\"s-string\">\"Leading\"<\/span>)\n            }, trailing: {\n                <span class=\"s-type\">Text<\/span>(<span class=\"s-string\">\"Trailing\"<\/span>)\n            })\n\n            <span class=\"s-comment\">\/\/ Modifier-based version:<\/span>\n            <span class=\"s-type\">Text<\/span>(<span class=\"s-string\">\"Leading\"<\/span>).<span class=\"s-call\">split<\/span>(with: <span class=\"s-type\">Text<\/span>(<span class=\"s-string\">\"Trailing\"<\/span>))\n        }\n    }\n}<\/code><\/pre>\n<p>Looking at the view-based call site above, it\u2019s very clear that our two texts are being wrapped within a container, and it\u2019s also easy to tell which of those two texts will end up being the leading versus trailing view.<\/p>\n<p>That same thing can\u2019t really be said for the modifier-based version this time, though, which really requires us to know that the view that we\u2019re <em>applying<\/em> the modifier to will end up in the leading slot. Plus, we can\u2019t really tell that those two texts will end up being wrapped in some kind of container at all. It looks more like we\u2019re styling the leading label using the trailing label somehow, which really isn\u2019t the case.<\/p>\n<p>While we could attempt to solve that clarity problem with more verbose API naming, the core issue would still remain \u2014 that the modifier-based version doesn\u2019t properly show what the resulting view hierarchy will be in this case. So, in situations like the one above, when we\u2019re wrapping multiple siblings within a parent container, opting for a view-based solution will often give us a much clearer end result.<\/p>\n<p>On the flip side, if all that we\u2019re doing is applying a set of styles to a single view, then implementing that as either a \u201cmodifier-like\u201d extension, or using a proper <code>ViewModifier<\/code> type, will most often be the way to go. And for everything in between \u2014 such as our earlier \u201cfeatured label\u201d example \u2014 it all really comes down to code style and personal preference as to which solution will be the best fit for each given project.<\/p>\n<p>Just look at how SwiftUI\u2019s built-in API was designed \u2014 containers (such as <code>HStack<\/code> and <code>VStack<\/code>) are views, while styling APIs (such as <code>padding<\/code> and <code>foregroundColor<\/code>) are implemented as modifiers. So, if we follow that same approach as much as possible within our own projects, then we\u2019ll likely end up with UI code that feels consistent and inline with SwiftUI itself.<\/p>\n<p>I hope that you found this article interesting and useful. Feel free to <a href=\"https:\/\/mastodon.social\/@johnsundell\">find me on Mastodon<\/a>, or <a href=\"https:\/\/www.swiftbysundell.com\/contact\">contact me via email<\/a>, if you have any questions, comments, or feedback.<\/p>\n<p>Thanks for reading!<\/p>","protected":false},"excerpt":{"rendered":"<p>One of the most interesting aspects of SwiftUI, at least from an architectural perspective, is how it essentially treats views as data. After all, a SwiftUI view isn\u2019t a direct representation of the pixels that are being rendered on the screen, but rather a description of how a given piece of UI should work, look, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rop_custom_images_group":[],"rop_custom_messages_group":[],"rop_publish_now":"initial","rop_publish_now_accounts":[],"rop_publish_now_history":[],"rop_publish_now_status":"pending","footnotes":""},"categories":[1,15],"tags":[],"class_list":["post-9740","post","type-post","status-publish","format-standard","hentry","category-explore","category-world"],"_links":{"self":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts\/9740","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/comments?post=9740"}],"version-history":[{"count":0,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts\/9740\/revisions"}],"wp:attachment":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/media?parent=9740"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/categories?post=9740"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/tags?post=9740"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}