Repository: beeware/toga

Issue:
ios cannot add widgets to main_window.content 
**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Try to run this app in ios:
```
import toga
class MyApp(toga.App):

    def startup(self):
        """
        Construct and show the Toga application.

        Usually, you would add your application to a main content box.
        We then create a main window (with a name matching the app), and
        show the main window.
        """
        self.state = GameState()
        self.main_window = toga.MainWindow(title=self.formal_name)
        box = toga.Box()
        label = toga.Label("Test1")
        box.add(label)
        self.main_window.content = box
        label2 = toga.Label("Test2")
        box.add(label2)
        self.main_window.show()
MyApp().main_loop()
```

**Expected behavior**
The application should open and shows the labels "Test1" and "Test2". Instead, you get a black screen. In the xcode debugger there is a log message on the "box.add(label2)" line about the label (test2) not having the attribute "viewport".

If you comment out box.add(label2), you correctly get a window that has "Test1".

On targets macos and android, the above test will correctly show "Test1 Test2"

**Environment:**
 - Operating System: macos
 - Python version: 3.8
 - Software versions:
   - Briefcase: 0.3.3
   - Toga: 0.3.0.dev23
   - IOS: iphone 11

This may be related to #225, as you will get a similar error if you try to rebind main_window.content to a different widget.
ios cannot add widgets to main_window.content 
**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Try to run this app in ios:
```
import toga
class MyApp(toga.App):

    def startup(self):
        """
        Construct and show the Toga application.

        Usually, you would add your application to a main content box.
        We then create a main window (with a name matching the app), and
        show the main window.
        """
        self.state = GameState()
        self.main_window = toga.MainWindow(title=self.formal_name)
        box = toga.Box()
        label = toga.Label("Test1")
        box.add(label)
        self.main_window.content = box
        label2 = toga.Label("Test2")
        box.add(label2)
        self.main_window.show()
MyApp().main_loop()
```

**Expected behavior**
The application should open and shows the labels "Test1" and "Test2". Instead, you get a black screen. In the xcode debugger there is a log message on the "box.add(label2)" line about the label (test2) not having the attribute "viewport".

If you comment out box.add(label2), you correctly get a window that has "Test1".

On targets macos and android, the above test will correctly show "Test1 Test2"

**Environment:**
 - Operating System: macos
 - Python version: 3.8
 - Software versions:
   - Briefcase: 0.3.3
   - Toga: 0.3.0.dev23
   - IOS: iphone 11

This may be related to #225, as you will get a similar error if you try to rebind main_window.content to a different widget.


Discussion:

Resolving patch:
diff --git a/src/iOS/toga_iOS/widgets/base.py b/src/iOS/toga_iOS/widgets/base.py
--- a/src/iOS/toga_iOS/widgets/base.py
+++ b/src/iOS/toga_iOS/widgets/base.py
@@ -6,10 +6,12 @@ def __init__(self, interface):
         self.interface = interface
         self.interface._impl = self
         self._container = None
+        self._viewport = None
         self.constraints = None
         self.native = None
         self.create()
         self.interface.style.reapply()
+        self.set_enabled(self.interface.enabled)
 
     def set_app(self, app):
         pass
@@ -23,15 +25,35 @@ def container(self):
 
     @container.setter
     def container(self, container):
-        self._container = container
-        if self.constraints:
+        if self.container:
+            if container:
+                raise RuntimeError('Already have a container')
+            else:
+                # existing container should be removed
+                self.constraints = None
+                self._container = None
+                self.native.removeFromSuperview()
+        elif container:
+            # setting container
+            self._container = container
             self._container.native.addSubview(self.native)
+            if not self.constraints:
+                self.add_constraints()
             self.constraints.container = container
 
         for child in self.interface.children:
             child._impl.container = container
+
         self.rehint()
 
+    @property
+    def viewport(self):
+        return self._viewport
+
+    @viewport.setter
+    def viewport(self, viewport):
+        self._viewport = viewport
+
     def set_enabled(self, value):
         self.native.enabled = self.interface.enabled
 
@@ -41,13 +63,13 @@ def focus(self):
     # APPLICATOR
 
     def set_bounds(self, x, y, width, height):
+        offset_y = 0
         if self.container:
-            viewport = self.container.viewport
-        else:
-            viewport = self.viewport
-
+            offset_y = self.container.viewport.statusbar_height
+        elif self.viewport:
+            offset_y = self.viewport.statusbar_height
         self.constraints.update(
-            x, y + viewport.statusbar_height,
+            x, y + offset_y,
             width, height
         )
 
@@ -55,8 +77,8 @@ def set_alignment(self, alignment):
         pass
 
     def set_hidden(self, hidden):
-        if self._container:
-            for view in self._container._impl.subviews:
+        if self.container:
+            for view in self.container._impl.subviews:
                 if view._impl:
                     view.setHidden(hidden)
 
@@ -75,10 +97,16 @@ def set_background_color(self, color):
     # INTERFACE
 
     def add_child(self, child):
-        if self.container:
-            child.viewport = self.root.viewport
+
+        if self.viewport:
+            # we are the the top level UIView
+            child.container = self
+        else:
             child.container = self.container
 
+    def remove_child(self, child):
+        child.container = None
+
     def add_constraints(self):
         self.native.translatesAutoresizingMaskIntoConstraints = False
         self.constraints = Constraints(self)


Test patch: