Android TabView to look like iOS


#1

I have a project where I need a bottom bar of tappable buttons for navigating through tabs (or pages).

I see there’s a TabView, but both Android and iOS do them differently, I basically need an icon and text under it per button.

Is there any trick to this to make them the same look with Nativescript?

Thank you,

~ Chris


#2

I think you could fake it out with a DockLayout or some StackLayout tweaking. Anyone tried this?


#3

I’ve done this already with the original TabView, just hacked the original android.js file a bit. Will post the exact example when I get home :slight_smile:

I wanted my apps to look the same on iOS and Android and needed this badly. The only minus here is that when you updated tns-core-modules, you’ll need to edit the file again.


#4

Or you could make that into a simple plugin that has a peerDependency of tns-core-modules (or the tns-core-widgets, depending on what API you consume)


iOS tabView on the top
#5

That would be great! I’m up for trying it out, when can you post it? Thank you.


#6

I figured out a different solution that doesn’t use TabView at all!

I ended up using FlexboxLayout with Button Spans. Is this ok? lol

<FlexboxLayout>
  <Button class="footer-btn current" tap="offers" flexGrow="1">
    <FormattedString>
      <Span text="&#xf10b;&#x000a;" fontSize="24" fontFamily="FontAwesome, fontawesome-webfont" />
      <Span text="Offers" />
    </FormattedString>
  </Button>
  <Button class="footer-btn" tap="categories" flexGrow="1">
    <FormattedString>
      <Span text="&#xf009;&#x000a;" fontSize="24" fontFamily="FontAwesome, fontawesome-webfont" />
      <Span text="Categories" />
    </FormattedString>
  </Button>
  <Button class="footer-btn" tap="favorites" flexGrow="1">
    <FormattedString>
      <Span text="&#xf005;&#x000a;" fontSize="24" fontFamily="FontAwesome, fontawesome-webfont" />
      <Span text="Favorites" />
    </FormattedString>
  </Button>
  <Button class="footer-btn" tap="rewards" flexGrow="1">
    <FormattedString>
      <Span text="&#xf091;&#x000a;" fontSize="24" fontFamily="FontAwesome, fontawesome-webfont" />
      <Span text="Rewards" />
    </FormattedString>
  </Button>
  <Button class="footer-btn" tap="search" flexGrow="1">
    <FormattedString>
      <Span text="&#xf002;&#x000a;" fontSize="24" fontFamily="FontAwesome, fontawesome-webfont" />
      <Span text="Search" />
    </FormattedString>
  </Button>
</FlexboxLayout>

#8

Sorry for the late reply, I am under severe jet-lag :smile:
I don’t have my exact code in front, but I use the source as a walkthrough. The code is same for TS and JS it seems. (Line numbers might be different in .js).

Ok so, looking here: https://github.com/NativeScript/NativeScript/blob/master/tns-core-modules/ui/tab-view/tab-view.android.ts we open the android.ts/js file and locate the part where “createUI” is (L188).

This part of the code creates the containers for GridLayout, TabLayout (buttons) and the ViewPager (tab pages). So we just need to alter the way they are placed.

First, we reverse the row GridUnitTypes (L194 & L195):

this._grid.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star));
this._grid.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto));

Then we add the TabLayout (buttons) to the last row of the gridLayout (L197 & L198):

this._tabLayout = new org.nativescript.widgets.TabLayout(this._context);
var lp = new org.nativescript.widgets.CommonLayoutParams();
lp.row = 1;
this._tabLayout.setLayoutParams(lp);
this._grid.addView(this._tabLayout);

Last, we make sure the ViewPager is placed in the first row of gridLayout, so we remove the original layout params: (L220 - L225):

this._viewPager.setId(this._androidViewId);
//var lp = new org.nativescript.widgets.CommonLayoutParams();
//lp.row = 1;
//this._viewPager.setLayoutParams(lp);
this._grid.addView(this._viewPager);

That’s it. This should do it. But @Pete.K did inspire me regarding the creation of a plugin that does this for us, so I will be looking into that. :slight_smile:


#9

Thank you for posting this!

If I choose to try it this way, is there a way to ignore the file change whenever I do an update? (should I?) or do i just need to remember to make the change each time?

Im not sure if you saw my post above, I sort of made my layout work, but running into a problem with the FlexboxLayout where if there’s not enough content, the bottom bar is too large, and if there’s too much content, the bottom bar is too skinny. I tried setting some heights, but the flexGrow=“1” takes over. Not sure what to do for that.


#10

No problem.
Yeah, you should apply the change after each upgrade, so that you don’t miss out on potential fixes in other places. But otherwise you could just backup the android.js file and put it back in. I will take a look and see if we can get a plugin out of this.

Regarding the usage of your Flexbox solution, it all depends on how you use it. Your custom made tabbar (button-bar) is of type FlexboxLayout, the parent container should not be. I prefer a GridLayout around the content and the buttons. Where you set row=0 to * (star) and row=1 to either auto or a static height value. I think if you use auto and a FlexboxLayout, then it could show symptoms like your describe, since there is nothing that tells the layout containers what the height is (it’s flexible) :slight_smile:

Something like this:

<GridLayout rows="*,40">
   <StackLayout row="0">
      //... main content
   </StackLayout
   <FlexboxLayout row="1">
      // Your implemenation of TabBar
   </FlexboxLayout>
</GridLayout

#11

Thank you again manijak!

You set me on the right path to what I needed, here’s what I ended up with:

<GridLayout rows=”*,50”>
  <ScrollView>
    <StackLayout>
      //... main content
     </StackLayout>
   </ScrollView>
   <FlexboxLayout row="1">
     <FlexboxLayout row="1" cssClass="footer">
       <Button tap="offers" class="current">
         <FormattedString>
           <Span text="&#xf10b;&#x000a;" fontSize="24" fontFamily="FontAwesome, fontawesome-webfont" />
           <Span text="Offers" />
         </FormattedString>
       </Button>
       <Button tap="categories">
         <FormattedString>
           <Span text="&#xf009;&#x000a;" fontSize="24" fontFamily="FontAwesome, fontawesome-webfont" />
           <Span text="Categories" />
         </FormattedString>
       </Button>
       <Button tap="favorites">
         <FormattedString>
           <Span text="&#xf005;&#x000a;" fontSize="24" fontFamily="FontAwesome, fontawesome-webfont" />
           <Span text="Favorites" />
         </FormattedString>
       </Button>
       <Button tap="rewards">
         <FormattedString>
           <Span text="&#xf091;&#x000a;" fontSize="24" fontFamily="FontAwesome, fontawesome-webfont" />
           <Span text="Rewards" />
         </FormattedString>
       </Button>
       <Button tap="search">
         <FormattedString>
           <Span text="&#xf002;&#x000a;" fontSize="24" fontFamily="FontAwesome, fontawesome-webfont" />
           <Span text="Search" />
         </FormattedString>
       </Button>
     </FlexboxLayout>
   </FlexboxLayout>
</GridLayout>

#12

You can also check this plugin:

https://www.npmjs.com/package/nativescript-bottombar

:smiley:


#13

Is there any similar way to change the iOS file and relocate the tab View on the top??


#14

New version of the plugin is out :beers:

  • Badges/Notifications unabled
  • Change programatically title, icon and color whenever you want
  • Hide/Show with animation

Have fun :wink:

https://www.npmjs.com/package/nativescript-bottombar


#15

Hello @rhanb, I’m using the component (thanks for that!) Everything is fine on android but on iOS it’s not ignored and it gives me an error saying it doesn’t find the module:

JS ERROR Error: Could not find module 'nativescript-bottombar'. Computed path '/Users/denny/Library/Developer/CoreSimulator/Devices/2E8D729B-03BF-40E6-A4FA-D8CD01792D26/data/Containers/Bundle/Application/2C3B49E9-8C10-4339-B804-49128A0506A0/myapp.app/app/tns_modules/nativescript-bottombar'.
Apr 25 10:54:49 Demetrios-Macbook-Pro com.apple.CoreSimulator.SimDevice.2E8D729B-03BF-40E6-A4FA-D8CD01792D26.launchd_sim[1117] (UIKitApplication:org.nativescript.MyApp[0x654b][1139][2258]): Service exited due to Segmentation fault: 11

I’ve seen the resolved issue in github but it seems I’m still having it…

Any idea?

Thanks,
Dem


#16

@rhanb it looks like only the android version of the files is there, so just adding bottombar.ios.d.ts (copied from bottombar.android.d.ts) and an empty bottombar.ios.d.js worked…

Let me know what you think…

Thanks,
Dem


#17

Hi, this plugin is only compatible with android that’s why you got this error on iOS, you should conditionnaly require the plugin like so:

if (isAndroid) {
    var bottomBar = require('nativescript-bottombar');
}

#18

Hello,
thanks for the reply…I’m still not convinced, if I do that then I can’t import anything in typescript, do you have an example with typescript to show me how you do with it?

Thanks,
Dem


#19

Hi,

This plugin doesn’t support iOS, so in any cases you’ll have to do a different template for iOS you can do it in various ways like so:

Component Angular XML:

<ios>
    <your-ios-template />
</ios>
<android>
    <BottomBar />
</android>

Component Angular TS:

if (isAndroid) {
    const bottomBar = require('nativescript-bottombar');
    registerElement('BottomBar', () => bottomBar.BottomBar);
}

I’m not familiar with pure TypeScript + NativeScript projects, but I think you should be able to do king of the same thing :smile:


#20

The problem is that in typescript we still have to “import” the types we are using and we cannot do conditional import, that’s why I had to add the definition and an empty implementation for iOS, I’m not saying you should support iOS (it has already support for native tabs), I’m just saying you should add a definition file for iOS and then an empty implementation so at least it will run in iOS without complaining about the import.

The way you suggested doesn’t work with typescript (and yes I’m using 2 templates but the component code is shared so I cannot import the specific types and run it on iOS)…this one will complain that there is no module on iOS:

import { BottomBar, BottomBarItem, TITLE_STATE, SelectedIndexChangedEventData } from 'nativescript-bottombar';

I’ll check it and will send a pull request if you are interested…

Thanks,
Dem


#21

Ok, I understand the issue, I had the same problem when I wanted to implement the plugin in one of my project. To solve this issue I created two different (not only templates) component.ts, one for iOS and one for Android and you actually conditionally require the right component in your module.

But you are also right, it could easily be avoided by implementing an empty module for iOS. But I’m not sure it’s the best practice for consistency since the plugin is not supported for iOS you shouldn’t import the library for your iOS app. From my point of view it’s better to conditionnaly use the plugin for consistency. Like this in your final build you won’t have a useless library in your release app.

I would be glad to here the opinion of other, if someone prove me wrong I will implement it :smile: