I have an application which displays a view inside another view with appcelerator 5.1.2.
I want to animate the view to appear from bottom when opened, but could not figure out how to do it.
I have already done the exact opposite which is to make it disappear to bottom. Here is the code :
function closeViewFromBottom(animationView) {
var newtop = Ti.Platform.displayCaps.platformHeight + 20;
animationView.animate({
top:newtop,
duration:1000,
autoreverse: false
});
};
You can set the start top value to platformHeight and then animate top to 0. Also set the height of that view to platformHeight. For iOS you can also set the outer-view clipMode to Titanium.UI.iOS.CLIP_MODE_ENABLED
Edit: full example
index.tss
"Window": {
backgroundColor: "#fff"
}
"#view":{
backgroundColor: "red",
width:Ti.UI.FILL,
height:Ti.UI.FILL,
}
index.xml
<Alloy>
<Window >
<View id="view"/>
<Button id="btn" title="show view"/>
</Window>
</Alloy>
index.js
$.view.top = Ti.Platform.displayCaps.platformHeight;
function openViewFromBottom(animationView) {
animationView.top = Ti.Platform.displayCaps.platformHeight;
animationView.animate({
top:0,
duration:1000
});
};
function onClickButton(e){
openViewFromBottom($.view);
}
$.btn.addEventListener("click",onClickButton);
$.index.open();
Related
I'm currently trying to set a full background image on my login view.
I've used the following code to do so but noticed that Iphone 11 have like a white bar at the bottom and top of the phone. Is this something that can't be overlapped since it's like the navigation?
const Background = styled.ImageBackground`
padding:20px;
justify-content:center;
width:100%;
height:100%;
`
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
enter image description here
return <SafeAreaView style={styles.container}><Background source={require('../../assets/images/background-cover.jpg')}>
<CoverLogo width={100} height={100} color={Colors.White} />
<Introduction loop={false}>
<TextHeading text={`#test`} />
<TextPage text={`Btest2`} />
<TextPage text={`Ttest3`} />
<TextPage text={`test4 Sign up !`} />
</Background>
</SafeAreaView>
I'm not sure if this will help with the bottom of the screen, but for the top you can try and set your header style in navigationOptions like so if using react navigation
headerStyle: {
backgroundColor: 'transparent',
position: 'absolute',
top: 0,
left: 0,
right: 0,
borderBottomWidth: 0 // removes the border on the bottom
}
If you follow the SafeArea advice also given in the other answer, then this may be of help too https://reactnavigation.org/docs/handling-safe-area/
Try using :
SafeArea as a parent view, with flex:1
Or maybe have a function to check for iPhone 11/ iPhoneX like that:
isIphoneX: () => {
const dimen = Dimensions.get('window');
const deviceModel = DeviceInfo.getModel();
return (
deviceModel === 'iPhone X' ||
(Platform.OS === 'ios' &&
!Platform.isPad &&
!Platform.isTVOS &&
(dimen.height === 812 ||
dimen.width === 812 ||
dimen.height === 896 ||
dimen.width === 896))
);
},
iPhones Specs
your issue lies in your use of
<SafeAreaView> tag which limits the use of the screen space in ios devices below the notch and just above the lower edge of the screen. The use of SafeAreaView is only visible in the effects only on ios devices.
To resolve your issue I would suggest using the tag <View> instead of <SafeAreaView>
This is my first post, so sorry for any errors.
I'm trying to make a series of nested navigators, starting from a Switch navigator, then going to a Drawer Navigator, that for each of it's sections, it calls a different Stack Navigator, like this:
SwitchNavigator
DrawerNavigator
MapStackNavigator
Map Screen
ProfileStackNavigator
Profile Screen
The initial route for the Drawer Nav is the Stack Nav which calls a Map Screen. The problem is, if I go to the "Profile" Section of the drawer nav, which calls for the Stack Nav that opens the Profile Screen, I'm not beeing able to return to the Map Screen through an onPress() method inside the Navigation headerLeft Icon.
The react-native app is being developed for iOS only, and now as I don't have an iOS device to test on, neither a Mac to use a simulator, I'm currently testing it on my android smartphone.
As I've tested, it wouldn't be a problem if the app was meant to run on android, as the android has the built-in "back" button and it automatically goes back to the previous screen (which is set to the Map Screen inside the DrawerNav). But, as it is for iOS only, we actually need to display a "back" button on the top of the screen (so, inside the StackNav Header) for going back to the Map Screen.
My first guess was to use the navigation.navigate inside the onPress() to call another screen from the current profile StackNav, and that screen calls the first SwitchNav that I've Created to load things up, but it won't work, as it acts like a "navigation loop", and the const SwitchNavigator can't be called from the Profile StackNav.
I've tried to call the StackNav that holds the MapScreen from the onPress(), so it was: click on the back button > it calls the onPress() which calls another screen > that another screen calls the StackNav holding the MapScreen, but it nests one StackNav inside the other.
My Routes.js looks like this:
import Testemapa from './src/components/Map/index';
import MyProfile from './src/components/DrawerNavigator/MyProfile';
import Icon from 'react-native-vector-icons/Ionicons';
import IconE from 'react-native-vector-icons/Entypo';
// The StackNav that holds the Map Screen
const IvesStackNavigator = createStackNavigator ({
screenMap: Testemapa,
},
{
defaultNavigationOptions: ({navigation})=>{
return{
headerRight: (<Icon
style={{paddingRight: 10}}
onPress={() => navigation.openDrawer()}
name="md-menu" size={30}
/>)
};
}
});
// What I tried to do to call the MapScreen, but it ends up nesting it inside
const wayBackSwitchNavigator = createSwitchNavigator ({
transitionToHome: IvesStackNavigator,
});
// The StackNav that holds the Profile Screen
const myProfileStackNavigator = createStackNavigator ({
drawer_myProfile: MyProfile,
transitionPathHome: wayBackSwitchNavigator,
},
{
headerBackTitleVisible: 'true',
defaultNavigationOptions: ({navigation})=>{
return{
//This guy is the Back button, that's actually an Icon
headerLeft: (<IconE
style={{paddingLeft: 10}}
onPress={() => navigation.navigate('transitionPathHome')}
name="chevron-small-left" size={30}
/>),
};
},
});
// The Drawer which calls all of the stacks
const MainDrawerNavigator = createDrawerNavigator ({
BOOK_A_RIDE: {
screen: IvesStackNavigator,
navigationOptions: () =>
({
title: 'BOOK A RIDE'
})
},
MY_EARNINGS: {
screen: ridesStackNavigator,
navigationOptions: () =>
({
title: 'MY EARNINGS'
})
},
MY_PROFILE: {
screen: myProfileStackNavigator,
navigationOptions: () =>
({
title: 'MY PROFILE'
})
},
},
{
initialRouteName: 'BOOK_A_RIDE',
drawerOpenRoute:'DrawerOpen',
drawerPosition: 'right',
drawerBackgroundColor: 'black',
hideStatusBar: 'true',
overlayColor: 'red',
edgeWidth: 100,
contentOptions: {
inactiveBackgroundColor: 'black',
inactiveTintColor: '#fff',
activeBackgroundColor: '#4d4d4d',
activeTintColor: '#fff',
},
contentComponent: props =>
<Container style={{alignItems: 'stretch', marginLeft: 0, marginRight : 0, backgroundColor: 'black'}}>
<Header style={{height:150, marginLeft: 0, marginRight : 0, backgroundColor: 'black'}}>
<Body style={{marginLeft: 0, marginRight : 0, backgroundColor: 'black'}}>
<ImageBackground source={require('./src/assets/splash_ImageLimo.jpg')} style={{left: -10, width: 264, height:150, opacity:0.7, marginLeft: 0, marginRight : 0, paddingRight: 0}}>
</ImageBackground>
</Body>
</Header>
<Content>
<DrawerItems {...props} />
</Content>
</Container>
},
);
// The SwitchNav that is the first Navigator to run on the app
const firstSwitchNavigator = createSwitchNavigator(
{
TestDrawer: MainDrawerNavigator,
},
{
initialRouteName: 'TestDrawer',
}
);
export default createAppContainer(MySwitchNavigator);
So, when The Back button inside the MyProfileStackNavigator is pressed, It was supposed to go back to the MapScreen which is holded by the IvesStackNavigator, which is inside the mainDrawerNavigator.
I'm not allowed to let the user open the DrawerNav from the Profile Screen, so the back button is really important, as it's the only way to go back to the map.
If I try to directly call the SwitchNav or the DrawerNav by using any of them as the Screen path in MyProfileStackNavigator, I get the error that the screen that was called isn't a React component ("The component for route 'transitionToHome' must be a React component...").
If I change it to call IvesStackNavigator directly, the navigator will end up nested inside the MyProfileStackNavigator.
And, if I call the actual map page, it will display the map page inside the MyProfileStackNavigator, having all of the MyProfileStackNavigator properties (like the Back button and the absency of the button to open the drawer menu).
Any help with this would be appreciated, I'm fairly new to React-Native and it's dependencies, and I had to jump in right to coding before learning, as it is a task assigned to me by my boss, so I try to learn while producing the app.
So I am looking to customize the TabBarIOS ui a little bit. Right now the TabBar has an icon field and a name field:
iconName="ios-pin"
title="Map"
Using these results in something like this (with the icon above the text):
However I am trying to get something that looks a like this (with the icon next to the text p.s sorry for the crappy image)
Does anyone have any idea how to do this? Below is my current TabBarIOS code.
Thanks!
renderScene(route, navigator) {
return (
<TabBarIOS
translucent={false}
unselectedTintColor="white"
tintColor="#f9c827"
barTintColor="#2c2c2c">
<Icon2.TabBarItemIOS
iconName="ios-pin"
title="Map"
selectedIconName="ios-pin"
selected={this.state.selectedTab === 'MapTab'}
onPress={() => {
this.setState({
selectedTab: 'MapTab',
});
}}>
{this.renderMap(route, navigator)}
</Icon2.TabBarItemIOS>
<Icon2.TabBarItemIOS
iconName="ios-list-box-outline"
title="List"
selectedIconName="ios-list-box-outline"
selected={this.state.selectedTab === 'ScrollTab'}
onPress={() => {
this.setState({
selectedTab: 'ScrollTab',
toolbar: YELLOW,
navigationBar: NavigationBarYellow,
});
}}>
{this.renderScrollView(route, 'ScrollTab')}
</Icon2.TabBarItemIOS>
</TabBarIOS>
);
}
The simplest way is to use images with text, the other one is to create container viewcontroller and use UIView instead of UITabbar.
That view should have two buttons with icons and texts that you need.
I am using a ScrollView inside of a PanResponder. On Android it works fine but on iOS the ScrollView will not scroll. I did some investigation and here are some facts:
If I put a break point in PanResponder.onMoveShouldSetPanResponder(), before I step over, the scrollView will scroll as normal but once I release the break point, the scrollView stops working.
If I modify ScrollResponder.js, and return true in scrollResponderHandleStartShouldSetResponderCapture() - it used to return false at runtime; and return false in scrollResponderHandleTerminationRequest(), the scrollView works OK but of course, since it swallows the event the outer PanResponder will not get the event.
So the questions are:
I want to make the scrollview to work, and not to swallow the event. Any one know what's the approach?
How the responding system works on iOS? The react-native responder system doc does not explain that to me.
To enable scrolling in a ScrollView that is a child of a parent with PanResponder, you have to make sure the ScrollView is the responder to any gesture inside of it. By default, gesture events bubble up from the deepest component to the parent component. In order to capture the event by the ScrollView, you can add a View with a PanResponder inside of it. See the (pseudo) example below, where ChildComponent is a child of a parent with PanResponder.
const ChildComponent = ({ theme }) => {
const panResponder = React.useRef(
PanResponder.create({
// Ask to be the responder:
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
})
).current;
return (
<ScrollView style={{ height: 500 }}>
<View {...panResponder.panHandlers}>
...
</View>
</ScrollView>
);
};
Within PanResponder is an event that returns the current touch position. You can use that to compare 2 values to perform a scroll.
I finally solve this by wrap the scrollview inside a view ,and set the style of scrollview a limited height.
import * as React from 'react';
import { Text, View, StyleSheet,PanResponder,Animated,ScrollView,Dimensions} from 'react-native';
import { Constants } from 'expo';
const WINDOW_WIDTH = Dimensions.get("window").width;
const WINDOW_HEIGHT = Dimensions.get("window").height;
// You can import from local files
export default class App extends React.Component {
constructor(props){
super(props);
this.minTop = 100;
this.maxTop = 500;
this.state={
AnimatedTop:new Animated.Value(0),
}
}
componentWillMount(){
let that = this;
this._previousTop = 100;
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder(){
return true;
},
onPanResponderGrant(){
that._previousTop = that.state.AnimatedTop.__getValue();
return true;
},
onPanResponderMove(evt,gestureState){
let currentTop = that._previousTop + gestureState.dy;
that.state.AnimatedTop.setValue(that._previousTop+gestureState.dy);
},
onPanResponderRelease(){
}
})
}
render() {
return (
<View style={styles.container}>
<Animated.View
style={[styles.overlay,{top:this.state.AnimatedTop}]}
{...this._panResponder.panHandlers}
>
<View style={{height:200,backgroundColor:"black"}}></View>
<View>
<ScrollView
style={{height:500}}
>
<View style={{backgroundColor:"blue",height:200}}></View>
<View style={{backgroundColor:"yellow",height:200}}></View>
<View style={{backgroundColor:"pink",height:200}}></View>
<View style={{backgroundColor:"red",height:200}}></View>
</ScrollView>
</View>
</Animated.View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
overlay:{
position:"absolute",
width:WINDOW_WIDTH,
height:WINDOW_HEIGHT-100,
}
});
enter link description here
I spent plenty of time to solve this.Hope this will help someone confused with the same problem.
I don't know if it's usefull right now but you can add that line,
return !(gestureState.dx === 0 && gestureState.dy === 0)
in 'onMoveShouldSetPanResponder' property of PanResponder with evt and gestureState as parameters.
Your PanResponder should look like this :
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder(evt, gestureState){
return !(gestureState.dx === 0 && gestureState.dy === 0)
},
onPanResponderGrant(){
that._previousTop = that.state.AnimatedTop.__getValue();
return true;
},
onPanResponderMove(evt,gestureState){
let currentTop = that._previousTop + gestureState.dy;
that.state.AnimatedTop.setValue(that._previousTop+gestureState.dy);
},
onPanResponderRelease(){
}
})
I have solved this by adding onPress handlers to all of the contents of the ScrollView - doesn't matter if the handler is a no-op, the scroll view still works fine.
I'm not sure you still need this or not but I'll put to help others as well
If you put the panResponder on a sibling view of the ScrollView, the ScrollView behaves properly. then you can position that sibling view
here is an example of the workaround.
I solved this issue by doing it
onMoveShouldSetPanResponder: (event, gesture) => {
if (gesture?.moveX > gesture?.moveY) {
return false;
}
return true;
},
On my case, I have a horizontal Flatlist inside a PanResponder...
We have a simple mobile app running in Mobile Safari (MS) on iOS. When the user scrolls down the page n pixels, a "top" button slides up from the bottom. The top button is fixed position. Problem is, when you start scrolling in MS, the navigation and toolbar UI is hidden. When you tap the "top" button, it reveals the bottom toolbar and a second tap is required to tap the "top" button. Is there any way to disable the default "tap on the bottom part of the viewport to reveal the toolbar" behavior so our top button works as expected (i.e. jumps to the top of the page with one click, not two?
No there is not. You can control the content of your webpage but not the behavior of the safari app.
The simple solution here is to add about 50px padding-bottom on your bottom most div. Safari seems to think that you are trying to access the bottom navigation bar, unless you click well above the bottom area. With extra padding at bottom, the user will click much higher on the page (not always, but in general).
Mika and typeoneerror are correct, but there is a workaround.
The best workaround solution I found (that doesn't require minimal-ui) is to force the bottom navigation of iOS Safari to always stay open/visible. That way, clicks to the bottom of the window never open the bottom navigation since it's always open.
To do that, you just need to apply some CSS and browser targeting with JS. Detailed steps on how:
How might one force-show the mobile Safari bottom nav bar to show programmatically?
Buttons aligned to bottom of page conflict with mobile Safari's menu bar
For iOS 7.1, you can set this in your header to minimize the UI:
<meta name="viewport" content="width=device-width, minimal-ui">
It was introduced in iOS 7.1 beta 2. This site was instrumental in helping me understand how minimal-ui works: http://www.mobilexweb.com/blog/ios-7-1-safari-minimal-ui-bugs
Here's how I'm dealing with this. With a position:fixed;bottom:0 toolbar of my own, I'm adding 44px offset to it (with a semi-transparent buffer zone) shortly after the safari toolbar is hidden (as this is the scenario where a tap near the bottom will reveal the toolbar again).
var min_inner_height = false;
var max_inner_height = false;
var passiveIfSupported = false;
try {
window.addEventListener("test", null, Object.defineProperty({}, "passive", {
get: function () {
passiveIfSupported = {
passive: true
};
}
}));
} catch (err) {}
document.addEventListener('scroll', function (e) {
var win_inner_h = window.innerHeight;
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
if (min_inner_height === false || win_inner_h < min_inner_height) {
min_inner_height = win_inner_h;
}
if ((max_inner_height === false || win_inner_h > max_inner_height) && win_inner_h > min_inner_height) {
max_inner_height = win_inner_h;
}
if (max_inner_height !== false && max_inner_height == win_inner_h) {
addElementClass(document.body, 'safari-toolbars-hidden');
} else {
removeElementClass(document.body, 'safari-toolbars-hidden');
}
}
}, passiveIfSupported);
This basically adds the .safari-toolbars-hidden class to the <body> sometime around when they disappear due to the user scrolling down the page.
At this point, I move my own toolbar up the page:
.my-bottom-toolbar {
bottom: 0px;
position: fixed;
}
#supports (-webkit-overflow-scrolling: touch) {
/* CSS specific to iOS devices */
.my-bottom-toolbar {
box-shadow: 0 44px 0 rgba(255, 255, 255, 0.8);
transition: bottom 0.15s ease-in-out;
}
.safari-toolbars-hidden .my-bottom-toolbar {
bottom: 44px;
}
}
Hope this helps someone!
Instead of offsetting by a further 44px, you could also add an extra 44px of bottom padding if that works better for your case.
The best solution for me comes from this article.
My solution is with react but simply translated from the articles solution.
import { useWindowHeight } from '#react-hook/window-size/throttled';
//... inside your component
const height = useWindowHeight();
React.useEffect(() => {
const vh = height * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}, [height]);
body {
/* other styles */
height: 100vh;
height: calc(var(--vh, 1vh) * 100);
}
Now when the innerHeight changes the hook is fired and the height variable is adjusted. The window's innerHeight changes when the safari url bar and bottom navigation are hidden so my app fits just right for both situations.