Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions iOS/src/toga_iOS/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def update(self, x, y, width, height):
# f"UPDATE CONSTRAINTS {self.widget} in {self.container} "
# f"{width}x{height}@{x},{y}"
# )
y += self.container.top_offset
self.left_constraint.constant = x
self.top_constraint.constant = y

Expand Down
53 changes: 48 additions & 5 deletions iOS/src/toga_iOS/container.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from rubicon.objc import objc_method, objc_property, send_super

import toga

from .libs import (
UIApplication,
UINavigationController,
Expand All @@ -6,6 +10,9 @@
UIViewController,
)

toga.Widget.DEBUG_LAYOUT_ENABLED = True


#######################################################################################
# Implementation notes:
#
Expand All @@ -15,8 +22,19 @@
#######################################################################################


class TogaContainerView(UIView):
container = objc_property(object, weak=True)

@objc_method
def safeAreaInsetsDidChange(self):
send_super(__class__, self, "safeAreaInsetsDidChange")
if self.container.content:
self.container.content.interface.refresh()
self.container.refreshed()


class BaseContainer:
def __init__(self, content=None, on_refresh=None):
def __init__(self, content=None, on_refresh=None, safe_bottom=False):
"""A base class for iOS containers.

:param content: The widget impl that is the container's initial content.
Expand All @@ -25,6 +43,7 @@ def __init__(self, content=None, on_refresh=None):
"""
self._content = content
self.on_refresh = on_refresh
self._safe_bottom = safe_bottom

@property
def content(self):
Expand Down Expand Up @@ -52,7 +71,9 @@ def refreshed(self):


class Container(BaseContainer):
def __init__(self, content=None, layout_native=None, on_refresh=None):
def __init__(
self, content=None, layout_native=None, on_refresh=None, safe_bottom=False
):
"""
:param content: The widget impl that is the container's initial content.
:param layout_native: The native widget that should be used to provide size
Expand All @@ -62,9 +83,14 @@ def __init__(self, content=None, layout_native=None, on_refresh=None):
the size can be different.
:param on_refresh: The callback to be notified when this container's layout is
refreshed.
:param safe_bottom: Whether the container should not extend into bottom
safe area insets.
"""
super().__init__(content=content, on_refresh=on_refresh)
self.native = UIView.alloc().init()
super().__init__(
content=content, on_refresh=on_refresh, safe_bottom=safe_bottom
)
self.native = TogaContainerView.alloc().init()
self.native.container = self
self.native.translatesAutoresizingMaskIntoConstraints = True
self.native.autoresizingMask = (
UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
Expand All @@ -83,7 +109,14 @@ def width(self):

@property
def height(self):
return self.layout_native.bounds.size.height - self.top_offset
if self._safe_bottom:
return (
self.layout_native.bounds.size.height
- self.top_offset
- self.layout_native.safeAreaInsets.bottom
)
else:
return self.layout_native.bounds.size.height - self.top_offset

@property
def top_offset(self):
Expand All @@ -96,6 +129,7 @@ def __init__(
content=None,
layout_native=None,
on_refresh=None,
safe_bottom=False,
):
"""
:param content: The widget impl that is the container's initial content.
Expand All @@ -106,11 +140,14 @@ def __init__(
rendered, the source of the size can be different.
:param on_refresh: The callback to be notified when this container's layout is
refreshed.
:param safe_bottom: Whether the container should not extend into bottom
safe area insets.
"""
super().__init__(
content=content,
layout_native=layout_native,
on_refresh=on_refresh,
safe_bottom=safe_bottom,
)

# Construct a ViewController that provides a navigation bar, and
Expand All @@ -128,6 +165,7 @@ def __init__(
content=None,
layout_native=None,
on_refresh=None,
safe_bottom=False,
):
"""A bare content container.

Expand All @@ -141,11 +179,14 @@ def __init__(
rendered, the source of the size can be different.
:param on_refresh: The callback to be notified when this container's layout is
refreshed.
:param safe_bottom: Whether the container should not extend into bottom
safe area insets.
"""
super().__init__(
content=content,
layout_native=layout_native,
on_refresh=on_refresh,
safe_bottom=safe_bottom,
)

# Construct a UIViewController to hold the root content
Expand Down Expand Up @@ -174,6 +215,7 @@ def __init__(
content=None,
layout_native=None,
on_refresh=None,
safe_bottom=False,
):
"""A top level container that provides a navigation/title bar.

Expand All @@ -190,6 +232,7 @@ def __init__(
content=content,
layout_native=layout_native,
on_refresh=on_refresh,
safe_bottom=safe_bottom,
)

# Construct a NavigationController that provides a navigation bar, and
Expand Down
2 changes: 1 addition & 1 deletion iOS/src/toga_iOS/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def set_tab_index(self, tab_index):

def set_bounds(self, x, y, width, height):
# print("SET BOUNDS", self, x, y, width, height, self.container.top_offset)
self.constraints.update(x, y + self.container.top_offset, width, height)
self.constraints.update(x, y, width, height)

def set_text_align(self, alignment):
pass
Expand Down
18 changes: 14 additions & 4 deletions iOS/src/toga_iOS/widgets/optioncontainer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import platform

from rubicon.objc import SEL, objc_method, objc_property
from travertino.size import at_least

Expand Down Expand Up @@ -53,8 +55,9 @@ def create(self):
self.native_controller.impl = self
self.native_controller.delegate = self.native_controller

# Make the tab bar non-translucent, so you can actually see it.
self.native_controller.tabBar.setTranslucent(False)
if int(platform.release().split(".")[0]) < 26: # pragma: no branch
# Make the tab bar non-translucent, so you can actually see it.
self.native_controller.tabBar.setTranslucent(False)

# The native widget representing the container is the view of the native
# controller. This doesn't change once it's created, so we can cache it.
Expand All @@ -66,7 +69,12 @@ def create(self):
self.add_constraints()

def set_bounds(self, x, y, width, height):
super().set_bounds(x, y, width, height)
if y + height == self.container.height and self.container._safe_bottom:
super().set_bounds(
x, y, width, height + self.container.layout_native.safeAreaInsets.bottom
)
else:
super().set_bounds(x, y, width, height)

# Setting the bounds changes the constraints, but that doesn't mean
# the constraints have been fully applied. Schedule a refresh to be done
Expand All @@ -81,7 +89,9 @@ def content_refreshed(self, container):

def add_option(self, index, text, widget, icon=None):
# Create the container for the widget
sub_container = ControlledContainer(on_refresh=self.content_refreshed)
sub_container = ControlledContainer(
on_refresh=self.content_refreshed, safe_bottom=True
)
sub_container.content = widget
sub_container.enabled = True
self.sub_containers.insert(index, sub_container)
Expand Down
16 changes: 14 additions & 2 deletions iOS/src/toga_iOS/widgets/scrollcontainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ def refreshContent(self):


class ScrollContainer(Widget):
@property
def verticalInsets(self):
return self.native.safeAreaInsets.bottom

def create(self):
self.native = TogaScrollView.alloc().init()
self.native.interface = self.interface
Expand All @@ -54,7 +58,12 @@ def set_content(self, widget):
self.document_container.content = widget

def set_bounds(self, x, y, width, height):
super().set_bounds(x, y, width, height)
if y + height == self.container.height and self.container._safe_bottom:
super().set_bounds(
x, y, width, height + self.container.layout_native.safeAreaInsets.bottom
)
else:
super().set_bounds(x, y, width, height)

# Setting the bounds changes the constraints, but that doesn't mean
# the constraints have been fully applied. Schedule a refresh to be done
Expand All @@ -71,7 +80,10 @@ def content_refreshed(self, container):
width = max(self.interface.content.layout.width, width)

if self.interface.vertical:
height = max(self.interface.content.layout.height, height)
height = max(
self.interface.content.layout.height,
height + 0.001 * self._allow_vertical,
)

self.native.contentSize = NSMakeSize(width, height)

Expand Down
Loading