Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set default background color for widgets on iOS #3009

Merged
merged 13 commits into from
Jan 9, 2025

Conversation

proneon267
Copy link
Contributor

@proneon267 proneon267 commented Dec 2, 2024

Refs #767

This PR is broken out of #2484.

This PR sets default background color as TRANSPARENT for Box, Canvas, ImageView, Label, ProgressBar, ScrollContainer and Slider widgets on iOS.

Context for tests:

Currently all the 3 background color tests on the testbed cannot be enabled for all widgets, since there are problems associated with the widget background color on some platforms like on Android and Winforms. If we enable all 3 background color tests on every widget, then the testbed would pass on iOS, but would fail on other platforms. At the same time, we cannot correct their behavior to make them pass the tests as fixes for those platforms exist on separate PRs.
For example, If we enable all the 3 tests: test_background_color, test_background_color_reset, test_background_transparent on the Box widget then the tests would pass on iOS, but would fail on Winforms, since the fix for winforms exists on a separate PR.

Since behavioral changes need tests that confirms their behavior. So, to prevent a deadlock situation that would prevent this PR from being merged, I want to propose that we create a new issue to keep track of the widgets on which background color tests are not enabled. Since all 3 tests are enabled on most widgets so the list would be small. Afterwards, when all 3 dependent PRs are merged, we can enable all the 3 background color tests on all the widgets.

Issue for tracking background color tests on widgets: #3015

PR Checklist:

  • All new features have been tested
  • All new features have been documented
  • I have read the CONTRIBUTING.md file
  • I will abide by the code of conduct

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's the start of some interesting cleanups here, but:

  1. This is all in the iOS backend, so we can lean into using iOS native colors. Trying to convert to Toga colors means we end up spending effort converting back and forth between the two formats for no real gain
  2. The changes to Button don't seem right. The existing implementation explicitly sets the background to None because that's the desired behavior. I will admit that I haven't run the testbed with these changes; but I spent a lot of time getting these settings right when they were originally implemented, so I'm starting from a point of scepticism that these changes are required for button.
  3. It's a little concerning that other than button, there's no changes to testbed tests. That means this change is either a no-op, or there are untested changes. If it's a no-op, we need to be clear why we're making this change.


# Set default background color
try:
# systemBackgroundColor() was introduced in iOS 13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer need this branch; as a result of the PEP 730 changes, iOS 13 is the minimum supported iOS version. The iOS platform docs should also be updated to reflect this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've removed that branch.

except AttributeError: # pragma: no cover
self.native.backgroundColor = UIColor.whiteColor
self.native.backgroundColor = (
native_color(self._default_background_color)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unnecessarily complex - doing a round trip from UIColor to toga.Color to UIColor just to get a (cached, and possibly stale) UIColor.systemBackgroundColor().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I've modified it to work with the native colors directly.

@@ -15,7 +15,7 @@
)
from travertino.size import at_least

from toga.colors import BLACK, TRANSPARENT, color
from toga.colors import BLACK, TRANSPARENT, color as named_color
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth noting that this is a little misleading - color here isn't just named colors - it's a toga color that will accept any valid string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed it to toga_color

self.native.backgroundColor = native_color(color)
super().set_background_color(
self._default_background_color if color in {None, TRANSPARENT} else color
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT, this is a change in behavior. backgroundColor=None is a required call in this instance, and this new implementation doesn't provide that as an option.

Copy link
Contributor Author

@proneon267 proneon267 Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've restored it. But it's worth noting that backgroundColor=None produces the same result as backgroundColor=UIColor.clearColor:

backgroundColor=None image
backgroundColor=UIColor.clearColor image

According to: https://developer.apple.com/documentation/uikit/uiview/1622591-backgroundcolor?language=objc:

Changes to this property can be animated. The default value is nil, which results in a transparent background color.

But according to: https://developer.apple.com/documentation/uikit/uibackgroundconfiguration/3600317-backgroundcolor

If the value is nil, the background color is the view’s tint color. Use clear for a transparent background with no color.

So, I have set the background color of widgets which should have transparent background by default to UIColor.clearColor. But for button, I've set the default background color to None.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok - I think that interpretation of "None = Transparent" is what I was remembering. Based on the examples you've presented here, it looks like you've preserved the historical behavior.

@proneon267
Copy link
Contributor Author

Here are the preview of the widgets that this PR changes:
Untitled-13(2)

Test Code:

"""
My first application
"""

import toga
from toga.colors import PINK
from toga.style import Pack
from toga.style.pack import COLUMN


class iOSBackground(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.content = toga.Box(
            style=Pack(
                direction=COLUMN,
                flex=1,
                padding=0,
                background_color=PINK,
            ),
            children=[
                toga.Box(style=Pack(flex=1.5)),
                toga.Button(text="Button"),
                toga.Box(style=Pack(flex=1.5)),
                toga.ImageView(toga.Image(self.paths.app / "resources/imageview.png")),
                toga.Box(style=Pack(flex=1.5)),
                toga.Label(text="Label"),
                toga.Box(style=Pack(flex=1.5)),
                toga.ProgressBar(max=100, value=50),
                toga.Box(style=Pack(flex=1.5)),
                toga.Selection(items=["Alice", "Bob", "Charlie"]),
                toga.Box(style=Pack(flex=1.5)),
                toga.Slider(range=(-5, 10), value=7),
                toga.Box(style=Pack(flex=1.5)),
                toga.NumberInput(value=8908),
                toga.Box(style=Pack(flex=1.5)),
                toga.MultilineTextInput(
                    value="Some text.\nIt can be multiple lines of text."
                ),
                toga.Box(style=Pack(flex=1.5)),
                toga.TextInput(value="Jane Developer"),
                toga.Box(style=Pack(flex=1.5)),
            ],
        )

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = self.content
        self.main_window.show()


def main():
    return iOSBackground()

@proneon267
Copy link
Contributor Author

Regarding enabling background color tests on all the widgets, I cannot enable background color tests on the testbed for all widgets, since there are problems associated with the widget background color on some platforms like on Android and Winforms. If I enable all 3 background color tests on every widget, then the testbed would pass on iOS, but would fail on other platforms. At the same time, I cannot correct their behavior to make them pass the tests as fixes for those platforms exist on separate PRs.
For example, If I enable all the 3 tests: test_background_color, test_background_color_reset, test_background_transparent on the Box widget then the tests would pass on iOS, but would fail on Winforms, since the fix for winforms exists on a separate PR.

I know you have told me that behavioral changes need tests that confirms their behavior. So, to prevent a deadlock situation that would prevent this PR from being merged, I want to propose that we create a new issue to keep track of the widgets on which background color tests are not enabled. Since, all 3 tests are enabled on most widgets so the list would be small. Afterwards, when all 3 dependent PRs are merged, we can enable all the 3 background color tests on all the widgets.

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're getting closer - and the sample project you've posted definitely suggests this PR is on the right track.

There's still some overly convoluted logic around the color reset (on button, in particular); I've flagged my concerns inline.

Regarding testbed testing - your explanation makes sense. From a process perspective, if this is something you've given thought to, it's helpful to include that context in the pull request description. You might think it's obvious given the history of the ticket - but a reviewer (read: me) is doing a lot of other things, and so the connection to "obvious" things might not always be so obvious.

def set_background_color_simple(self, value):
if value:
self.native.backgroundColor = native_color(value)
def set_background_color(self, color, is_native_color=False):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a good idea. The API either takes a native color, or it doesn't. And given that the only usage that I can see of is_native_color=True... uses native_color() on the invocation, it seems to be completely unnecessary.

Copy link
Contributor Author

@proneon267 proneon267 Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_background_color() will receive a toga color from the core interface, but on the backend since we are working directly with the native UIColor, so any invocation of set_background_color() from the backend requires is_native_color=True. This is why in the previous review, I was specifying _default_background_color in the form of toga color instead of the native UIColor.

But since now we are directly specfying the _default_background_color in the form of native UIColor, we need the additional indicator is_native_color=True to prevent any incorrect interpretation while assigning the background color.

You were right.

Comment on lines 96 to 98
default_background_color = getattr(self, "_default_background_color", None)
if default_background_color is None:
default_background_color = UIColor.systemBackgroundColor()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use _default_background_color as the default in the call to getattr?

Also - it feels like there's some ambiguity here about what "None" means in the context of default_background_color - is it "transparent", or "system background"? Does it make a difference if the property is explicitly set to None, or the property is not defined at all? (it probably should - because it gives you a way to differentiate between two interpretations of "reset" behaviour - and that interpretation should be documented here).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed it to use UIColor.systemBackgroundColor() as the default in the call to getattr.

On the backends, there are 3 different possible values for set_background_color():

None Default background color
TRANSPARENT Actual transparency
Color Actual color

But for Button, the historical behavior is "None = TRANSPARENT", which I have preserved in this PR.

self.native.backgroundColor = native_color(color)
super().set_background_color(
self._default_background_color if color in {None, TRANSPARENT} else color
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok - I think that interpretation of "None = Transparent" is what I was remembering. Based on the examples you've presented here, it looks like you've preserved the historical behavior.

@proneon267
Copy link
Contributor Author

proneon267 commented Dec 4, 2024

I agree, I should have added context for not enabling tests. I have edited the PR ticket to include the context and will keep in mind to add context on new PRs.

I have created #3015 to keep track of the widgets on which background color tests are not enabled.

@proneon267
Copy link
Contributor Author

Current Progress:

Source code:
"""
My first application
"""

import toga
from toga.style import Pack
from toga.style.pack import COLUMN


class iOSBackground(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.content = toga.Box(
            style=Pack(
                direction=COLUMN,
                flex=1,
                padding=0,
            ),
            children=[
                toga.Box(style=Pack(flex=1.5)),
                toga.Button(text="Button"),
                toga.Box(style=Pack(flex=1.5)),
                toga.ImageView(toga.Image(self.paths.app / "resources/imageview.png")),
                toga.Box(style=Pack(flex=1.5)),
                toga.Label(text="Label"),
                toga.Box(style=Pack(flex=1.5)),
                toga.ProgressBar(max=100, value=50),
                toga.Box(style=Pack(flex=1.5)),
                toga.Selection(items=["Alice", "Bob", "Charlie"]),
                toga.Box(style=Pack(flex=1.5)),
                toga.Slider(range=(-5, 10), value=7),
                toga.Box(style=Pack(flex=1.5)),
                toga.NumberInput(value=8908),
                toga.Box(style=Pack(flex=1.5)),
                toga.MultilineTextInput(
                    value="Some text.\nIt can be multiple lines of text."
                ),
                toga.Box(style=Pack(flex=1.5)),
                toga.TextInput(value="Jane Developer"),
                toga.Box(style=Pack(flex=1.5)),
                toga.ActivityIndicator(running=True),
                toga.Box(style=Pack(flex=1.5)),
                toga.Switch(text="Switch"),
                toga.Box(style=Pack(flex=1.5)),
            ],
        )
        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = toga.ScrollContainer(content=self.content)
        self.main_window.show()


def main():
    return iOSBackground()
LightMode DarkMode
Screenshot 2024-12-04 at 12 46 57 PM Screenshot 2024-12-04 at 12 45 42 PM

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structure of the PR is looking good now; the only question is about the actual colors used (and in particular the secondary background color change)

except AttributeError: # pragma: no cover
self.native.backgroundColor = UIColor.whiteColor
default_background_color = getattr(
self, "_default_background_color", UIColor.secondarySystemBackgroundColor()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why secondarySystemBackgroundColor? This is a fairly big change - and looking at the most recent sample screenshot, it definitely doesn't look right for switch, and it's arguable whether it's correct for TextInput, MultilineTextInput et al

@proneon267
Copy link
Contributor Author

If we use systemBackgroundColor, the widgets are not distinguishable from the background:

LightMode DarkMode
Screenshot 2024-12-04 at 9 45 14 PM Screenshot 2024-12-04 at 9 47 53 PM

I had used secondarySystemBackgroundColor since it made the widgets distinguisable and similar in appearance as the native settings app:

LightMode DarkMode
Screenshot 2024-12-04 at 9 50 13 PM Screenshot 2024-12-04 at 9 48 47 PM
Screenshot 2024-12-04 at 9 49 51 PM Screenshot 2024-12-04 at 9 49 37 PM

But looking closely, I now realize that the UISwitch widget itself has a background color of UIColor.clearColor and is inside a UIView which has a non-transparent background color.
Untitled-1(1)
The same also seems to be true for Input-related widgets:
Untitled-1(2)

  • So, should we use UIColor.clearColor as the default for all widgets, or
  • should we use UIColor.secondarySystemBackgroundColor() as the default for input-related widgets and UIColor.clearColor as the default for other widgtes?

@freakboy3742
Copy link
Member

If we use systemBackgroundColor, the widgets are not distinguishable from the background:

Yes - but that's part of iOS's style guide. You can disagree with that style if you like (I know I definitely have my issues with the "minimalist" iOS style guide), but that doesn't give us liberty to "fix" it.

I had used secondarySystemBackgroundColor since it made the widgets distinguisable and similar in appearance as the native settings app:

Yes - but the native settings app is specifically on a different background color. It's the "form" layout that is providing the background color, not the widget. The UISwitch is the widget that makes this distinction obvious.

  • So, should we use UIColor.clearColor as the default for all widgets, or
  • should we use UIColor.secondarySystemBackgroundColor() as the default for input-related widgets and UIColor.clearColor as the default for other widgtes?

By default, we shouldn't be making any change from the "default" value. Functionally, that may mean we need to encode (or capture at runtime) the default; but the background color of the widget should be the same as if we did nothing programatically.

@proneon267
Copy link
Contributor Author

proneon267 commented Dec 16, 2024

Keeping the widget's original background color as the default background color, corrected #767 bug and also produces the correct widget background color like you had described before:

LightMode DarkMode
Screenshot 2024-12-04 at 9 45 14 PM Screenshot 2024-12-04 at 9 47 53 PM

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So - this is definitely a simpler implementation - but in the process of simplifying the implementation, you've also introduced an edge case bug.

If you change from light mode to dark mode while the app is running, the default background color of the widget will change - but _default_background_color won't.

This is why the call the UIColor.systemBackgroundColor() was dynamic in the old approach. This is a capability we need to preserve.

@proneon267
Copy link
Contributor Author

I did some more tests and it seems like the default background color for any widget is always None. No matter the system theme whether light or dark mode, if we don't set the native.backgroundColor property manually, then native.backgroundColor for any widget always reports as None.

According to: https://developer.apple.com/documentation/uikit/uiview/backgroundcolor?language=objc, a value of nil/None indicates transparent background color.

If we use UIColor.systemBackgroundColor(), then it will return an opaque background color, which would not be the default native behavior, and would cause bugs like #767.

@freakboy3742
Copy link
Member

I did some more tests and it seems like the default background color for any widget is always None. No matter the system theme whether light or dark mode, if we don't set the native.backgroundColor property manually, then native.backgroundColor for any widget always reports as None.

According to: https://developer.apple.com/documentation/uikit/uiview/backgroundcolor?language=objc, a value of nil/None indicates transparent background color.

Sure - issue is that we do invoke set_background_color on every widget, so the "Default color" case needs to be handled consistently by all widgets.

@proneon267
Copy link
Contributor Author

proneon267 commented Dec 22, 2024

Yes, we do invoke set_background_color on every widget, and when we get set_background_color(None), we set the widget background color to _default_background_color and _default_background_color is equal to the value of native.backgroundColor.

Since, native.backgroundColor always returns None as the system default value, so the correct native default system color gets applied when the default color is requested. Therefore, I am not sure about the stale value of _default_background_color that you are describing.

Since, the native system default background color is always None, whether the system is in "light mode" or in "dark mode", so _default_background_color keeps the correct native default system background color at all times.

As an example test, running the following example app:

"""
My first application
"""

import toga
from toga.style import Pack
from toga.style.pack import COLUMN


class iOSBackground(toga.App):
    def reset_to_native_default_background_color(self, widget, **kwargs):
        for widget in self.widgets:
            del widget.style.background_color
        self.content.refresh()

    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.content = toga.Box(
            style=Pack(
                direction=COLUMN,
                flex=1,
            ),
            children=[
                toga.Box(style=Pack(flex=1.5)),
                toga.Button(
                    text="Reset to native default background color",
                    on_press=self.reset_to_native_default_background_color,
                ),
                toga.Box(style=Pack(flex=1.5)),
                toga.ImageView(toga.Image(self.paths.app / "resources/imageview.png")),
                toga.Box(style=Pack(flex=1.5)),
                toga.Label(text="Label"),
                toga.Box(style=Pack(flex=1.5)),
                toga.ProgressBar(max=100, value=50),
                toga.Box(style=Pack(flex=1.5)),
                toga.Selection(items=["Alice", "Bob", "Charlie"]),
                toga.Box(style=Pack(flex=1.5)),
                toga.Slider(range=(-5, 10), value=7),
                toga.Box(style=Pack(flex=1.5)),
                toga.NumberInput(value=8908),
                toga.Box(style=Pack(flex=1.5)),
                toga.MultilineTextInput(
                    value="Some text.\nIt can be multiple lines of text."
                ),
                toga.Box(style=Pack(flex=1.5)),
                toga.TextInput(value="Jane Developer"),
                toga.Box(style=Pack(flex=1.5)),
                toga.ActivityIndicator(running=True),
                toga.Box(style=Pack(flex=1.5)),
                toga.Switch(text="Switch"),
                toga.Box(style=Pack(flex=1.5)),
            ],
        )
        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = toga.ScrollContainer(content=self.content)
        self.main_window.show()


def main():
    return iOSBackground()
Light Mode to Dark Mode Screenshot 2024-12-22 at 1 25 25 PM Screenshot 2024-12-22 at 1 27 37 PM
Dark Mode to Light Mode Screenshot 2024-12-22 at 1 26 49 PM Screenshot 2024-12-22 at 1 31 39 PM

The background color of all widgets automatically change according to the native default system theme, and resetting the background color widgets(by clicking on the reset button) has no effect, indicating that the correct native default background color is being applied.

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since, native.backgroundColor always returns None as the system default value, so the correct native default system color gets applied when the default color is requested. Therefore, I am not sure about the stale value of _default_background_color that you are describing.

Since, the native system default background color is always None, whether the system is in "light mode" or in "dark mode", so _default_background_color keeps the correct native default system background color at all times.

That all makes sense - but if the default background color is always None... then why preserve it as an attribute? Why not just use None?

I've also flagged a couple of places where there's an odd inconsistency in the widget changes being made. Those inconsistencies aren't necessarily wrong - I just want to make sure that their both intentional and correct.

self.native.backgroundColor = native_color(TRANSPARENT)
else:
self.native.backgroundColor = native_color(color)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the same implementation as Button... why hasn't the implementation changed in the same way?

Copy link
Contributor Author

@proneon267 proneon267 Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, the implementation of Button was:

def set_background_color(self, color):
    if color == TRANSPARENT or color is None:
        self.native.backgroundColor = None
    else:
        self.native.backgroundColor = native_color(color)

Here, TRANSPARENT and None are shorted to None, as the behavior of setting transparent or resetting the button background color is restoring to the original background color, like on other backends.

While the previous implementation of ImageView was:

def set_background_color(self, color):
    if color == TRANSPARENT or color is None:
        self.native.backgroundColor = native_color(TRANSPARENT)
    else:
        self.native.backgroundColor = native_color(color)

Here, TRANSPARENT and None are instead shorted to TRANSPARENT. Hence, the backgroundColor implementation is different from Button.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - they're very similar, but not quite the same; the key difference being that Button is explicitly changing the interpretation of TRANSPARENT to "reset", whereas imageview/label are using the default interpretation (and relying on the fact that "clearColor" and "None" are effectively the same on iOS for these widgets). Thanks for the clarification.

if color == TRANSPARENT or color is None:
self.native.backgroundColor = native_color(TRANSPARENT)
else:
self.native.backgroundColor = native_color(color)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with ImageView - why does the implementation change not match Button?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reason as #3009 (comment)

@@ -260,7 +260,7 @@ def _line_height(self, font):
def measure_text(self, text, font):
# We need at least a fill color to render, but that won't change the size.
sizes = [
self._render_string(line, font, fill_color=color(BLACK)).size()
self._render_string(line, font, fill_color=toga_color(BLACK)).size()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I see what this name change is adding; there's no ambiguity in color here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reverted it.

@proneon267
Copy link
Contributor Author

proneon267 commented Jan 7, 2025

I have manually tested and also the docs: https://developer.apple.com/documentation/uikit/uiview/backgroundcolor?language=objc state that:

The default value is nil, which results in a transparent background color.

So, if we set the backgroundColor to None then it will use the default background color, which is a transparent background color(same as UIColor.clearColor).

Therefore, I agree with you that there isn't any benefit in caching the default background color of widgets, rather than just setting backgroundColor to None. Hence, I have removed _default_background_color.

Since, None and UIColor.clearColor are essentially the same, so the cases where the previous implementation was the following, are not required:

if color == TRANSPARENT or color is None:
     self.native.backgroundColor = native_color(TRANSPARENT)
else:
     self.native.backgroundColor = native_color(color)

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for those additional clarifications - I think that's everything! On to the next platform :-)

self.native.backgroundColor = native_color(TRANSPARENT)
else:
self.native.backgroundColor = native_color(color)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - they're very similar, but not quite the same; the key difference being that Button is explicitly changing the interpretation of TRANSPARENT to "reset", whereas imageview/label are using the default interpretation (and relying on the fact that "clearColor" and "None" are effectively the same on iOS for these widgets). Thanks for the clarification.

@freakboy3742 freakboy3742 merged commit 7f02868 into beeware:main Jan 9, 2025
41 checks passed
@proneon267 proneon267 deleted the iOS_transparency_fix branch January 21, 2025 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants