LAST CHANCE: Save 50% on all my Swift books and bundles! >>

Showing book details

Paul Hudson    @twostraws   

When the user taps a book in ContentView we’re going to present a detail view with some more information – the genre of the book, their brief review, and more. We’re also going to reuse our new RatingView, and even customize it so you can see just how flexible SwiftUI is.

To make this screen more interesting, we’re going to add some artwork that represents each category in our app. I’ve picked out some artwork already from Unsplash, and placed it into the project11-files folder for this book – if you haven’t downloaded them, please do so now and then drag them into your asset catalog.

Unsplash has a license that allows us to use pictures commercially or non-commercially, with or without attribution, although attribution is appreciated. The pictures I’ve added are by Ryan Wallace, Eugene Triguba, Jamie Street, Alvaro Serrano, Joao Silas, David Dilbert, and Casey Horner – you can get the originals from if you want.

Next, create a new SwiftUI view called “DetailView”, then give it an import for SwiftData. This new view only needs one property, which is the book it should show, so please add that now:

let book: Book

Even just having that property is enough to break the preview code at the bottom of DetailView.swift. Previously this was easy to fix because we just sent in an example object, but with SwiftData involved things are messier: creating a new book also means having a view context to create it inside.

This is the first thing that's actually tricky in SwiftData; we need to get things exactly right in order for it to work:

  1. In order to create a sample Book object, we must first create a model context.
  2. That model context comes from creating a model container.
  3. If we create a model container, we don't want it to actually store anything, so we can create a custom configuration that tells SwiftData to store its information in memory only. That means everything is temporary.

I know that sounds a lot, but in practice it's only a few lines of code – we need to make our model container by hand, and do so using a new type called ModelConfiguration that lets us request temporary in-memory storage. Once we have that, we can create a Book object as normal, then send it into DetailView along with the model container itself.

Replace your current preview code with this:

#Preview {
    do {
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        let container = try ModelContainer(for: Book.self, configurations: config)
        let example = Book(title: "Test Book", author: "Test Author", genre: "Fantasy", review: "This was a great book; I really enjoyed it.", rating: 4)

        return DetailView(book: example)
    } catch {
        return Text("Failed to create preview: \(error.localizedDescription)")

Yes, creating the Book instance doesn't actually mention the model container or configuration anywhere, but it does matter: trying to create a SwiftData model object without a container around is likely to make your code crash.

With that done we can turn our attention to more interesting problems, namely designing the view itself. To start with, we’re going to place the category image and genre inside a ZStack, so we can put one on top of the other nicely. I’ve picked out some styling that I think looks good, but you’re welcome to experiment with the styling all you want – the only thing I’d recommend you definitely keep is the ScrollView, which ensures our review will fit fully onto the screen no matter how long it is, what device the user has, or whether they have adjusted their font sizes or not.

Replace the current body property with this:

ScrollView {
    ZStack(alignment: .bottomTrailing) {

            .offset(x: -5, y: -5)

That places the genre name in the bottom-right corner of the ZStack, with a background color, bold font, and a little padding to help it stand out.

Below that ZStack we’re going to add the author, review, and rating. We don’t want users to be able to adjust the rating here, so instead we can use another constant binding to turn this into a simple read-only view. Even better, because we used SF Symbols to create the rating image, we can scale them up seamlessly with a simple font() modifier, to make better use of all the space we have.

So, add these views directly below the previous ZStack:



RatingView(rating: .constant(book.rating))

That completes DetailView, so we can head back to ContentView.swift to add a navigation destination to the List view:

.navigationDestination(for: Book.self) { book in
    DetailView(book: book)

Now run the app again, because you should be able to tap any of the books you’ve entered to show them in our new detail view.

Hacking with Swift is sponsored by Essential Developer.

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until July 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.6/5

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.