Create multi-line button with text and icon


#1

If you want to create a multi-line button with text and icons (as pictured below), you’re going to need to use the “advanced” text settings properties called FormattedString.

Normally, when you set the text of a button, you simply use the text property:

<button text="Add Scene" />

If you want to add an icon to this button, we can add a unicode string that represents an icon available in an Icon Font (that I’m going to assume you’ve already added to your app):

XML
<button text="&#xf48a; Add Scene" class="btnIcon" />

CSS

.btnIcon {
   font-family: "Ionicons";
   font-size: 60;
}

This will now give us a button with our icon and text on one line…AND one size (big). Let’s use FormattedString to fix this up:

XML

<button>
   <formattedString>
      <span text="&#xf48a;" fontFamily="Ionicons" fontSize="60" />
      <span text="Add Scene" />
   </formattedString>
</button>

Getting closer. Now we have our icon and text rendered at different sizes, but still on the same line. To add a line break between our icon and text, we can use another unicode character:

XML

<button>
   <formattedString>
      <span text="&#xf48a;&#x000a;" fontFamily="Ionicons" fontSize="60" />
      <span text="Add Scene" />
   </formattedString>
</button>

This will now give us the desired result as pictured above: icon and text in a button (on separate lines) with the ability to control the styling for the icon and text separately.

NOTE: When we start using FormattedString, we do have to specify our styles in-line using XML attributes instead of using CSS styling. Hopefully this limit will go away in the future, but here today (as of {N} 2.4).

NOTE 2: There is an alternate, more verbose FormattedString syntax that looks like this:

XML

<button>
   <button.formattedText>
      <formattedString>
         <formattedString.spans>
            <span text="Add Scene" />
         </formattedString.spans>
      </formattedString>
   </button.formattedText>
</button>

DON’T use this syntax. It works, but only in non-Angular {N} apps. The more concise syntax works in ALL {N} apps and it’s WAY easier to read. (Docs ref)

Hope this helps!


#2

Awesome!
This works for both iOS and Android?
I remember having issues rendering icon fonts for Android.


#3

Should work fine on both iOS and Android. Just confirmed that the example shown here will work in Android (with {N} 2.4).

One thing to note: on Android, the “font-family” name matches the font file name, where on iOS it always matches the original name embedded in the font. So if I have a file called ionicons.ttf in my app’s fonts folder…

On iOS
font-family: Ionicons;

On Android
font-family: ionicons;

Note the capitalization change. The cross-platform solution is to make sure the font file name matches exactly (including capitalization) the original font family name.


#4

@toddanglin I am trying to follow your example above, just I have a need to create it dynamically (not through XML). On Android it works fine, but on iOS I see only the font-icon and not the text. Any idea what I am missing?

let formattedString = new formattedStringModule.FormattedString();
let iconSpan = new spanModule.Span();
// iconSpan.text=String.fromCharCode(item.icon) + String.fromCharCode(0x000a); // 0x000a is unicode line-feed
iconSpan.text=String.fromCharCode(item.icon) + '\n';
iconSpan.fontFamily=this.iconFontFamily;
iconSpan.fontSize=this.iconSize;

let textSpan = new spanModule.Span();
textSpan.text = item.title;
textSpan.fontSize = this.fontSize;

formattedString.spans.push(iconSpan);
formattedString.spans.push(textSpan);

tab.button = new buttonModule.Button();
tab.button.formattedText = formattedString;

This post seems to indicate there might be a need to set the size of the frame, but I don’t understand why my use is different from yours…


#5

I haven’t tried the programmatic approach to this scenario yet, so don’t have definitive guidance…

For troubleshooting, I’d try:

  1. Build the button in XML and test on iOS just to be sure that works (all your fonts are working correctly, etc)
  2. If that works, make sure your dynamic implementation matches the working iOS XML
  3. If you get this far and the dynamic iOS version is still only displaying the icon…
  4. Try manually setting the height on the button to see if that forces the button to properly size to show the icon + text

It is possible that you’ll have to do some manual sizing of the element with the dynamic approach that would not be necessary with declarative XML.

You might also try adding your button to your tab after you’ve configured your formattedString. Once the UI element gets added to the visual tree, layout is calculated. So if your button is already in the visual tree before you set the formattedString, its height has already been calculated. (Not sure if tab is already in the UI tree, so just another guess.)

Hope this helps. If you remain stuck, let me know and I’ll try to reproduce on a test app.


#6

Thanks @toddanglin, but what if an XML button has the same problem? I copied your XML example and adapted it for {N} + ng2 and have the same result: Android shows fine, but on iOS I see only the FontAwesome icon, whereas no text is shown. If anything I would have expected the icon font not to work… I played around with height and stuff but it does not help either. I’m kinda out of ideas what to investigate. Starting to think it does have to do with iOS not properly calculating the size of the text inside the button.

    <Button height="100">
       <FormattedString>
          <Span text="&#xf118;&#x000a;" fontFamily="FontAwesome" fontSize="35"></Span>
          <Span text="Add Scene"></Span>
       </FormattedString>
    </Button>

#7

Ah! Good debugging step. Now we know it’s not the dynamic approach causing the problem. :slight_smile:

Next, I’d try just putting your icon and text on one line and make that works. Something like:

<Button text="&#xf48a; Add Scene" fontFamily="FontAwesome" />

That will eliminate another variable. If that works, my next step would be to make sure multi-line unicode (&#x000a;) is working. It worked for me with the Ionicons font, but it may not work with FontAwesome.

Ultimately, this syntax should work on iOS and properly render the height of the button, especially if defined in the XML. Try eliminating variables until this works, and then work back in to your target tab button scenario.

Hope that helps.


#8

@toddanglin I actually got it to work just now like this:

declare var NSLineBreakMode: any;
declare var NSTextAlignmentCenter: any;

<Button #myBtn>
       <FormattedString>
          <Span text="&#xf118;&#x000a;" fontFamily="FontAwesome" fontSize="35"></Span>
          <Span  text="Add Scene" fontSize="20"></Span>
       </FormattedString>
</Button>


@ViewChild('myBtn') myBtn: ElementRef;

ngOnInit() {
  this.myBtn.nativeElement.ios.titleLabel.textAlignment = NSTextAlignmentCenter;
  this.myBtn.nativeElement.ios.titleLabel.lineBreakMode = NSLineBreakMode.NSLineBreakByWordWrapping;
}

That is for the static XML part of it. Now need to understand how I can get access to the nativeElement of programmatically declared buttons…


#9

OK so for the dynamic code this works on iOS:

let formattedString = new formattedStringModule.FormattedString();
let iconSpan = new spanModule.Span();
iconSpan.text=String.fromCharCode(item.icon) + String.fromCharCode(0x000a); // 0x000a is unicode line-feed
iconSpan.fontFamily=this.iconFontFamily;
iconSpan.fontSize=this.iconSize;

let textSpan = new spanModule.Span();
textSpan.text = item.title;
textSpan.fontSize = this.fontSize;

formattedString.spans.push(iconSpan);
formattedString.spans.push(textSpan);

tab.button = new buttonModule.Button();
if (isIOS) {
        //tab.button.ios.numberOfLines = 0;
        tab.button.ios.titleLabel.textAlignment = NSTextAlignmentCenter;
        tab.button.ios.titleLabel.lineBreakMode = NSLineBreakMode.NSLineBreakByWordWrapping;
      }
tab.button.formattedText = formattedString;

#10

why not simply set textWrap="true" on the button to allow multiline?