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”. This 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 Core Data involved things are messier: creating a new book also means having a managed object context to create it inside.
To fix this, we can update our preview code to create a temporary managed object context, then use that to create our book. Once that’s done we can pass in some example data to make our preview look good, then use the test book to create a detail view preview.
Creating a managed object context means we need to start by importing Core Data. Add this line near the top of DetailView.swift, next to the existing import
:
import CoreData
As for the previews code itself, replace whatever you have now with this:
struct DetailView_Previews: PreviewProvider {
static let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
static var previews: some View {
let book = Book(context: moc)
book.title = "Test book"
book.author = "Test author"
book.genre = "Fantasy"
book.rating = 4
book.review = "This was a great book; I really enjoyed it."
return NavigationView {
DetailView(book: book)
}
}
}
As you can see, creating a managed object context involves telling the system what concurrency type we want to use. This is another way of saying “which thread do you plan to access your data using?” For our example, using the main queue – that’s the one the app was launched using – is perfectly fine.
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 ?? "Fantasy")
.resizable()
.scaledToFit()
Text(book.genre?.uppercased() ?? "FANTASY")
.font(.caption)
.fontWeight(.black)
.padding(8)
.foregroundColor(.white)
.background(.black.opacity(0.75))
.clipShape(Capsule())
.offset(x: -5, y: -5)
}
}
.navigationTitle(book.title ?? "Unknown Book")
.navigationBarTitleDisplayMode(.inline)
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 ?? "Unknown author")
.font(.title)
.foregroundColor(.secondary)
Text(book.review ?? "No review")
.padding()
RatingView(rating: .constant(Int(book.rating)))
.font(.largeTitle)
That completes DetailView
, so we can head back to ContentView.swift to change the navigation link so it points to the correct thing:
NavigationLink {
DetailView(book: book)
} label: {
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 Thorough mobile testing hasn’t been efficient testing. With Waldo Sessions, it can be! Test early, test often, test directly in your browser and share the replay with your team.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.