Forum Archive

330018: popover view now contains arrow in its frame

cvp

I often use popover presentation for popup menu. I have always computed the view frame to contain my TextView. But now, with the 330018 beta using iOS 13, the little arrow of the popover is included in the view frame, and that gives erroneous windows.
I don't know if it will be a job for @omz or for us, but it could be a big work.

Issue open https://github.com/omz/Pythonista-Issues/issues/625

shinyformica

Just wanted to poke this issue, as I encountered it as well when updating a project to Pythonista 3.3 which makes extensive use of popover view presentation.

For now I have to special-case the pythonista version, but it would be nice not to have to do that. Actually, after checking on a device which is Pythonista 3.3, but iOS 12, the issue is not Pythonista version, but iOS version-specific, as it only shows up under iOS 13+.

JonB

@cvp seems to be a known ios13 "feature"

https://stackoverflow.com/questions/57988889/ios-13-uipopoverpresentationcontroller-sourceview-content-visible-in-the-arrow

There are a few ideas in there -- involving safeArea anchors, etc. Something to play with while in social isolation ;)

cvp

@JonB said:

seems to be a known ios13 "feature"

Yes, I know it but it could be ennoying for old scripts. My new ones take this feature in account.

But, anyway, thanks to remember me. I've closed the Pythonista issue.

mikael

@cvp, if you solved it with safe area, could you share the code?

cvp

@mikael I didn't say that I solved it in a general way but I take it in account by positioning my ui objects more at right if arrow at left or more at bottom if arrow at top.

I set my first ui object at dd,dd

            dd = 20 # distance between end of arrow and first ui element 

mikael

@cvp, I do not have an iPad to test with right now, but I would try something like this:

popup = ui.View()
popup_content.frame = (0,0,<desired content size here>)
popup.add_subview(popup_content)

popup.present(style="popover",
    popover_location=popup_pos,
    hide_title_bar=True)

safe = popup.objc_instance.safeAreaInsets()
popup.width = popup_content.width + safe.left + safe.right
popup.height = popup_content.height + safe.top + safe.bottom
popup_content.x = safe.left
popup_content.y = safe.top
cvp

@mikael sorry, but what is popup_content as object?

And you can't change popup width and height after it has been presented

See the differences of both codes

mikael

@cvp, thanks for trying it out, and thanks for sharing the pictures – they make it much easier to discuss the results. What happens in the second version if you move the safe area retrieval and popup_content.x and .y setting after the present?

cvp

@mikael obliged to copy instead of move the safe retrieval => 👍

import ui

popup = ui.View()
popup_content = ui.Label()
popup_content.border_width = 3
popup_content.frame = (0,0,200,200)
popup.add_subview(popup_content)

#popup.present(style="popover",popover_location=(500,250),hide_title_bar=True)

safe = popup.objc_instance.safeAreaInsets()
popup.width = popup_content.width + safe.left + safe.right
popup.height = popup_content.height + safe.top + safe.bottom
#popup_content.x = safe.left
#popup_content.y = safe.top

popup.present(style="popover",popover_location=(500,250),hide_title_bar=True)

safe = popup.objc_instance.safeAreaInsets()
popup_content.x = safe.left
popup_content.y = safe.top 

cvp

mikael

@cvp, you’re the man!

mikael

@cvp, here’s a ”minimalized” version. There is still a brief delay between the popup coming up and the content showing.

import ui


def present_popup(content_view, position, round_corners=True):
    popup = ui.View(
        width=content_view.width,
        height=content_view.height,
        hidden=True
    )
    popup.add_subview(content_view)

    popup.present(style="popover",
        popover_location=position,
        hide_title_bar=True)

    safe = content_view.objc_instance.safeAreaInsets()
    content_view.x = safe.left
    content_view.y = safe.top

    if round_corners:
        content_view.corner_radius = max(
            safe.left, safe.top, safe.right, safe.bottom)

    popup.hidden = False

    return popup


if __name__ == '__main__':

    popup_content = ui.Label(
        border_width=3,
        frame=(0,0,150,100),
    )

    popup = present_popup(popup_content, (100, 100))
cvp

@mikael said:

you’re the man!

I think YOU are the man. 👍

This is a team work, isn't it?

shinyformica

I did a little digging on this, to figure out how it is being solved by other developers.

First, though this would appear to many to be a bug, it actually puts the popover presentation view controller in line with how view controllers work in general. Since iOS 11+ Apple has provided a "safe area" concept which is supposed to be used by the view hierarchy to indicate what part of their content area is obscured. In the case of the popover view controller, the arrow is still part of the content area, but it obscures content.

@cvp, @mikael you are both the dudes!

I'm providing a somewhat more complex but general way below, which doesn't suffer from the delay between presentation of the popover and content. The trouble is that the safeAreaInsets() is always 0 before a view is displayed, so it can't be used to set the proper location of a view before it is visible. However, using the safeAreaLayoutGuide() and the constraint/anchor system, the content view can be made to size itself automatically to the safe area:

import ui


def present_popup(content_view, position):
    popup = ui.View(
        width=content_view.width,
        height=content_view.height,
    )
    popup.add_subview(content_view)

    content_view.objc_instance.translatesAutoresizingMaskIntoConstraints = False
    guide = popup.objc_instance.safeAreaLayoutGuide()
    anchor = content_view.objc_instance.leadingAnchor()
    anchor.constraintEqualToAnchor_(guide.leadingAnchor()).active = True
    anchor = content_view.objc_instance.trailingAnchor()
    anchor.constraintEqualToAnchor_(guide.trailingAnchor()).active = True
    anchor = content_view.objc_instance.topAnchor()
    anchor.constraintEqualToAnchor_(guide.topAnchor()).active = True
    anchor = content_view.objc_instance.bottomAnchor()
    anchor.constraintEqualToAnchor_(guide.bottomAnchor()).active = True

    popup.present(style="popover",
        popover_location=position,
        hide_title_bar=True)

    return popup


if __name__ == '__main__':

    popup_content = ui.Label(
        text="Hello!",
        border_width=3,
        frame=(0,0,150,100),
    )

    popup = present_popup(popup_content, (100, 100))
mikael

@shinyformica, I guess you are the superdude then?

shinyformica

@mikael nah, just a regular ol' dude, writin' Python for the man.

stephen

@mikael i know its been a few but the subject was left open with the a workaround that didnt really fiit the needs of @Samer at this post. would safeAreaLayoutGuide() and Anchors being used here fix the the problem with safe area we had?