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 https://unsplash.com 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:
Book
object, we must first create a model context.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)
.modelContainer(container)
} 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) {
Image(book.genre)
.resizable()
.scaledToFit()
Text(book.genre.uppercased())
.font(.caption)
.fontWeight(.black)
.padding(8)
.foregroundStyle(.white)
.background(.black.opacity(0.75))
.clipShape(.capsule)
.offset(x: -5, y: -5)
}
}
.navigationTitle(book.title)
.navigationBarTitleDisplayMode(.inline)
.scrollBounceBehavior(.basedOnSize)
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
:
Text(book.author)
.font(.title)
.foregroundStyle(.secondary)
Text(book.review)
.padding()
RatingView(rating: .constant(book.rating))
.font(.largeTitle)
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.
SPONSORED Alex is the iOS & Mac developer’s ultimate AI assistant. It integrates with Xcode, offering a best-in-class Swift coding agent. Generate modern SwiftUI from images. Fast-apply suggestions from Claude 3.5 Sonnet, o3-mini, and DeepSeek R1. Autofix Swift 6 errors and warnings. And so much more. Start your 7-day free trial today!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.