diff --git a/android/tests_backend/window.py b/android/tests_backend/window.py index cd974b5cde..6a8806aafd 100644 --- a/android/tests_backend/window.py +++ b/android/tests_backend/window.py @@ -1,3 +1,4 @@ +from android.content import Context from androidx.appcompat import R as appcompat_R from .dialogs import DialogsMixin @@ -20,6 +21,16 @@ def content_size(self): self.root_view.getHeight() / self.scale_factor, ) + @property + def top_bar_height(self): + # Android doesn't require explicit allowances for the top bar in content layout; + # the size of the top bar is the difference between the screen and the root + # window content size. + context = self.app._impl.native.getApplicationContext() + window_manager = context.getSystemService(Context.WINDOW_SERVICE) + display = window_manager.getDefaultDisplay() + return (display.getHeight() - self.root_view.getHeight()) / self.scale_factor + def _native_menu(self): return self.native.findViewById(appcompat_R.id.action_bar).getMenu() diff --git a/changes/2836.bugfix.rst b/changes/2836.bugfix.rst new file mode 100644 index 0000000000..1a7ee5881e --- /dev/null +++ b/changes/2836.bugfix.rst @@ -0,0 +1 @@ +iOS apps now correctly account for the size of the navigation bar when laying out app content. diff --git a/iOS/src/toga_iOS/container.py b/iOS/src/toga_iOS/container.py index 002143bba6..f9b452cf26 100644 --- a/iOS/src/toga_iOS/container.py +++ b/iOS/src/toga_iOS/container.py @@ -78,7 +78,7 @@ def width(self): @property def height(self): - return self.layout_native.bounds.size.height + return self.layout_native.bounds.size.height - self.top_offset @property def top_offset(self): @@ -149,11 +149,6 @@ def __init__( # Set the controller's view to be the root content widget self.controller.view = self.native - # The testbed app won't instantiate a simple app, so we can't test these properties - @property - def height(self): # pragma: no cover - return self.layout_native.bounds.size.height - self.top_offset - # The testbed app won't instantiate a simple app, so we can't test these properties @property def top_offset(self): # pragma: no cover diff --git a/iOS/tests_backend/window.py b/iOS/tests_backend/window.py index cab72b21cc..00fd816d8f 100644 --- a/iOS/tests_backend/window.py +++ b/iOS/tests_backend/window.py @@ -30,5 +30,12 @@ def content_size(self): ), ) + @property + def top_bar_height(self): + return ( + UIApplication.sharedApplication.statusBarFrame.size.height + + self.native.rootViewController.navigationBar.frame.size.height + ) + def has_toolbar(self): pytest.skip("Toolbars not implemented on iOS") diff --git a/testbed/tests/app/test_mobile.py b/testbed/tests/app/test_mobile.py index e4f0c004b4..d019c70f88 100644 --- a/testbed/tests/app/test_mobile.py +++ b/testbed/tests/app/test_mobile.py @@ -1,6 +1,8 @@ import pytest import toga +from toga.colors import REBECCAPURPLE +from toga.style import Pack #################################################################################### # Mobile platform tests @@ -9,6 +11,22 @@ pytest.skip("Test is specific to desktop platforms", allow_module_level=True) +async def test_content_size(app, main_window, main_window_probe): + """The content size doesn't spill outsize the viewable area.""" + + box = toga.Box(style=Pack(background_color=REBECCAPURPLE)) + main_window.content = box + + await main_window_probe.redraw("Content is a box") + + # The overall layout has both the box, plus the top bar. + assert main_window.screen.size.height >= ( + box.layout.content_height + main_window_probe.top_bar_height + ) + # The box is the same width as the screen. + assert main_window.screen.size.width == box.layout.content_width + + async def test_show_hide_cursor(app): """The app cursor methods can be invoked""" # Invoke the methods to verify the endpoints exist. However, they're no-ops,