Fix: SafeArea for iOS dynamic island

Fixes iOS safe area issues caused by the dynamic island.
This commit is contained in:
gpt-engineer-app[bot]
2025-04-05 05:08:29 +00:00
parent af51ba2d52
commit b7b55cc816
4 changed files with 106 additions and 17 deletions

View File

@@ -21,7 +21,17 @@ const Header: React.FC = () => {
// 플랫폼 감지
useEffect(() => {
setIsIOS(isIOSPlatform());
const checkPlatform = async () => {
try {
const isiOS = isIOSPlatform();
console.log('Header: iOS 플랫폼 감지 결과:', isiOS);
setIsIOS(isiOS);
} catch (error) {
console.error('플랫폼 감지 중 오류:', error);
}
};
checkPlatform();
}, []);
// 이미지 프리로딩 처리
@@ -37,7 +47,8 @@ const Header: React.FC = () => {
};
}, []);
const headerClass = isIOS ? 'has-safe-area-top' : 'py-4';
// iOS 전용 헤더 클래스 - 안전 영역 적용
const headerClass = isIOS ? 'ios-notch-padding' : 'py-4';
return (
<header className={headerClass}>

View File

@@ -25,15 +25,22 @@ const SafeAreaContainer: React.FC<SafeAreaContainerProps> = ({
// 마운트 시 플랫폼 확인
useEffect(() => {
setIsIOS(isIOSPlatform());
const checkPlatform = async () => {
const isiOS = isIOSPlatform();
console.log('SafeAreaContainer: 플랫폼 확인 - iOS:', isiOS);
setIsIOS(isiOS);
};
checkPlatform();
}, []);
// 플랫폼에 따른 클래스 결정
let safeAreaClass = '';
let safeAreaClass = 'safe-area-container';
if (isIOS) {
if (!bottomOnly) safeAreaClass += ' has-safe-area-top'; // iOS 상단 안전 영역
if (!topOnly) safeAreaClass += ' has-safe-area-bottom'; // iOS 하단 안전 영역
safeAreaClass += ' ios-safe-area'; // iOS 전용 클래스 추가
} else {
if (!bottomOnly) safeAreaClass += ' pt-4'; // 안드로이드 상단 여백
if (!topOnly) safeAreaClass += ' pb-4'; // 안드로이드 하단 여백
@@ -42,6 +49,30 @@ const SafeAreaContainer: React.FC<SafeAreaContainerProps> = ({
// 추가 하단 여백 적용
const extraBottomClass = extraBottomPadding ? 'pb-[80px]' : '';
// 디버그용 로그 추가
useEffect(() => {
if (isIOS) {
console.log('SafeAreaContainer: iOS 안전 영역 적용됨', {
topOnly,
bottomOnly,
extraBottomPadding
});
// 안전 영역 값 확인 (CSS 변수)
try {
const computedStyle = getComputedStyle(document.documentElement);
console.log('Safe area 변수 값:', {
top: computedStyle.getPropertyValue('--safe-area-top'),
bottom: computedStyle.getPropertyValue('--safe-area-bottom'),
left: computedStyle.getPropertyValue('--safe-area-left'),
right: computedStyle.getPropertyValue('--safe-area-right')
});
} catch (error) {
console.error('CSS 변수 확인 중 오류:', error);
}
}
}, [isIOS, topOnly, bottomOnly]);
return (
<div className={`${safeAreaClass} ${extraBottomClass} ${className}`}>
{children}

View File

@@ -52,11 +52,11 @@
--sidebar-ring: 217.2 91.2% 59.8%;
/* Safe area 값 */
--safe-area-top: env(safe-area-inset-top, 0px);
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
--safe-area-left: env(safe-area-inset-left, 0px);
--safe-area-right: env(safe-area-inset-right, 0px);
/* Safe area 값 - !important 추가하여 우선 적용 */
--safe-area-top: env(safe-area-inset-top, 0px) !important;
--safe-area-bottom: env(safe-area-inset-bottom, 0px) !important;
--safe-area-left: env(safe-area-inset-left, 0px) !important;
--safe-area-right: env(safe-area-inset-right, 0px) !important;
}
.dark {
@@ -150,17 +150,29 @@
@apply neuro-pressed px-4 py-3 w-full focus:outline-none focus:ring-2 focus:ring-neuro-accent/30;
}
/* 안전 영역 관련 클래스 */
/* 안전 영역 관련 클래스 - 강화된 버전 */
.safe-area-container {
width: 100%;
position: relative;
box-sizing: border-box;
}
.ios-safe-area {
/* iOS 전용 안전 영역 처리 */
position: relative;
box-sizing: border-box;
}
.has-safe-area-top {
padding-top: max(1rem, var(--safe-area-top));
padding-top: max(1rem, var(--safe-area-top)) !important;
}
.has-safe-area-bottom {
padding-bottom: max(1rem, var(--safe-area-bottom));
padding-bottom: max(1rem, var(--safe-area-bottom)) !important;
}
.ios-header {
padding-top: max(1rem, var(--safe-area-top));
padding-top: max(1rem, var(--safe-area-top)) !important;
}
/* 모바일 화면에서의 추가 스타일 */
@@ -186,6 +198,11 @@
.SheetContent {
@apply rounded-xl overflow-hidden;
}
/* iOS 고유 노치/다이나믹 아일랜드 영역 고려한 추가 스타일 */
.ios-safe-area-screen {
padding: var(--safe-area-top) var(--safe-area-right) var(--safe-area-bottom) var(--safe-area-left) !important;
}
}
/* 데스크탑 화면에서의 추가 스타일 */
@@ -260,11 +277,21 @@
/* iOS 전용 스타일 */
@supports (-webkit-touch-callout: none) {
.ios-safe-area-top {
padding-top: var(--safe-area-top);
padding-top: var(--safe-area-top) !important;
}
.ios-safe-area-bottom {
padding-bottom: var(--safe-area-bottom);
padding-bottom: var(--safe-area-bottom) !important;
}
/* iOS에서 노치/다이나믹 아일랜드 영역 처리를 위한 추가 클래스 */
.ios-notch-padding {
padding-top: max(1.5rem, var(--safe-area-top)) !important;
}
/* iOS에서 하단 홈 인디케이터 영역을 위한 추가 클래스 */
.ios-bottom-padding {
padding-bottom: max(1.5rem, var(--safe-area-bottom)) !important;
}
}

View File

@@ -12,6 +12,26 @@ import { useDataInitialization } from '@/hooks/useDataInitialization';
import { useIsMobile } from '@/hooks/use-mobile';
import useNotifications from '@/hooks/useNotifications';
import SafeAreaContainer from '@/components/SafeAreaContainer';
import { BudgetData } from '@/contexts/budget/types';
// 기본 예산 데이터 (빈 객체 대신 사용할 더미 데이터)
const defaultBudgetData: BudgetData = {
daily: {
targetAmount: 0,
spentAmount: 0,
remainingAmount: 0
},
weekly: {
targetAmount: 0,
spentAmount: 0,
remainingAmount: 0
},
monthly: {
targetAmount: 0,
spentAmount: 0,
remainingAmount: 0
}
};
// 메인 컴포넌트
const Index = () => {
@@ -69,7 +89,7 @@ const Index = () => {
try {
console.log('Index 페이지 마운트, 현재 데이터 상태:');
console.log('트랜잭션:', transactions?.length || 0);
console.log('예산 데이터:', budgetData);
console.log('예산 데이터:', budgetData || defaultBudgetData);
// 페이지 첫 마운트 시에만 실행되는 로직으로 수정
const isFirstMount = sessionStorage.getItem('initialDataLoaded') !== 'true';
@@ -194,7 +214,7 @@ const Index = () => {
<HomeContent
transactions={transactions || []}
budgetData={budgetData || {}}
budgetData={budgetData || defaultBudgetData}
selectedTab={selectedTab}
setSelectedTab={setSelectedTab}
handleBudgetGoalUpdate={handleBudgetGoalUpdate}