Anonymous
UX не отображается одинаково на iOS и Expo
Сообщение
Anonymous » 04 июл 2024, 19:33
Я использую React Native для разработки пользовательского интерфейса мобильного приложения. На следующем снимке экрана показано, как я хочу, чтобы мой экран выглядел при визуализации с помощью Expo на моем компьютере:
Просмотр с компьютера
Однако, когда я создаю IPA и просматриваю его на на моем iPad макет отличается:
вид с iPad
Появляются поля, а заголовок и оставшиеся минуты не соответствуют желаемому расположению. Вот код с моего экрана:
Код: Выделить всё
javascript
`
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Image, TouchableOpacity, ScrollView, useWindowDimensions, Dimensions, Alert, Modal, Platform } from 'react-native';
import { theme } from '../utils/theme';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
const LaunchedClassScreen = ({ navigation, route }) => {
const { activities, targetFinishTime } = route.params;
const [currentActivityIndex, setCurrentActivityIndex] = useState(0);
const [startTime] = useState(Math.floor(Date.now() / 1000)); // Track the start time, do not reset
const [timer, setTimer] = useState(0);
const [totalDuration, setTotalDuration] = useState(0);
const [isCancelModalVisible, setIsCancelModalVisible] = useState(false);
const [estimatedFinishTime, setEstimatedFinishTime] = useState(0);
const window = useWindowDimensions();
const isLandscape = window.width > window.height;
const activity = activities[currentActivityIndex];
const nextActivity = currentActivityIndex < activities.length - 1 ? activities[currentActivityIndex + 1] : null;
useEffect(() => {
const totalActivitiesDuration = activities.reduce((acc, curr) => acc + curr.Duration, 0);
setTotalDuration(totalActivitiesDuration);
const timerId = setInterval(() => {
const currentTime = Math.floor(Date.now() / 1000);
const elapsedTime = currentTime - startTime;
const remainingActivitiesDuration = activities.slice(currentActivityIndex).reduce((acc, curr) => acc + curr.Duration, 0);
const newEFT = currentTime + remainingActivitiesDuration;
setEstimatedFinishTime(newEFT);
setTimer(elapsedTime);
}, 1000);
return () => clearInterval(timerId);
}, [activities, currentActivityIndex, startTime, totalDuration]);
const determineBarColor = () => estimatedFinishTime > targetFinishTime ? 'red' : 'green';
const determineTextColor = () => estimatedFinishTime > targetFinishTime ? 'red' : 'black';
const timeLeftText = () => {
const currentTime = Math.floor(Date.now() / 1000);
const timeLeftInSeconds = targetFinishTime - currentTime;
const minutesLeft = Math.floor(timeLeftInSeconds / 60);
return `${minutesLeft} mins left`;
};
const deltaTimeText = () => {
const deltaTime = estimatedFinishTime - targetFinishTime;
return deltaTime > 0 ? `${Math.ceil(deltaTime / 60)} mins over` : `${Math.abs(Math.floor(deltaTime / 60))} mins ahead`;
};
const formatTime = (totalSeconds) => {
if (totalSeconds < 0) return ""; // Avoid negative time display
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
if (minutes > 0 && seconds > 0) {
return `${minutes} mins ${seconds}`;
} else if (minutes > 0) {
return `${minutes} mins`;
} else if (seconds > 0) {
return `${seconds}`;
} else {
return "0 secs";
}
};
const goToNextActivity = () => {
if (currentActivityIndex < activities.length - 1) {
setCurrentActivityIndex(currentActivityIndex + 1);
}
};
const goToPreviousActivity = () => {
if (currentActivityIndex > 0) {
setCurrentActivityIndex(currentActivityIndex - 1);
}
};
const completeClass = () => {
navigation.navigate('Home');
};
const handleCancelClass = () => {
Alert.alert(
"Cancel Class",
"Are you sure you want to cancel the class?",
[
{ text: "No", style: "cancel" },
{
text: "Yes, cancel it",
style: "destructive",
onPress: () => {
setIsCancelModalVisible(false);
navigation.navigate('Home');
},
},
],
{ cancelable: true }
);
};
useEffect(() => {
const subscription = Dimensions.addEventListener('change', ({ window }) => {
// Handle the orientation change logic here if needed
});
return () => subscription.remove();
}, []);
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: theme.colors.background,
},
mainSection: {
flex: 3,
padding: theme.spacing.medium,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: theme.colors.background,
marginTop: theme.spacing.medium,
paddingRight: theme.spacing.medium,
},
activitiesLeftText: {
fontSize: Math.min(window.width, window.height) * 0.05,
fontWeight: 'bold',
color: theme.colors.primary,
},
activityDetails: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: theme.spacing.medium,
marginBottom: 0,
},
activityTitle: {
fontSize: Math.min(window.width, window.height) * 0.07,
fontWeight: 'bold',
color: theme.colors.primary,
textAlign: 'center',
flex: 1,
},
repetitionText: {
fontSize: theme.fontSize.large,
fontWeight: 'bold',
color: theme.colors.primary,
textAlign: 'right',
marginLeft: theme.spacing.medium,
},
activityImage: {
width: '100%',
height: window.height * 0.65,
resizeMode: 'contain',
},
descriptionText: {
fontSize: theme.fontSize.small,
color: theme.colors.gray,
textAlign: 'center',
padding: theme.spacing.small,
},
footer: {
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
padding: theme.spacing.small,
backgroundColor: theme.colors.backgroundLight,
},
backButton: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: theme.spacing.small,
paddingHorizontal: theme.spacing.small,
},
backIcon: {
fontSize: theme.iconSize.small,
color: theme.colors.primary,
},
backText: {
fontSize: theme.fontSize.small,
color: theme.colors.primary,
marginLeft: theme.spacing.xsmall,
},
nextActivitySection: {
flex: 1,
backgroundColor: theme.colors.backgroundLight,
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing.medium,
width: '100%',
marginTop: theme.spacing.small,
},
nextText: {
fontSize: theme.fontSize.medium,
fontWeight: 'bold',
color: theme.colors.subtitle,
marginBottom: theme.spacing.small,
},
nextActivityImage: {
width: '80%',
height: undefined,
opacity: 0.5,
aspectRatio: 4 / 3,
resizeMode: 'contain',
},
nextActivityTitle: {
fontSize: theme.fontSize.small,
color: theme.colors.subtitle,
marginBottom: theme.spacing.small,
},
completeButtonContainer: {
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing.medium,
},
completeButton: {
backgroundColor: theme.colors.primary,
borderRadius: theme.borderRadius.medium,
paddingVertical: theme.spacing.medium,
paddingHorizontal: theme.spacing.large,
elevation: 3,
},
completeButtonText: {
color: theme.colors.white,
fontSize: theme.fontSize.medium,
fontWeight: 'bold',
textAlign: 'center',
},
progressContainer: {
width: '100%',
paddingHorizontal: 10,
paddingBottom: 10,
alignItems: 'center',
},
progressBar: {
height: 20,
backgroundColor: '#cccccc',
position: 'relative',
},
progress: {
height: '100%',
},
progressText: {
width: '15%',
textAlign: 'right',
paddingLeft: 10,
},
centeredView: {
flex: 1,
justifyContent: "center",
alignItems: "center",
marginTop: 22
},
modalView: {
margin: 20,
backgroundColor: "white",
borderRadius: 20,
padding: 35,
alignItems: "center",
shadowColor: "#000",
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
},
modalButton: {
borderRadius: 20,
padding: 10,
elevation: 2,
flexDirection: 'row',
alignItems: 'center',
},
modalCloseButton: {
backgroundColor: "#2196F3",
},
textStyle: {
color: "white",
fontWeight: "bold",
textAlign: "center",
},
modalText: {
marginBottom: 15,
textAlign: "center",
marginLeft: 10,
},
});
const calculateSegmentWidths = () => {
const totalActivitiesDuration = activities.reduce((acc, curr) => acc + curr.Duration, 0);
return activities.map(activity => (activity.Duration / totalActivitiesDuration) * 100);
};
const renderTimeSegments = () => {
const elapsedTime = Math.min(timer, totalDuration); // Ensure we don't exceed total duration
const progress = Math.min(elapsedTime / totalDuration, 1);
return (
{timeLeftText()}
);
};
const renderActivitySegments = () => {
const segmentWidths = calculateSegmentWidths();
return (
{segmentWidths.map((width, index) => (
))}
{deltaTimeText()}
);
};
const formatRepetitionAndTime = (repetitions, durationInSeconds) => {
const duration = formatTime(durationInSeconds);
if (repetitions && durationInSeconds) {
return `${repetitions} times (${duration})`;
} else if (repetitions) {
return `${repetitions} times`;
} else if (durationInSeconds) {
return `${duration}`;
}
return '';
};
return (
{activities.length - currentActivityIndex } LEFT
setIsCancelModalVisible(true)}
/>
{activity && (
{activity.Name.toUpperCase()}
{formatRepetitionAndTime(activity.Repetitions, activity.Duration)}
{activity.StaffOnlyNotes}
)}
{nextActivity && (
NEXT
{nextActivity?.Name.toUpperCase()}
)}
{currentActivityIndex === activities.length - 1 && (
COMPLETE CLASS
)}
{renderTimeSegments()}
{renderActivitySegments()}
{currentActivityIndex > 0 && (
BACK
)}
{
setIsCancelModalVisible(!isCancelModalVisible);
}}
>
Are you sure you want to cancel the class?
Yes, cancel it
setIsCancelModalVisible(false)}
>
No
);
};
export default LaunchedClassScreen;
Я пытался изменить стили несколько раз и ожидал, что они будут выглядеть одинаково как на Expo, так и на iPad, но различия все еще присутствуют.
Подробнее здесь:
https://stackoverflow.com/questions/787 ... s-and-expo
1720110790
Anonymous
Я использую React Native для разработки пользовательского интерфейса мобильного приложения. На следующем снимке экрана показано, как я хочу, чтобы мой экран выглядел при визуализации с помощью Expo на моем компьютере: Просмотр с компьютера Однако, когда я создаю IPA и просматриваю его на на моем iPad макет отличается: вид с iPad Появляются поля, а заголовок и оставшиеся минуты не соответствуют желаемому расположению. Вот код с моего экрана: [code]javascript ` import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, Image, TouchableOpacity, ScrollView, useWindowDimensions, Dimensions, Alert, Modal, Platform } from 'react-native'; import { theme } from '../utils/theme'; import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'; const LaunchedClassScreen = ({ navigation, route }) => { const { activities, targetFinishTime } = route.params; const [currentActivityIndex, setCurrentActivityIndex] = useState(0); const [startTime] = useState(Math.floor(Date.now() / 1000)); // Track the start time, do not reset const [timer, setTimer] = useState(0); const [totalDuration, setTotalDuration] = useState(0); const [isCancelModalVisible, setIsCancelModalVisible] = useState(false); const [estimatedFinishTime, setEstimatedFinishTime] = useState(0); const window = useWindowDimensions(); const isLandscape = window.width > window.height; const activity = activities[currentActivityIndex]; const nextActivity = currentActivityIndex < activities.length - 1 ? activities[currentActivityIndex + 1] : null; useEffect(() => { const totalActivitiesDuration = activities.reduce((acc, curr) => acc + curr.Duration, 0); setTotalDuration(totalActivitiesDuration); const timerId = setInterval(() => { const currentTime = Math.floor(Date.now() / 1000); const elapsedTime = currentTime - startTime; const remainingActivitiesDuration = activities.slice(currentActivityIndex).reduce((acc, curr) => acc + curr.Duration, 0); const newEFT = currentTime + remainingActivitiesDuration; setEstimatedFinishTime(newEFT); setTimer(elapsedTime); }, 1000); return () => clearInterval(timerId); }, [activities, currentActivityIndex, startTime, totalDuration]); const determineBarColor = () => estimatedFinishTime > targetFinishTime ? 'red' : 'green'; const determineTextColor = () => estimatedFinishTime > targetFinishTime ? 'red' : 'black'; const timeLeftText = () => { const currentTime = Math.floor(Date.now() / 1000); const timeLeftInSeconds = targetFinishTime - currentTime; const minutesLeft = Math.floor(timeLeftInSeconds / 60); return `${minutesLeft} mins left`; }; const deltaTimeText = () => { const deltaTime = estimatedFinishTime - targetFinishTime; return deltaTime > 0 ? `${Math.ceil(deltaTime / 60)} mins over` : `${Math.abs(Math.floor(deltaTime / 60))} mins ahead`; }; const formatTime = (totalSeconds) => { if (totalSeconds < 0) return ""; // Avoid negative time display const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; if (minutes > 0 && seconds > 0) { return `${minutes} mins ${seconds}`; } else if (minutes > 0) { return `${minutes} mins`; } else if (seconds > 0) { return `${seconds}`; } else { return "0 secs"; } }; const goToNextActivity = () => { if (currentActivityIndex < activities.length - 1) { setCurrentActivityIndex(currentActivityIndex + 1); } }; const goToPreviousActivity = () => { if (currentActivityIndex > 0) { setCurrentActivityIndex(currentActivityIndex - 1); } }; const completeClass = () => { navigation.navigate('Home'); }; const handleCancelClass = () => { Alert.alert( "Cancel Class", "Are you sure you want to cancel the class?", [ { text: "No", style: "cancel" }, { text: "Yes, cancel it", style: "destructive", onPress: () => { setIsCancelModalVisible(false); navigation.navigate('Home'); }, }, ], { cancelable: true } ); }; useEffect(() => { const subscription = Dimensions.addEventListener('change', ({ window }) => { // Handle the orientation change logic here if needed }); return () => subscription.remove(); }, []); const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', backgroundColor: theme.colors.background, }, mainSection: { flex: 3, padding: theme.spacing.medium, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: theme.colors.background, marginTop: theme.spacing.medium, paddingRight: theme.spacing.medium, }, activitiesLeftText: { fontSize: Math.min(window.width, window.height) * 0.05, fontWeight: 'bold', color: theme.colors.primary, }, activityDetails: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', paddingHorizontal: theme.spacing.medium, marginBottom: 0, }, activityTitle: { fontSize: Math.min(window.width, window.height) * 0.07, fontWeight: 'bold', color: theme.colors.primary, textAlign: 'center', flex: 1, }, repetitionText: { fontSize: theme.fontSize.large, fontWeight: 'bold', color: theme.colors.primary, textAlign: 'right', marginLeft: theme.spacing.medium, }, activityImage: { width: '100%', height: window.height * 0.65, resizeMode: 'contain', }, descriptionText: { fontSize: theme.fontSize.small, color: theme.colors.gray, textAlign: 'center', padding: theme.spacing.small, }, footer: { flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', padding: theme.spacing.small, backgroundColor: theme.colors.backgroundLight, }, backButton: { flexDirection: 'row', alignItems: 'center', paddingVertical: theme.spacing.small, paddingHorizontal: theme.spacing.small, }, backIcon: { fontSize: theme.iconSize.small, color: theme.colors.primary, }, backText: { fontSize: theme.fontSize.small, color: theme.colors.primary, marginLeft: theme.spacing.xsmall, }, nextActivitySection: { flex: 1, backgroundColor: theme.colors.backgroundLight, justifyContent: 'center', alignItems: 'center', padding: theme.spacing.medium, width: '100%', marginTop: theme.spacing.small, }, nextText: { fontSize: theme.fontSize.medium, fontWeight: 'bold', color: theme.colors.subtitle, marginBottom: theme.spacing.small, }, nextActivityImage: { width: '80%', height: undefined, opacity: 0.5, aspectRatio: 4 / 3, resizeMode: 'contain', }, nextActivityTitle: { fontSize: theme.fontSize.small, color: theme.colors.subtitle, marginBottom: theme.spacing.small, }, completeButtonContainer: { justifyContent: 'center', alignItems: 'center', padding: theme.spacing.medium, }, completeButton: { backgroundColor: theme.colors.primary, borderRadius: theme.borderRadius.medium, paddingVertical: theme.spacing.medium, paddingHorizontal: theme.spacing.large, elevation: 3, }, completeButtonText: { color: theme.colors.white, fontSize: theme.fontSize.medium, fontWeight: 'bold', textAlign: 'center', }, progressContainer: { width: '100%', paddingHorizontal: 10, paddingBottom: 10, alignItems: 'center', }, progressBar: { height: 20, backgroundColor: '#cccccc', position: 'relative', }, progress: { height: '100%', }, progressText: { width: '15%', textAlign: 'right', paddingLeft: 10, }, centeredView: { flex: 1, justifyContent: "center", alignItems: "center", marginTop: 22 }, modalView: { margin: 20, backgroundColor: "white", borderRadius: 20, padding: 35, alignItems: "center", shadowColor: "#000", shadowOpacity: 0.25, shadowRadius: 4, elevation: 5, }, modalButton: { borderRadius: 20, padding: 10, elevation: 2, flexDirection: 'row', alignItems: 'center', }, modalCloseButton: { backgroundColor: "#2196F3", }, textStyle: { color: "white", fontWeight: "bold", textAlign: "center", }, modalText: { marginBottom: 15, textAlign: "center", marginLeft: 10, }, }); const calculateSegmentWidths = () => { const totalActivitiesDuration = activities.reduce((acc, curr) => acc + curr.Duration, 0); return activities.map(activity => (activity.Duration / totalActivitiesDuration) * 100); }; const renderTimeSegments = () => { const elapsedTime = Math.min(timer, totalDuration); // Ensure we don't exceed total duration const progress = Math.min(elapsedTime / totalDuration, 1); return ( {timeLeftText()} ); }; const renderActivitySegments = () => { const segmentWidths = calculateSegmentWidths(); return ( {segmentWidths.map((width, index) => ( ))} {deltaTimeText()} ); }; const formatRepetitionAndTime = (repetitions, durationInSeconds) => { const duration = formatTime(durationInSeconds); if (repetitions && durationInSeconds) { return `${repetitions} times (${duration})`; } else if (repetitions) { return `${repetitions} times`; } else if (durationInSeconds) { return `${duration}`; } return ''; }; return ( {activities.length - currentActivityIndex } LEFT setIsCancelModalVisible(true)} /> {activity && ( {activity.Name.toUpperCase()} {formatRepetitionAndTime(activity.Repetitions, activity.Duration)} {activity.StaffOnlyNotes} )} {nextActivity && ( NEXT {nextActivity?.Name.toUpperCase()} )} {currentActivityIndex === activities.length - 1 && ( COMPLETE CLASS )} {renderTimeSegments()} {renderActivitySegments()} {currentActivityIndex > 0 && ( BACK )} { setIsCancelModalVisible(!isCancelModalVisible); }} > Are you sure you want to cancel the class? Yes, cancel it setIsCancelModalVisible(false)} > No ); }; export default LaunchedClassScreen; [/code] Я пытался изменить стили несколько раз и ожидал, что они будут выглядеть одинаково как на Expo, так и на iPad, но различия все еще присутствуют. Подробнее здесь: [url]https://stackoverflow.com/questions/78708159/ux-not-shown-the-same-way-on-ios-and-expo[/url]