iosswiftxcodeswiftui

How to avoid clipping of .popover content close to bottom screen edge?


I would like use .popover with .presentationCompactAdaptation(.popover) in an iPhone iOS 16+ app. This shows the popover not as sheet but as real popover not only on iPad but on iPhone as well.

While this works fine in general, the popover is not positioned correctly when coming close to the bottom screen edge.

In the following example the popover is positioned correctly to the top and side screen edges (buttons 1-3). Button 5 at the very bottom shows the popover above the button. However, the popover of button 4 is not shown above the button (although there is enough free space) but below the button. Since there is not enough space, the content is clipped.

This is quite strange, isn't it? Is this some bug or intended behavior? Can this be fixed?

enter image description here

struct PopoverTest: View {
    var body: some View {
        VStack {
            HStack {
                PopoverButton(text: "Button 1")
                Spacer()
            }
            
            Spacer()
            
            HStack {
                Spacer()
                PopoverButton(text: "Button 2")
            }
            
            Spacer()
            
            HStack {
                Spacer(minLength: 270)
                PopoverButton(text: "Button 3")
                Spacer()
            }
            
            PopoverButton(text: "Button 4")
                .padding(.top, 300)
            
            Spacer()
            
            PopoverButton(text: "Button 5")
        }
    }
}


struct PopoverButton: View {
    @State private var isPresented = false
    var text: String
    
    var body: some View {
        Button(action: { isPresented = true }) {
            Text(text)
        }
        .popover(isPresented: $isPresented) {
            VStack {
                Text("Some Line of Text 1")
                Text("Some Line of Text 2")
                Text("Some Line of Text 3")
                Text("Some Line of Text 4")
                Text("Some Line of Text 5")
                Text("Some Line of Text 6")
                Text("Some Line of Text 7")
                Text("Some Line of Text 8")
                Text("Some Line of Text 9")
            }
            .presentationCompactAdaptation(.popover)
        }
    }
}

Solution

  • Thanks so the hint of @DonMag and after some more digging, I was able to find a solution.

    It seems that this is a <s>bug</s> intended behaviour in iOS 18+. The arrowEdge is not use as a hint anymore (place the arrow here, if there is sufficient space) but as constraint (place the arrow here and clip content if necessary).

    This can be solved with the following wrapper which uses different calls for iOS18 or below:

    extension View {
        @ViewBuilder func autoEdgePopover<Content: View>(
            isPresented: Binding<Bool>,
            attachmentAnchor: PopoverAttachmentAnchor = .rect(.bounds),
            @ViewBuilder content: @escaping () -> Content
        ) -> some View {
            if #available(iOS 18, *) {
                self
                    .popover(isPresented: isPresented, attachmentAnchor: attachmentAnchor, content: content)
            } else {
                self
                    .popover(isPresented: isPresented, attachmentAnchor: attachmentAnchor, arrowEdge: .top, content: content)
            }
        }
    }