1import { ExpoConfig } from '@expo/config-types'; 2 3import { createInfoPlistPlugin } from '../plugins/ios-plugins'; 4import { gteSdkVersion } from '../utils/versions'; 5import { addWarningIOS } from '../utils/warnings'; 6import { InfoPlist } from './IosConfig.types'; 7 8export const withRequiresFullScreen = createInfoPlistPlugin( 9 setRequiresFullScreen, 10 'withRequiresFullScreen' 11); 12 13// NOTES: This is defaulted to `true` for now to match the behavior prior to SDK 14// 34, but will change to `false` in SDK +43. 15export function getRequiresFullScreen(config: Pick<ExpoConfig, 'ios' | 'sdkVersion'>) { 16 // Yes, the property is called ios.requireFullScreen, without the s - not "requires" 17 // This is confusing indeed because the actual property name does have the s 18 if (config.ios?.hasOwnProperty('requireFullScreen')) { 19 return !!config.ios.requireFullScreen; 20 } else { 21 // In SDK 43, the `requireFullScreen` default has been changed to false. 22 if ( 23 gteSdkVersion(config, '43.0.0') 24 // TODO: Uncomment after SDK 43 is released. 25 // || !config.sdkVersion 26 ) { 27 return false; 28 } 29 return true; 30 } 31} 32 33const iPadInterfaceKey = 'UISupportedInterfaceOrientations~ipad'; 34 35const requiredIPadInterface = [ 36 'UIInterfaceOrientationPortrait', 37 'UIInterfaceOrientationPortraitUpsideDown', 38 'UIInterfaceOrientationLandscapeLeft', 39 'UIInterfaceOrientationLandscapeRight', 40]; 41 42function isStringArray(value: any): value is string[] { 43 return Array.isArray(value) && value.every((value) => typeof value === 'string'); 44} 45 46function hasMinimumOrientations(masks: string[]): boolean { 47 return requiredIPadInterface.every((mask) => masks.includes(mask)); 48} 49 50/** 51 * Require full screen being disabled requires all ipad interfaces to to be added, 52 * otherwise submissions to the iOS App Store will fail. 53 * 54 * ERROR ITMS-90474: "Invalid Bundle. iPad Multitasking support requires these orientations: 'UIInterfaceOrientationPortrait,UIInterfaceOrientationPortraitUpsideDown,UIInterfaceOrientationLandscapeLeft,UIInterfaceOrientationLandscapeRight'. Found 'UIInterfaceOrientationPortrait,UIInterfaceOrientationPortraitUpsideDown' in bundle 'com.bacon.app'." 55 * 56 * @param interfaceOrientations 57 * @returns 58 */ 59function resolveExistingIpadInterfaceOrientations(interfaceOrientations: any): string[] { 60 if ( 61 // Ensure type. 62 isStringArray(interfaceOrientations) && 63 // Don't warn if it's an empty array, this is invalid regardless. 64 interfaceOrientations.length && 65 // Check if the minimum requirements are met. 66 !hasMinimumOrientations(interfaceOrientations) 67 ) { 68 const existingList = interfaceOrientations!.join(', '); 69 addWarningIOS( 70 'ios.requireFullScreen', 71 `iPad multitasking requires all \`${iPadInterfaceKey}\` orientations to be defined in the Info.plist. The Info.plist currently defines values that are incompatible with multitasking, these will be overwritten to prevent submission failure. Existing: ${existingList}` 72 ); 73 return interfaceOrientations; 74 } 75 return []; 76} 77 78// Whether requires full screen on iPad 79export function setRequiresFullScreen( 80 config: Pick<ExpoConfig, 'ios'>, 81 infoPlist: InfoPlist 82): InfoPlist { 83 const requiresFullScreen = getRequiresFullScreen(config); 84 if (!requiresFullScreen) { 85 const existing = resolveExistingIpadInterfaceOrientations(infoPlist[iPadInterfaceKey]); 86 87 // There currently exists no mechanism to safely undo this feature besides `expo prebuild --clear`, 88 // this seems ok though because anyone using `UISupportedInterfaceOrientations~ipad` probably 89 // wants them to be defined to this value anyways. This is also the default value used in the Xcode iOS template. 90 91 // Merge any previous interfaces with the required interfaces. 92 infoPlist[iPadInterfaceKey] = [...new Set(existing.concat(requiredIPadInterface))]; 93 } 94 95 return { 96 ...infoPlist, 97 UIRequiresFullScreen: requiresFullScreen, 98 }; 99} 100