5 Steps to Better SwiftUI Views

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
sponsored by revenue cat where you can add subscriptions to your ios project in minutes rather than hours so you can forget about any storkit problems and get back to building your app hello my name is paul and this talk is called five steps to better swift ui views just briefly if you haven't heard of my work before i run a site called hackingwithswift.com where there's stacks of free swift and swifty white tutorials anyway at this point with 50y we're past the novelty folks understand how easy it is to build and prototype fantastic looking applications we're used to the idea that we can have state managed effectively in our application plus we know we can write great cross-platform code for mac os ios watch os and more however although many folks are writing increasingly large swift ui programs we're still a long way from figuring out what best practices look like we sort of understand what it means as we write bigger and bigger programs and it's happening folks were writing increasingly complex swifty ui programs and we're starting to suffer from the same problems we had in ui kit for example the the classic ui kit problem was of course uh mvc model view controller became massive view controller it was retronymd that which isn't great but of course in swifty ui we don't have the controllers in fact some folks would say that's the best feature of 50y there are no view controllers however we do have lots of views and we cram lots of functionality into them logic actions layout and more and this has created a wholly new problem which is that every view is large and helpfully this has the acronym evil which i think tells you a great deal about what i think about it now practice evil comes in many forms including lawful evil where you're doing your best but your views still get large or neutral evil you'd like to do your best but your team doesn't have the time resources funding and as a result your views get large then there is chaotic evil you're using your views as dumping grounds just like you might have done with app delegate in the olden days now of course as we all know the only thing required for evil to triumph is for good programmers to do nothing so in this talk i'm going to present five steps anyone can take to improve their 50y views making them simpler smaller easy to understand easier to reuse and more and these steps are small enough you can implement them easily no matter what kind of evil you have in your project right now i should say this is going to be super fast so i if you want to watch it again don't feel too bad about it i'm going to race through this i know your time's precious we'll get right into the code now so i've made a simple sandbox project right here in xcode to demonstrate the ways we can prove our code it's running right now here in the sim as you can see you can choose a destination like abu simbel in egypt or the blue mountains australia i'm going to choose penang in malaysia you can see there's a picture of penang some titles information about malaysia a little factoid a little uh travel advisory um you can add and remove favorites so you can see this little icon here it's got a heart next to it it's a favorite place to go i can remove that if i wanted to want to start a fight um and you can of course shorten the map to see exactly where penang is in the world plus a little tip screen here you can say you know where do i want to go or where the travel or whatever a little tip screen one last thing in our discover view here you can uh press show only favorites and it'll filter down to only the ones you've marked with a heart so that's our base application it's very simple and that's intentional but it's written in such a way that'll show off the five ways we can simplify our view code now the first way you can simplify your views is by carving off independent parts of your views into properties getting them out of your body into separate properties or sometimes methods for example in listing view we have down here this big old code for making a toolbar button this is what i showed you a minute ago for toggling showing all locations or only favorites and this is important code like i want to have this functionality i just don't want to have it here this is right inside my body for my view and alongside the navigation view the list the section navigation links that are there plus my toolbars it's a lot of code in here and if you're writing this in ui kit you'd make your right bar button items array using individual objects you'd say you know make my first button second button third button and then make an array of those into my right bar button items you wouldn't try and do it on one massive line of code it'd just be hard to work with and the same is exactly true here in swift ui we have this button here we can actually join that whole thing out into a custom computed property inside listing view and then use that inside our body to make our code easier to think about and so with that whole button plus modify selected i'll press command x to put it onto my clipboard and then at the end of body i say is a new property called favorites button returns some kind of view and paste it right on in there the same code unchanged and now in my toolbar i'll just say toolbar favorites button use that button inside our toolbar and the code still builds just fine it runs exactly the same looks the same there's no real difference from a user's perspective it's exactly the same and this works great for any kind of supplementary views like toolbar buttons like tab items like alerts like sheets and if it's not part of the core thing you're trying to show on the screen i'd get it out of there into an external property that's easier to work with now realistically at compile time there's no difference to swift having this here was having it directly in line in the body property there's a simple function called it's a property but it's a function called realistically and that'll be optimized away it'll be inlined very nicely by the swift optimizer it'll do great work here and you could if you wanted to use a method you could say uh you know funk favorites button return sound view then do favorites button um i wouldn't do that only because swifty y does use a body property this matches the swifty wire way of thinking it's very clear it's declarative we're not doing lots of work in here it's not a computationally expensive thing to call it's just some uh layout happening like that i prefer a property but you know it's down to you these properties also work great by the way um when your body is being recomputed when you've you know changed your program state for example because it will recall the properties as needed for example uh in this code here we have a for each and in the for reach if we're showing favorites we then filter our locations here otherwise we just show all locations and we actually carve that out into another property let's take this whole piece of code out like this boom and then go down here and say there is another property the items returns an array of location and paste that in there and now flesh out a bit that's easier to read if showing favorites then return that thing uh line break there there we go uh then oops else return that thing so a more natural sort of swifty way of writing this code so we can go ahead and now use that inside our full reach just to for each items which i think is so much clearer in terms of writing your code if i of course make no mistakes about it i've got to say return down here that way i get much happier there we go it's a much clearer way of thinking i think because now we're not doing this sort of inline logic inside our body we're saying go ahead and calculate everything we need beforehand or elsewhere so when body finally runs all the logic is ready or decided or simplified ready to go and that means i think kind of looking at your code critically thinking where are my conditions where's my logic where can i get things out of my view and put it somewhere else for example if we look in um location view we have an if condition right here on line 23 uh and this is trying to show the little heart icon if it's a favorite place or not and again this is important i don't want to get rid of this kind of code i want this kind of functionality in there but i don't want it there because i'm either looking at the layout here or i'm looking at logic because you can see it's mixing the two right now there's like layout scroll view layout z stack layout image and then suddenly a big condition followed by more layout layout layout layout and so forth it's kind of mixing up conditions and logic and and layout which is messy realistically you're not going to want to read this code most of the time the logic should be put somewhere else where you can handle it separately and this should be the results of your logic the body and so you might think fine i'll just go ahead and select this whole if block here and again join that out into its own property so i'll i'll command x that if block and then uh make a property up here somewhere let's do uh var uh favorite icon is some view and paste it all in there like that and uh as you can see it doesn't work can't do that even if you do say you know return in here it's still not gonna work because we're saying our favorite icon will return some kind of view but it'll only return some kind of view if it is a favorite location if it isn't you know what happens here an else block right it won't always work and so swift will be unhappy with us for for very right reasons now we could if you wanted to enable swifty wires view builder system here we could say i want to use at view builder var favorite icon some view and then remove return and now the code will compile correctly so you can now say i want to have just favorite icon just there that it works right you could do that if you wanted to again i wouldn't i happen to think that if you have at view builder in your code a bit of a code smell you're kind of saying hey i acknowledge i have complex logic in here please figure it out for me i think a better solution is just to uh not do that in fact it's giving me a neat and somehow carefully planned segue into my second tip to help you make your swift ui views simpler smaller more maintainable more reusable not to use viewbuilder and it is to simply break them up it's the most obvious thing in the world it works brilliantly in swift ui break big views up into smaller views don't try and make more and more properties particularly if you're doing view builder i'm going to undo this change get back to what we had before which was this thing here and i'm going to go ahead and break this for you up into multiple kinds of views because right now you can see very clearly we've got this big uh view of the picture the heart icon the text below it the buttons below and so forth i'm gonna carve off just one part of this into a separate view so you can see how this looks and that means i'm going to go ahead and select the whole if plus the image the whole thing like that i take out onto my clipboard and now i'll press command n to make a new file choose swift ui view and i'll call this thing location header dot swift like this and i'll go ahead and paste that into the body property like this and we've got to have uh the location as you can see it wants a location and a data controller both of those we can kind of steal from our location view here i'll just take these two here uh so that is boom take out showing map like that so we've got to pass in a data controller and a location we make this header view and now inside our zed stack we can just say hey pass in that location so here's my z stack i'll say location header that is location our location and it's probably still complained because the preview needs an example location so i'll just do location is location.example one i made earlier helpful that and that's it so i've ripped out that whole view into its own view and now i saved the functionality somewhere else all that condition to be shown over the image is handled by that image header it knows what to do automatically i'm going to worry about it when i care more about my text or my buttons down here they are split out nicely and neatly i think it's a big improvement and in fact this is particularly powerful when used with list rows or for each anything involves some kind of content closure being called for example in our listing view back in this uh we have this for each here um nearly all the view code for listing view is inside this for each it's just massive it's this whole chunk of code here describing a single row like one of these rows you know the picture of the flag overlay title description navigation link at all happening right in here there's lots lots of code to do one single row so our listing view is not really describing itself it's also having to describe its children which makes it very very long and this is a great example of places you can rip out things into their own view just like again we used to do a ui kit with ui table view cell subclasses so i'm going to go ahead and select the entire navigation link and command x that to my clipboard and then paste it into a new view so again command n new file shift ui view i'll call this thing a listing row and i'll just paste all that into the body property like that and now i've got to add the location property so it knows what the show so i'll just say let location be a location pass one of these in in my example down here again location is location oops location.example boom and now our for each is much much simpler we can just say each row is a listing row with location being location that's it and the result will be exactly the same boom you can see in the preview exactly the same but now we've taken out all that code to describe rows and put it into a row view they'll be shown again again again now we can actually go a step further because two marvelous swift features combine right here first up all our views in swifty y including listing row are all structs which means swift will automatically generate for us a memberwise initializer an initializer method that will accept uh the parameters for the sync in this case is a location and second this for each we have here is actually a view under the hood it's a struct uh view and that means it too has initializer and the second parameter for that is what to do with a given item from the array so you say go over go over all the items and for a given item in our case a given location give me one of those things i'll send you back a view so on on one side we have a uh member wise initializer give it give me my parameter a single location i'll give you back a listing row on the other side we have a for each struct that expects to be given a function that takes a location and returns a view right now it's all trailing closure stuff doesn't need to be we can bring them together we can say do this at the same time we'll do for each items content is listing listingrow.init for each item in the array create a listing row and that is just beautiful as you can see it works exactly the same way there's no difference here in terms of actual end user functionality it's quite quite brilliant okay that's our first two steps complete so now let's look at step three which is getting action code out of your views this is the flip side of getting logic out of your views we have those if statements and similar get it out of the views it's now onto action code this is just as important quite frankly and so for example in location view we have buttons one for uh toggling favorites here and one for shogunate map both of which put their action code directly into the button closure here it is toggle favorite here or um set a building a true and i think my dogs are sticking outside the door they are they know i'm talking to myself they know his treats going bunch of bad dogs yes hello both dogs come on quickly i'm on a tight time here you and you hold on all right so uh we're putting the action code directly into the button which is weird because we're now mixing together layout hdac spacer button to do and logic if statements and loops and so forth and action closures all in one thing it's a real nightmare of work at the very least we should be getting the action functionality out somewhere else um even though in both these places hello dog come on come on quick shot last one though even though in both these places it's just one line of code this thing here one line of code this thing here one line of code i'd rip them both out so i'll just take this whole toggle favorite line here and move it into its own method so again put the clipboard go down the way and say uh funk toggle favorite and let's paste it in there directly and now the button for that becomes a bit simpler we can say uh get rid of these braces uh boom and say uh action is toggle favorite boom so we're saying when this button is pressed call toggle favorite straight away now remember we don't want to say toggle favorite open and close parens that means you can find out my action by calling toggle favorite and it will turn the action to you that you want to call later on without the parents it means when i'm tapped call toggle favorite and you might think this is worse because our code's actually a bit longer because we now have the uh little signature here as well funk toggle favorite but it's particularly odd i think it's only one line of code right there's only one thing happening here it wasn't that much of an egregious problem in our body but now it's separate method we can do more with it you know we can go ahead and write a unit test to say make my location view struct call toggle favorite on it and check things actually happened check it written out to use the defaults or whether it's going to we actually verify our code without a ui test and let's face it if most of the time you want to have a ui test you probably have hate problems for yourself um because they're not very pleasant things right so this allows us to get our code our action code out of our body and therefore write tests for it particularly if you have multiple lines of code in here it makes your body much much simpler again and of course we can do the same thing for uh showing the map take the uh single line out and uh say funk show on map boom like that and then say button show on map we'll do action is show on map i'll just indent things neatly like that boom again we're only moving one line of code out here but it's surprisingly common to see actual complex closures passed in line with button actions or toolbar actions or similar and getting them out does make your code easier to understand and work with in fact what i've found time and time again is that when i'm working with my code i'm either thinking about the actions i want to perform you know the methods i want to run or the layouts i'm not flipping between the two of them very much so having the methods separately is a real help and in fact now they are separate methods they actually work inside the jump bar so you can go straight to show on map or toggle favorite if you want to later on which you wouldn't get in when they're in line closures so it's a big improvement across the board i think if you wanted to you could take us even further um you could say um in listing view we made that little favorites button thing here uh which calls our property you can actually break that property up further go as far as you like you know take that code out with animation code put that in a special method funk toggle favorites like that just go ahead and paste it on in there and now your button curve becomes simply let's do button action is toggle favorites boom so again your code gets a little bit simpler to understand you can now call toggle favorites from elsewhere if you want to testing purposes and similar which is great plus we're breaking up layout and functionality we're not cramming them all on the same properties same methods and similar that's always nice okay for our fourth way to simplify our views i want to head back to locationview.swift because here we have these three titles showing information about our place country that you know and travel advisories and these are nice and they're all done using these pieces of code here and you can see they have title 2 secondary foreground color black font weight and text case uppercase multiple times exactly the same set of modifiers to get an exact look and we want that look we want to keep it looking like that but it's unnecessarily long and it's also flaky if you decide to go to say title three you go okay find title three here and title three here and whoops you forgot one by accident and now you've got a problem so you've got different font sizes so it's not great having to repeat yourself again again again so what we're going to do is we're going to rip that code out again and place it into a custom view extension so we have more control over reusing this layout exactly right and so i'll go ahead and take all four of these modifiers onto my clipboard and make it uh just here i ideally put it somewhere else but here's fine make it in uh extension view i'll call this thing funk header style return some view and then do self with that attached so the same modifiers apply to whatever i call it on and now we can say all being well is slightly angry at me because why are you angry at me you're angry because ooh let's do uh text that'll be why don't do it on view do it on text um we can't have font weights on view doesn't work anyway so i've got text like that header style and i can use that method like any other kind of modifier it now just bundles them together into one single use case for me so i could say that my country here this is a dot header style or did you know you are dot header style advisories you are dot header style it makes it really nice to encapsulate commonly used header styles or text styles or image styles or you name it in one simple place and by having it as an extension of text it only appears possible on text views not other kinds of views you want to see hairstyle polluting your global namespace i actually find these extensions really useful sometimes because particularly if i'm doing cross-platform code i can have custom methods in place to control platform customizations i could say for example i want to have a an extension on extension on view itself to have funk ios with some kind of content which is a view pass in a modifier function that takes our self i return some kind of new content just self returns some view and then inside there we can say if the os is ios call our modifier on our self otherwise do not call the modifier send it back and this kind of helpful little method here is really great because now anywhere you say actually i'm on mac os and ios or watch os or tvs what do you want to work with but only on ios it doesn't look quite right i want to customize it here to add some more padding or different color or whatever it is you can now say something like um for our header style i'll do dot ios i want to have dollar zero dot padding is 10 i add 10 extra points of padding on ios but not mac os tbs watch os you want to and then once you've done that you go ahead and add other methods func mac os func watch us or whatever platform you're supporting with exactly the same code just changing ios here and here conditionally apply this modifier to mac os or watch os just a little kind of small tweaks between platforms it works really really well let's take that out again okay the fifth and final step in this whirlwind uh talk about making better views is to use styles which are separate beasts to view extensions now again in location view we have these buttons at the bottom down here and they have very particular styles they have a little bit padding blue background color white foreground color and then a clip shape of capsule and we could again take those out into custom extensions i could say i want this bunch of modifiers here and i'll say there is a extension on button i'll call this thing funk primary style return some view like this and do self uh that just the same four modifiers and that works you can totally do that if you want to you could then go down here and say um let's do button that dot primary style and then same thing there that code works do i do that do that but swifty y has a better solution which is the button style protocol this will hand us a button label whatever's on the button as a separate configuration view and we can position it or modify it however you want to and to do that go back to our little extension here and change it out we'll say instead of that code here there's a struct called primary button style which returns a button style and we'll say funk make body there's a little cover position helping me out takes that configuration data i told you about return some kind of view in there we'll use that configuration label the view that describes the contents of the button then padding background foreground and clip shape the rest of it's the same and with that in place we now no longer call primary style directly we instead say i want to use button style of primary button style my custom struct applied there and applied there and in this instance fine the result is pretty much identical not exactly the same but pretty much the same but now we're using a custom button style we get extra control that would not be possible with a simple view extension for example we might want to say when my button's actively being pressed like held down by someone's finger right now or you know mouse if they're on mac os with catalyst or whatever do something different you want to say i want to have this blue color be slightly transparent whatever so it's got a bit of indication it's being pressed right now this is available to us through configuration dot is pressed is it being pressed right now so we could say background is going to be configuration dot is pressed if true then color dot blue but opacity 0.5 otherwise color dot blue so now when it's being pressed it'll go slightly transparent let's give that a quick try as you can see what i mean so i'll choose the blue mountains beautiful place and press uh add favorite see that's going slightly bluish i press over it very very nice and simple so now not only has our view code got a lot smaller they can code out into this header style thing or this button style but we're now getting a completely reusable button style that can go anywhere in our program with extra functionality tracking is pressed or not that's our five things let's go back to some slides and that wraps up our lightning fast discussion of how to banish evil from your soft ui code and replace it with beautiful swift ui code you can be proud of to summarize we covered five things here first up making your supplementary views properties like toolbars alerts sheets and more breaking larger views into smaller ones rather than trying to use view builder look at how to get action code out of your view body make it more testable we looked at how to make view extensions for styling including custom ios or mac os one for optional code by platform and finally we looked at button style there's also list style toggle style and many many more again it's been a really really fast talk but i hope you enjoyed it if you've got questions hit me up on twitter i am two straws there or you can email me i'm paul at hackingwithswift.com
Info
Channel: Paul Hudson
Views: 23,153
Rating: 4.9887323 out of 5
Keywords: swift, swiftui, ui, Xcode, tips, protocol, view, refactor
Id: uC3X4FoielU
Channel Id: undefined
Length: 31min 18sec (1878 seconds)
Published: Wed Jan 27 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.