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

TabBarIOS (Tabs) in AppleTV flickering when switching between tabs #15081

Closed
rada opened this issue Jul 18, 2017 · 11 comments
Closed

TabBarIOS (Tabs) in AppleTV flickering when switching between tabs #15081

rada opened this issue Jul 18, 2017 · 11 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@rada
Copy link

rada commented Jul 18, 2017

Is this a bug report?

Yes

Have you read the Bugs section of the Contributing to React Native Guide?

Yes

Environment

  1. react-native -v: 0.46.1
  2. node -v: 6.5.0
  3. npm -v: 3.10.3
  4. yarn --version (if you use Yarn):

Then, specify:

  • Target Platform: AppleTV
  • Development Operating System: macOS Sierra (10.12.5)
  • Build tools: Xcode (8.3.3)

Steps to Reproduce

  1. Switch to any Tab back and forth
  2. Switch to any Tab back and forth

Expected Behavior

The switch to any tab is smooth and without any flickering or selection falling back to previous tab.

Actual Behavior

Switching between TabBarIOS.Items is flickering and is not smooth. While selecting the new tab, It seems like first the new tab is selected, then immediately the old one and then again the new tab. This results in tab flickering when switching between the tabs. Worse case is, when the tabs render some more complex composed element. In this case, the new tab is not being selected, as the render is taking longer. Once it is rendered, we can select it but again the flickering occurs.

It looks like by the time the JS processes the received native event (tab switch) and sends an event (prop) back to Native, the tab selection falls back to the previous tab.

I have included very basic TabBar example with plain view and one text where flickering occurs. However, given the simple View is used in tabs here, switching is occasionally smooth.

Reproducible Demo

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  Text,
  View,
  TabBarIOS,
} from 'react-native';

export default class tvTabBar extends Component {

	_tabItems: Array<{ key: string }> = [
		{ key: 'TAB 1' },
		{ key: 'TAB 2' },
		{ key: 'TAB 3' },
		{ key: 'TAB 4' },
	];

	constructor(props) {
		super(props);
		this.state = { activeTab: 'TAB 1' };
	}

	_getTabItem = (key: string) => {
		switch (key) {
			case 'TAB 1':
				return (
					<View style={{ flex: 1, top: 140, borderColor: 'red', borderWidth: 10 }}>
						<Text style={{ fontSize: 30 }}>TAB 1</Text>
					</View>
				);
			case 'TAB 2':
				return (
					<View style={{ flex: 1, top: 140, borderColor: 'green', borderWidth: 20 }}>
						<Text style={{ fontSize: 30 }}>TAB 2</Text>
					</View>
				);
			case 'TAB 3':
				return (
					<View style={{ flex: 1, top: 140, borderColor: 'darkblue', borderWidth: 30 }}>
						<Text style={{ fontSize: 30 }}>TAB 3</Text>
					</View>
				);
			case 'TAB 4':
				return (
					<View style={{ flex: 1, top: 140, borderColor: 'deeppink', borderWidth: 40 }}>
						<Text style={{ fontSize: 30 }}>TAB 4</Text>
					</View>
				);
			default:
				return null;
		}
	}

	_setTab(key) {
		return () => {
			console.log('Selecting tab: ', key);
			this.setState({ activeTab: key });
		};
	}

	render() {
		return (
			<TabBarIOS>
				{
					this._tabItems.map((tabItem) => {
						return (
							<TabBarIOS.Item
								key={tabItem.key}
								onPress={this._setTab(tabItem.key)}
								title={tabItem.key}
								selected={tabItem.key === this.state.activeTab}
							>
								{ this._getTabItem(tabItem.key) }
							</TabBarIOS.Item>
						);
					})
				}
			</TabBarIOS>
		);
	}
}

AppRegistry.registerComponent('tvTabBar', () => { return tvTabBar; });

link to project in gitlab: https://gitlab.com/radamich/tvTabBar

@hramos
Copy link
Contributor

hramos commented Jul 18, 2017

cc @dlowder-salesforce

@douglowder
Copy link
Contributor

Thanks for the bug report, I'll take a look.... might be a race condition between the native focus engine calls and JS

@douglowder
Copy link
Contributor

@rada While I'm working on this, I have a suggested code change that seems to remove most of the flickering.

    _setTab(key) {
        return () => {
            if(this.state.activeTab !== key) {
                console.log('Selecting tab: ', key);
                this.setState({ activeTab: key });
            }
        };
    }

@rada
Copy link
Author

rada commented Jul 18, 2017

@dlowder-salesforce thanks, the switching indeed improves by the condition however in case we have a more complex component, the tabs still flicker. Let me know if you need an example with such component.

douglowder added a commit to douglowder/react-native that referenced this issue Jul 19, 2017
@douglowder
Copy link
Contributor

@rada I have a fix, please feel free to try it out and let me know.

@rada
Copy link
Author

rada commented Jul 19, 2017

@dlowder-salesforce thanks, this fix works in case the component in the selected tab already finished rendering. However it's not working if we switch tab while the tab is not fully rendered yet.

We are developing an application for AppleTV and once the first tab is shown, we fetch data from GraphQL server and display it in a ListView. The problem is that if we switch tabs during ListView rendering (renderRow), the tabs are again flickering and jumping around.

I have updated the example at https://gitlab.com/radamich/tvTabBar and added a listView in the first tab and also timeout in componentDidMount. After 3 seconds, the listView is updated and is rendering 2000 rows (just for testing purposes so there's time to switch tabs, we don't have this amount of data in our app).

Steps to reproduce:
Scenario 1:

  1. Start the application
  2. Once the TAB 1 is loaded, wait 3 seconds for ListView to render data
  3. When the ListView starts rendering (you should see it in the TAB 1), switch to TAB 2
  4. you should see tab selection jumping back and forth

Scenario 2:

  1. Start the application
  2. Once TAB 1 is loaded, immediately switch to TAB 2 (or TAB 3/4)
  3. When the ListView (in TAB 1) starts rendering (after 3 seconds since app start, you can confirm it in the console log), switch to TAB 2 or 3 or 4.
  4. you should see tab selection jumping back and forth

Let me know if you need any more information. Thanks.

List.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  Text,
  View,
  ListView,
} from 'react-native';

export default class List extends Component {

	
	constructor(props) {
		super(props);

		this.ds = new ListView.DataSource({ rowHasChanged: (r1: Object, r2: Object) => { return r1 !== r2; } });

		this.state = {
			dataSource: this.ds.cloneWithRows([{ value: 1 }]),
		}
	};

	_renderRow(rowData) {
		console.log('RenderRow: ', rowData.value);
		return (
			<View>
				<Text>{rowData.value}</Text>
			</View>
		)
	}
	
	componentDidMount() {
		setTimeout(() => {
			const data = Array.from({ length: 2000 }).map((_, index) => {
				return { value: index };
			})
			this.setState({
				dataSource: this.ds.cloneWithRows(data),
			})
		}, 3000)
	}
	
	render() {
		return (
			<View>
				<ListView
					dataSource={this.state.dataSource}
					enableEmptySections
					removeClippedSubviews={false}
					renderRow={this._renderRow}
				/>
			</View>
		);
	}
}

index.ios.js/index.tvos.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  Text,
  View,
  TabBarIOS,
  ListView,
} from 'react-native';

import List from './List';

export default class tvTabBar extends Component {

	_tabItems: Array<{ key: string }> = [
		{ key: 'TAB 1' },
		{ key: 'TAB 2' },
		{ key: 'TAB 3' },
		{ key: 'TAB 4' },
	];

	constructor(props) {
		super(props);
		this.state = { activeTab: 'TAB 1' };
	}

	_getTabItem = (key: string) => {
		switch (key) {
			case 'TAB 1':
				return (
					<View style={{ flex: 1, top: 140, borderColor: 'red', borderWidth: 10 }}>
						<List />
						<Text style={{ fontSize: 30 }}>TAB 1</Text>
					</View>
				);
			case 'TAB 2':
				return (
					<View style={{ flex: 1, top: 140, borderColor: 'green', borderWidth: 20 }}>
						<Text style={{ fontSize: 30 }}>TAB 2</Text>
					</View>
				);
			case 'TAB 3':
				return (
					<View style={{ flex: 1, top: 140, borderColor: 'darkblue', borderWidth: 30 }}>
						<Text style={{ fontSize: 30 }}>TAB 3</Text>
					</View>
				);
			case 'TAB 4':
				return (
					<View style={{ flex: 1, top: 140, borderColor: 'deeppink', borderWidth: 40 }}>
						<Text style={{ fontSize: 30 }}>TAB 4</Text>
					</View>
				);
			default:
				return null;
		}
	}

	_setTab(key) {
		return () => {
			console.log('Selecting tab: ', key);
			this.setState({ activeTab: key });
		};
	}

	render() {
		return (
			<TabBarIOS>
				{
					this._tabItems.map((tabItem) => {
						return (
							<TabBarIOS.Item
								key={tabItem.key}
								onPress={this._setTab(tabItem.key)}
								title={tabItem.key}
								selected={tabItem.key === this.state.activeTab}
							>
								{ this._getTabItem(tabItem.key) }
							</TabBarIOS.Item>
						);
					})
				}
			</TabBarIOS>
		);
	}
}

AppRegistry.registerComponent('tvTabBar', () => { return tvTabBar; });

@douglowder
Copy link
Contributor

@rada that's very interesting, I will try that out. I take it that this problem does not occur in iOS?

@douglowder
Copy link
Contributor

@rada I tried out your test case.... With my fix, I was still able to reproduce scenario 1 some of the time, but not scenario 2. It seems to me that when the 2000 rows show up, the entire app (including the tab bar) is rerendered, not just the tab with the list view. I'll see if I can figure out a way to prevent that.

@rada
Copy link
Author

rada commented Jul 20, 2017

@dlowder-salesforce in iOS, tabBar is working fine, we don't see this problem.
Let me know if you need any more info/test.

douglowder added a commit to douglowder/react-native that referenced this issue Jul 23, 2017
@douglowder
Copy link
Contributor

@rada please try out https://github.com/dlowder-salesforce/react-native/tree/tvos-tabbar-fix -- I think this should fix the remaining issues. I tried it with your ListView example code above and it seems to be behaving correctly.

@VladaGit
Copy link

VladaGit commented Jul 25, 2017

@dlowder-salesforce Using "react-native": "git+https://github.com/dlowder-salesforce/react-native.git#tvos-tabbar-fix",
Can not reproduce the issue on the project anymore after using the tvos-tabbar-fix.
Thank you.

@facebook facebook locked as resolved and limited conversation to collaborators Aug 10, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Aug 10, 2018
sheck added a commit to sheck/react-native that referenced this issue Apr 10, 2019
sheck added a commit to sheck/react-native that referenced this issue Apr 11, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests

5 participants