Skip to content

Conversation

@hansemannn
Copy link
Collaborator

@hansemannn hansemannn commented Aug 18, 2025

This pull request refactors and modernizes the button configuration system in the Titanium iOS module. It removes the deprecated SystemButtonStyleProxy and introduces a new, more flexible ButtonConfigurationProxy that leverages the latest UIKit features. The changes include updates to the API, implementation of the new proxy, and project file adjustments.

Migration from SystemButtonStyle to ButtonConfiguration:

  • Removed all references to the deprecated TiUIiOSSystemButtonStyleProxy from the codebase and project files, including its header and implementation files. (TiUIiOSProxy.h, TiUIiOSProxy.m, TiUIiOSSystemButtonStyleProxy.h, TiUIiOSSystemButtonStyleProxy.m, Titanium.xcodeproj) [1] [2] [3] [4] [5] [6] [7] [8]

  • Added the new TiUIiOSButtonConfigurationProxy class, which supports modern UIKit button configurations (e.g., plain, tinted, filled, glass, etc.), and exposes properties for customizing button appearance and behavior. (TiUIiOSButtonConfigurationProxy.h, TiUIiOSButtonConfigurationProxy.m) [1] [2] [3] [4] [5]

API and Usage Updates:

  • Updated TiUIiOSProxy to add a createButtonConfiguration: factory method and included the new proxy in its imports and memory management. (TiUIiOSProxy.h, TiUIiOSProxy.m) (F1e356e5L52R52, [1] [2]

  • Modified TiUIButton to support the new button configuration via a setConfiguration_: method, enabling assignment of a UIButtonConfiguration object to a button. (TiUIButton.m) [1] [2]

Project Structure Adjustments:

  • Updated the Xcode project file to rename and re-link the new proxy files, ensuring correct compilation and integration. (Titanium.xcodeproj) [1] [2] [3] [4]

These changes modernize button customization for iOS in Titanium, making it easier to use the latest UIKit features and maintain code going forward.

Example code:

const window = Ti.UI.createWindow({
	title: "Window 1",
	backgroundColor: "#fff",
});

const btn = Ti.UI.createButton({
    configuration: Ti.UI.iOS.createButtonConfiguration({
        title: 'Sign In',
        subtitle: 'with Google',
        style: 'bordered',
        // color: 'red',
        backgroundColor: 'green',
        backgroundSelectedColor: 'blue',
        // padding: { left: 40, right: 40, top: 5, bottom: 5 },
        image: Ti.UI.createView({ width: 20, height: 20, backgroundColor: 'blue', borderRadius: 10 }).toImage(),
        imagePlacement: 'leading',
        imagePadding: 10,
        titlePadding: 5,
        textAlign: 'right',
        font: {
            fontSize: 20,
            fontWeight: 'bold'
        }
    })
});

window.add(btn);
window.open();

| Simulator Screenshot - iPhone 16 Pro - 2025-08-18 at 17 08 21 | simulator_screenshot_D345D3E3-4227-4E79-A84A-6A57974D2E9D |

@m1ga
Copy link
Contributor

m1ga commented Aug 19, 2025

very nice 👍

Quick question about the setup: At the moment you would need to duplicate stuff like title or padding if you want to create a cross-platform button and put those in the button properties and in the in configuration: Ti.UI.iOS.createButtonConfiguration() properties. Could this changed so we just use the setup like:

const btn = Ti.UI.createButton({
  title: 'Sign In',
  subtitle: 'with Google',
  style: 'bordered',
  // color: 'red',
  // backgroundColor: 'green',
  // padding: { left: 40, right: 40, top: 5, bottom: 5 },
  image: Ti.UI.createView({ width: 20, height: 20, backgroundColor: 'blue', borderRadius: 10 }).toImage(),
  imagePlacement: 'leading',
  imagePadding: 10,
  titlePadding: 5
});

and add a configurationLayout: true (or else) so it will create the whole createButtonConfiguration in the background for you?
That way we can reuse existing properties and the button would render with the normal/available properties on Android. And users can easier switch to the new layout by enabling the new layout option. Guess that would also make it easier to use in Alloy.

@hansemannn
Copy link
Collaborator Author

They are inside an own scope for now, so we can get feedback independently from the normal button properties. Even if props like "title" sound the same, they are handled differently internally.

If this effort works and is well adopted, they will be merged with the root properties in the near future.

@hansemannn
Copy link
Collaborator Author

@m1ga Quick update on this: I've added some more properties and it's been tested by the community in parallel. The plan is to move all remaining properties one by one to the new proxy and then merge it with the existing APIs - ideally, I'll also rewrite the class to Swift at that point.

What do you think about these backgroundImage, backgroundSelectedImage properties? I feel like they're from a time (iOS 5/6) where background images used to be a thing, but since iOS 7+ / flat design, no one should use it anymore. If those can be fully deprecated, we could integrate the more modern APIs sooner.

@m1ga
Copy link
Contributor

m1ga commented Aug 28, 2025

What do you think about these backgroundImage, backgroundSelectedImage properties? I feel like they're from a time (iOS 5/6) where background images used to be a thing, but since iOS 7+ / flat design, no one should use it anymore. If those can be fully deprecated, we could integrate the more modern APIs sooner.

People can still use a normal View with a backgroundImage and a label inside if they want to create such a button. But I think if you just want to use a normal UI Button it should just be the default iOS/Android button with colors.

@designbymind
Copy link

designbymind commented Sep 2, 2025

Another excellent addition to Ti! — This definitely provides a lot of flexibility in one easy to use Button object. Once again, awesome job on this and I'm personally super excited to convert over to Glass Buttons ✨

Below are some quick notes regarding usage from my experience:

  • Reconfirming: ButtonConfiguration takes precedence over Button properties set outside of the .configuration object.

  • Using backgroundSelectedColor requires that backgroundColor is set and one of the following values set for the style property: tinted | filled | gray | bordered | borderedTinted | borderedProminent.

  • All of the following style values provide a system/built-in highlighting cue when the Button is in 'active' state: tinted | borderedTinted | glass | prominentGlass | clearGlass | prominentClearGlass.

  • When using an SFSymbol in the image value, the color prop does not work on the image prop (but does on the title/subtitle), this is only true when the style prop has a value of glass.

  • The backgroundColor prop does not work with glass or clearGlass styles which I believe is the intended behavior. However, and I'm not condoning this behavior, but you can use backgroundColor outside of the .configuration object and it will work for both glass and clearGlass 😉

  • Not sure if I missed anything but as I continue to play'round, I'll come back...

        ^__^
        (xo)\_______
        (__)\       )\/\
            ||----w |
            ||     ||
    

@hansemannn
Copy link
Collaborator Author

Thanks Jason! As for your feedbacks: The backgroundColor for glass will work when used outside of the config, because it's then applied to the parent internal view, which then sits behind the glass button. As for the icon color of the buttons: This also seem to be expected, as the internal APIs call the textColor property. We can add an own image color in a follow-up PR (or refer to using custom view constructs for more advanced cases).

@hansemannn hansemannn merged commit a7ab5bd into tidev:master Sep 2, 2025
5 checks passed
@hansemannn hansemannn deleted the feature/ios-button-configuration branch September 2, 2025 08:18
@Max87ZA
Copy link

Max87ZA commented Sep 18, 2025

Hello @hansemannn , I tried it and I found a bug. Ti.UI.iOS.createButtonConfiguration works as expected on simulator, but according to my testing on device(iP15Pro, ios26) it shows error:
[ERROR] TypeError: Ti.UI.iOS.createButtonConfiguration is not a function. (In 'Ti.UI.iOS.createButtonConfiguration({style:"filled",title:"Sign In",subtitle:"with your account",backgroundColor:"#007AFF",color:"white"})', 'Ti.UI.iOS.createButtonConfiguration' is undefined)

And also docs - buttonConfiguration is accessible only from Ti.UI.Button docs via configuration property, it's missing from left menu.
I tried to implement it to my existing app and so to create blank app and put example code into it - same result - it works on simulator, it doesn't on device. Thank you

@hansemannn
Copy link
Collaborator Author

Thanks - will check that. For "And also docs - buttonConfiguration is accessible only from Ti.UI.Button docs via configuration property, it's missing from left menu." => I don't know what you mean there, can you re-iterate on that?

@m1ga
Copy link
Contributor

m1ga commented Sep 18, 2025

I should appear here in the menu:

Bildschirmfoto_20250918_140916

@Max87ZA
Copy link

Max87ZA commented Sep 18, 2025

Of course - there should be documentation for button's configuration properties here on left menu: https://titaniumsdk.com/api/titanium/ui/ios.html
Edit: miga was quicker 😀

@hansemannn
Copy link
Collaborator Author

I'm afraid the error is something on your config (e.g. wrong SDK selected), as it works fine here (production app, tested on device). Please check your setup to confirm, thx

@hansemannn
Copy link
Collaborator Author

hansemannn commented Sep 18, 2025

For the docs, tidev/titanium-docs@2fbcc78 addresses that.
Screenshot 2025-09-18 at 14 25 17

@Max87ZA
Copy link

Max87ZA commented Sep 18, 2025

I'm afraid the error is something on your config (e.g. wrong SDK selected), as it works fine here (production app, tested on device). Please check your setup to confirm, thx

I wrote DMs with Michael, we have same SDK version, fresh new app and example code and for device is not working, same code for simulator is working as expected.... I have no other problem besides this...

@m1ga
Copy link
Contributor

m1ga commented Sep 21, 2025

I have a iPhone 13 Mini at the moment with iOS26 installed and also have the crash on the device:

[ERROR] /alloy/controllers/index.js:51
[ERROR] configuration:Ti.UI.iOS.createButtonConfiguration({
[ERROR]                                                   ^
[ERROR] TypeError: Ti.UI.iOS.createButtonConfiguration is not a function. (In 'Ti.UI.iOS.createButtonConfiguration({
[ERROR] title:"Sign In",
[ERROR] subtitle:"with Google",
[ERROR] style:"bordered",
[ERROR] backgroundColor:"green",
[ERROR] backgroundSelectedColor:"blue",
[ERROR] image:Ti.UI.createView({width:20,height:20,backgroundColor:"blue",borderRadius:10}).toImage(),
[ERROR] imagePlacement:"leading",
[ERROR] imagePadding:10,
[ERROR] titlePadding:5,
[ERROR] textAlign:"right",
[ERROR] font:{
[ERROR] fontSize:20,
[ERROR] fontWeight:"bold"
[ERROR] }
[ERROR] })', 'Ti.UI.iOS.createButtonConfiguration' is undefined)
[ERROR]     at Controller (/alloy/controllers/index.js:51:50)
[ERROR]     at  (/alloy.js:427:48)
[ERROR]     at  (/app.js:40:23)
[ERROR]     at load (/ti.kernel.js:634:24)
[ERROR]     at loadJavascriptText (/ti.kernel.js:927:20)
[ERROR]     at loadAsFileOrDirectory (/ti.kernel.js:904:37)
[ERROR]     at require (/ti.kernel.js:730:52)
[ERROR]     at  (/ti.main.js:12837:10)
[ERROR]     at doLoad (/ti.main.js:12803:15)
[ERROR]     at loadBootstrapScripts (/ti.main.js:12811:11)
[ERROR]     at loadAsync (/ti.main.js:12816:23)
[ERROR]     at  (/ti.main.js:12834:10)
[ERROR]     at load (/ti.kernel.js:634:24)
[ERROR]     at  (/ti.kernel.js:1107:18)
[ERROR]     Foundation 0x000000019e5646cc _NSDescriptionWithLocaleFunc + 80
[ERROR]     CoreFoundation 0x00000001a0ea2e88 973E47E5-88F0-3367-B9D4-46D5CB999B75 + 89736
[ERROR]     CoreFoundation 0x00000001a0e95c80 973E47E5-88F0-3367-B9D4-46D5CB999B75 + 35968
[ERROR]     CoreFoundation 0x00000001a0e9f410 _CFStringCreateWithFormatAndArgumentsReturningMetadata + 184
[ERROR]     CoreFoundation 0x00000001a0e9f4c4 _CFStringCreateWithFormatAndArgumentsAux2 + 44
[ERROR]     TitaniumKit 0x0000000105367540 TiLogMessage + 52
[ERROR]     TitaniumKit 0x000000010539735c -[TiExceptionHandler reportScriptError:] + 60
[ERROR]     JavaScriptCore 0x00000001b61262b0 95CF7704-D824-301A-A1D4-2F2D335A7290 + 8233648
[ERROR]     JavaScriptCore 0x00000001b612acf8 95CF7704-D824-301A-A1D4-2F2D335A7290 + 8252664
[ERROR]     TitaniumKit 0x0000000105377630 -[KrollBridge evalFileOnThread:context:] + 340
[ERROR]     TitaniumKit 0x00000001053a1ae8 -[KrollInvocation invoke:] + 52
[ERROR]     TitaniumKit 0x00000001053a23e8 -[KrollContext invoke:] + 84
[ERROR]     TitaniumKit 0x0000000105377e48 -[KrollBridge didStartNewContext:] + 1084
[ERROR]     TitaniumKit 0x00000001053a2ac0 -[KrollContext main] + 988
[ERROR]     libdispatch.dylib 0x00000001d8865adc D34AC6A9-10DC-3535-9944-E3EFC5994788 + 6876
[ERROR]     libdispatch.dylib 0x00000001d887f7ec D34AC6A9-10DC-3535-9944-E3EFC5994788 + 112620
[ERROR]     libdispatch.dylib 0x00000001d889cb24 D34AC6A9-10DC-3535-9944-E3EFC5994788 + 232228
[ERROR]     libdispatch.dylib 0x00000001d8874ec8 D34AC6A9-10DC-3535-9944-E3EFC5994788 + 69320
[ERROR]     libdispatch.dylib 0x00000001d8874e04 _dispatch_main_queue_callback_4CF + 44
[ERROR]     CoreFoundation 0x00000001a0ef8520 973E47E5-88F0-3367-B9D4-46D5CB999B75 + 439584

code is from the first post:

const window = Ti.UI.createWindow({
	title: "Window 1",
	backgroundColor: "#fff",
});

const btn = Ti.UI.createButton({
    configuration: Ti.UI.iOS.createButtonConfiguration({
        title: 'Sign In',
        subtitle: 'with Google',
        style: 'bordered',
        // color: 'red',
        backgroundColor: 'green',
        backgroundSelectedColor: 'blue',
        // padding: { left: 40, right: 40, top: 5, bottom: 5 },
        image: Ti.UI.createView({ width: 20, height: 20, backgroundColor: 'blue', borderRadius: 10 }).toImage(),
        imagePlacement: 'leading',
        imagePadding: 10,
        titlePadding: 5,
        textAlign: 'right',
        font: {
            fontSize: 20,
            fontWeight: 'bold'
        }
    })
});

window.add(btn);
window.open();

I saw that there is an update to use an object as a parameter. Will check that later

@hansemannn
Copy link
Collaborator Author

hansemannn commented Sep 21, 2025

Can you attach the build folder (zipped) please? It seems like it's compiled out for you. And does it work if you set it after creation of the button, e.g.

btn.configuration = Ti.UI.iOS.createButtonConfiguration({ ... });

@m1ga
Copy link
Contributor

m1ga commented Sep 21, 2025

no, will also crash with Ti.UI.iOS.createButtonConfiguration is undefined

Build folder: https://migaweb.de/build.zip

@hansemannn
Copy link
Collaborator Author

botton?

@m1ga
Copy link
Contributor

m1ga commented Sep 21, 2025

sorry, typo. was typing it instead of copy & pasting it. The error is the one from above

@hansemannn
Copy link
Collaborator Author

hansemannn commented Sep 21, 2025

Okay - found the issue. The pre-compile statement is under the web-view statement, causing it to be dependent on it (has nothing to do with the API itself. Workaround for 13.0.0.GA: Add a Ti.UI.createWebView() (no assigment or usage required), which will generate the USE_TI_UIWEBVIEW precompile macro, also allowing the createButtonConfiguration function to be set. A fix is added to the master and 13_0_X.

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.

4 participants