Reputation: 1
I am facing a problem: I need to create a popover with an error message for a text field. I am using .popover in the below method:
.popover(
isPresented: $isValid,
attachmentAnchor: .point(.center),
arrowEdge: .bottom
) {
Text("Some error text")
.presentationCompactAdaptation(.popover)
}
However, I am getting parasitic vertical padding at the top and bottom.
But if I use some kind of representation with exact dimensions, there are no additional insets like with Text.
.popover(
isPresented: $isValid,
attachmentAnchor: .point(.center),
arrowEdge: .bottom
) {
Rectangle().fill(.yellow)
.frame(width: 100, height: 100)
.presentationCompactAdaptation(.popover)
}
I observe the problem only with Text.
I found many solutions on stackoverflow involving bulk extensions with custom popover implementations.
But is there a simpler solution? I don't want to add large custom implementations to the project purely because of the two insets.
I've tried a lot of options (for example, I've tried using ScrollView for Text) trying to get rid of the vertical padding and make the popover wrap the Text to fit the content, but I haven't been able to.
If anyone knows what the issue is, please help me out.
P.S. target: iOS 16
Upvotes: 0
Views: 389
Reputation: 2093
You could go for a custom implementation like this, it's not that large actually. I've just built it myself a few seconds ago:
extension View {
@ViewBuilder
func customPopOver(isPresented: Bool, message: String, popoverOffset: CGFloat = -30, indicatorOffset: CGFloat = 14) -> some View {
self
.overlay {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(.background)
.overlay(alignment: .bottom) {
IndicatorShape()
.rotation(.degrees(180))
.frame(width: 20, height: 15)
.foregroundStyle(.background)
//.shadow(radius: 2)
.offset(y: indicatorOffset)
}
.overlay {
Text(message)
.presentationCompactAdaptation(.popover)
}
.shadow(radius: 10)
.offset(y: popoverOffset)
.frame(minWidth: 160, maxWidth: .infinity)
.opacity(isPresented ? 1 : 0)
.scaleEffect(isPresented ? 1 : 0)
.animation(.spring(duration: 0.3), value: isPresented)
}
}
}
struct IndicatorShape: Shape {
func path(in rect: CGRect) -> Path {
return Path { path in
let width = rect.width
let height = rect.height
path.move(to: CGPoint(x: width / 2, y: 0))
path.addLine(to: CGPoint(x: 0, y: height))
path.addLine(to: CGPoint(x: width, y: height))
}
}
}
You can add more flexibilty to the extension adding more parameters like colors, witdth, etc. You only need the IndicatorShape if you want the little triangle at the bottom of the popover. Its use is pretty straightforward:
struct ContentView: View {
@State var isValid = false
var body: some View {
VStack {
Button("Show popover") {
isValid.toggle()
}
.customPopOver(isPresented: isValid, message: "Some text")
}
}
}
I hope to have helped you. Let me know your thought about this!
Upvotes: 0