I've got an app presenting some messages. I'm trying to add a contextMenu on those messages, so holding on one of them presents a larger version of the avatar and options such as showing the user's profile. I'm using .contextMenu {} preview: {}
for this. The previews are giving me trouble, though. I can't seem to get the size to match the content.
Take, for eample, this:
The content is very short here. That should make the text view smaller, which should bubble up to the preview. But it doesn't happen.
With a medium length string, it seems to match, but this is probably just conincidence:
When I do a longer string, it fails to expand and the text is truncated:
I'm not sure what's going on here. At first, I thought there must be a missing relationship between the size of the view I'm providing and its parent was missing, but the behaviour with the small content seems to show there's some relationship there.
(I'm aware my view isn't perfect, in that it wouldn't currently allow text larger than the photo. I'm going to solve that after, if I get this working… unless that's the problem, but I've fixed that temporarily and it hasn't helped. It made the code more complicated, so I've left it out here.)
How do I get the preview size to match my content?
Full project (just this code, the @main app, and some resources) is on github here: https://github.com/tewha/PreviewConfusion/ if you want to poke at it.
But the view code is below:
import SwiftUI
struct Message: Identifiable {
var id: UUID
var title: String
let text: String
init(_ title: String, text: String) {
self.id = UUID()
self.title = title
self.text = text
}
}
struct ContentView: View {
@State var presenting: Bool = false
let messages = [
Message("Short", text: "Is this thing even on?"),
Message("Medium", text: "Can you hear me? I can’t hear you. Give me a minute to set up my headphones."),
Message("Long", text: "Can you hear me? I can’t hear you. Give me a minute to set up my headphones. I still can't hear you.")
]
var body: some View {
VStack(spacing: 10) {
Text("Hold down on text to show context menu.")
ForEach(messages) { message in
Button {
presenting = true
} label: {
Text(message.title)
}
// Relevant code here (I think).
.contextMenu {
Button {
// nothing to do
} label: {
Label("View Profile", systemImage: "person.circle")
}
} preview: {
// The preview view.
HStack(spacing: 10) {
Image("Person1")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 120, maxWidth: 120)
VStack {
HStack {
Text("“\(message.text)”")
Spacer()
}
HStack {
Spacer()
Text(" – Person1").italic()
}
}
}
.padding(5)
.background(Color("Person1"))
}
}
}
.padding()
.alert("Hold down instead of tapping.", isPresented: $presenting) {
Button("OK", role: .cancel) {
// alert closes automatically
}
}
}
}
My guess is that the preview is applying (the equivalent of) .fixedSize
, before then scaling the content to fit. This means, it is using the ideal size of the content to determine the space needed.
The ideal size of the picture will be its natural size. So from your screenshots, I am guessing that your picture has a natural size that is larger than the size it is being shown at.
The ideal size of the text is determined with the complete text on a single line. If the text is long, this results in the height being scaled down when the width is reduced to fit.
I found that it helps to apply a sensible idealwidth
to the content. For the image, it is also important to apply a maxWidth
.
.contextMenu {
// ...
} preview: {
HStack(spacing: 10) {
Image("Person1")
.resizable()
.scaledToFit()
.frame(idealWidth: 100, maxWidth: 100)
VStack(alignment: .leading) {
Text("“\(message.text)”")
.frame(idealWidth: 250)
Text(" – Person1").italic()
.frame(maxWidth: .infinity, alignment: .trailing)
}
}
.padding(10)
.background(Color("Person1"))
}
You may find that very long text still gets truncated eventually, probably when the height of the preview reaches a threshold. But it is possible to show quite a lot: