Previous episode: 24. Environment Values
Next episode: 26. Swipe Actions
Get immediate access to this and 4,000+ other videos and books.
Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and
4,000+ professional videos in a single subscription, it's simply the best investment you can make in
your development career.
Some of your books you have read. Some, you have not. A useful organizational feature of your app would be to section those off from each other. And you're in luck, because sectioning is a feature of lists. For example, the contacts app has a section for every letter that an existing contact starts with. The settings app has sections for different kinds of settings. Some sections have headers or footers, and some don't. For our app, we'll use a section for each of the two Read Me options for a book. And each one will have a header, an old school, skeuomorphic header to match up with the physically-based theme of the app. And cut. We'll start by updating our library data to give us a representation of the sections we want, and all of our books sorted into those sections. To represent the sections in code, let's add an enumeration in the library file. We're going to want to be able to iterate through all of those sections later, so make this case iterable too. One of the ways you'll be using these cases is as dictionary keys. This books cache below represents all of our books, but they're not grouped by section. So let's update this sorted books array and make it a dictionary property with a section for a key and a book array for the value. Then instead of just forwarding along the books cache, we'll take all of the books in there and split them up based on their Read Me properties. There's actually a really handy dictionary initializer that can do that for us, dictionary, grouping, by. This groups the books based on their Read Me property. A book's Read Me property is a boolean, so that line of code creates a dictionary with bool keys. If the key is true, then the value is an array of the Read Me books. The value for the false key is all of the finished books. But as this error tells us, we're looking for sections as keys, not bools. We can use another dictionary initializer to take the grouped books and effectively transform their keys into the type we want. This one takes an array of tools that will represent key value pairs. We can turn our grouped books into that tuple array with map. If the existing key is true, we want to use Read Me, and if it's false, we want to use finished. There's no changes to the value. So now, all books with Read Me such true will be assigned to the Read Me section, and the rest will be assigned to the value for the finished section. With our library data all sorted into sections, we can move on to creating section UI. We're going to be using some images for section header background, so we need to import those. Open up the asset catalog. And let's drag the BookTexture.imageset from the resources folder for this episode to import it. That will give us a version for light and dark appearances. Now over in ContentView.swift, near the bottom, add a section view. And start a body property. This view will only be used in this file so you can make it private. That's just a way for you to mark the code that has no reason to be used outside of the file you wrote it in. And that's true of book row as well. Private is a good organizational tactic. Generally, it's good practice to keep your code private until you need other files to use it. But you can't do this with the preview struct. For undocumented reasons, private previews just don't work. Moving on. A section view is going to require a section. And like most everything else in your project, it will need a library environment object too. We'll only render the section if the library has any sorted books for it. When there are books, we'll use SwiftUI section view. And because we've got our own section type, you'll need to disambiguate using the framework name, SwiftUI. For the content, instantiate a book row for each book. The header for this section is going to be based on the book texture image you just imported. We'll want a for each view for the sections too, so head up to the for each in the content view struct. And get rid of it. The for each we want instead will be based on all section cases. This is why we made section case iterable earlier. Case iterable elements can be identified using self. And the view we need for each case is a section view. There's a header! But let's make it look a little nicer. Back down in the section view, resize it to fit. And to get it to fill up the whole width of the screen, you'll need the list row insets modifier. By default, that's nil. You need a value instead, but it can use whatever the default initializer provides. The last thing we'll need for the section view is text view. To set up the title for it, create a computed string property, switch on section. And let the fix it fill in the cases. Then return whatever you'd like to see for each section. I like exclamation points. Now embed the image in a ZStack. But move the insets down for them to keep taking effect. This does look a little wrong in the preview, but it will correct itself when we run the app. A ZStack is a way for you to put views in front of or behind each other. To put your title in front, put it in a text at the bottom of the ZStack. To go with the look of the image, let's use the American Typewriter custom font. At 24 points. And give it a primary foreground color. And that's all for section view. The trouble is though, the books all stay in the Read Me section, even after un-bookmarking them. That's because with book being a reference type, the library doesn't consider a change to one of its properties to be a change to the book itself. So nothing notifies SwiftUI that it should update the content view. There is a way we can manually tell SwiftUI that the library is different, and it should update views accordingly. Head over to Library.swift. And we'll write a short method just above add new book called sort books. And in the body, say objectWillChange.send. This is actually how observable objects work behind the scenes. Any property you wrap with published invisibly sends from an observable objects, object will change publisher, when the property is about to change. So we're just doing that manually here. One more little thing. After we've called this, we'll have updated a book's properties, and thus the sorted books computer property will also be updated. It would be a good idea at this point to also update the ordering in books cache. Start with assigning to sorted books at the top of the method. But the cache is an array, not a dictionary. So we need to take the books for both sections and combine them, which is easy, by flat mapping the values in the dictionary. And just for organizational purposes, let's sort the dictionary first so that all of the finished books come after the Read Me books. Now we can use this method over in DetailView.swift. You might think a good place to do that kind of thing is in this bookmark button. So after Read Me is toggled, we'll call sort books. But if you do this from inside the button, every time the Read Me value is toggled, the source of data that this detail view is based on will be invalidated, and you'll be immediately bumped back to the list view. So that's probably not what we want. Instead, we can use a modifier on this entire VStack called onDisappear, and use sort books from there. This way you can make multiple changes to a book and it will be updated when you leave the detail screen. You can make it extra fancy by wrapping the method call in a with animation function. That will animate any view updates resulting from changes within this closure. So now back in content view, live preview and pick a book. Tap the bookmark button. And then when you go back to the list, you should see that change animate right away.
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.