iOS archive submission : Error ITMS-90096


#1

Hi,

I get this ITMS-90096 error when I try to submit a new version of my app for validation. It was working fine before, but I did not submit anything for a few monthes. It seems that Apple introduced some requirements since then.

I never managed launchimages before... Any idea on how I can fix this?

Thanks

 


#2

If my understanding is correct, Introjucer is creating a set of images (Icon-***.png) in Images.xcassets/AppIcon.appiconset. These files are created by the XCode project exporter, in createiOSIconFiles() called from createiOSAssetsFolder().

I am wondering if something similar is missing for the Images.xcassets/LaunchImage.launchimage folder. That folder just contains "Contents.jason" but no image file... It seems normal since "createiOSLaunchimageFiles()" does not exists...

Is it a missing feature, or is there some config I don't know allowing app publishing without specifying the Launchimages?


#3

OK, I finally succeeded to submit my app to iTunesConnect for validation :-)

Here are the changes I made in juce_ProjectExport_XCode.h, see comments starting with (pca) and bold lines. These changes prevent the errors ITMS-90032 (due to deprecated icon declaration), ITMS-90475 (forcing full-screen to be compliant with the new split screen allowing two apps to run simultaneously), and ITMS-90096 (missing launchImages).

Around line 670, next to createiOSIconFiles():


    // (pca) added this method to create a set of black launch images
    void createiOSLaunchImageFiles (File launchImageSet) const
    {
        const Array<ImageType> types (getiOSLaunchImageTypes());
        for (int i = 0; i < types.size(); ++i)
        {
            const ImageType type = types.getUnchecked(i);
            // Create default black launch image
            Image image (Image::ARGB, type.width, type.height, true);
            Graphics g(image);
            g.fillAll(Colours::black);
            MemoryOutputStream pngData;
            PNGImageFormat pngFormat;
            pngFormat.writeImageToStream (image, pngData);
            overwriteFileIfDifferentOrThrow (launchImageSet.getChildFile (type.filename), pngData);
        }
    }

Around line 606:


    void writeInfoPlistFile() const
    {
        if (! xcodeCreatePList)
            return;
.....
        addPlistDictionaryKey (dict, "CFBundleExecutable",          "${EXECUTABLE_NAME}");
        // (pca) Removed this line because it causes a validation error during app publishing (error ITMS-90032)
        // addPlistDictionaryKey (dict, "CFBundleIconFile",            iconFile.exists() ? iconFile.getFileName() : String::empty);
        addPlistDictionaryKey (dict, "CFBundleIdentifier",          project.getBundleIdentifier().toString());
        addPlistDictionaryKey (dict, "CFBundleName",                projectName);
        addPlistDictionaryKey (dict, "CFBundlePackageType",         xcodePackageType);
        addPlistDictionaryKey (dict, "CFBundleSignature",           xcodeBundleSignature);
......
        if (iOS)
        {
            // (pca) needed to make the app compatible with new iOS multi-threading feature.
            // Forcing full screen allows to keep default behavior by disabling split screen feature (to prevent the error ITMS-90475)
            addPlistDictionaryKeyBool (dict, "UIRequiresFullScreen", true);
            static const char* kDefaultiOSOrientationStrings[] =
            {
                "UIInterfaceOrientationPortrait",
                "UIInterfaceOrientationPortraitUpsideDown",
                "UIInterfaceOrientationLandscapeLeft",
                "UIInterfaceOrientationLandscapeRight",
                nullptr
            };
.....

Around line 1390:


    static Array<AppIconType> getiOSAppIconTypes()
    {
        AppIconType types[] =
        {
            { "iphone", "29x29",   "Icon-Small.png",             "1x", 29  },
            { "iphone", "29x29",   "Icon-Small@2x.png",          "2x", 58  },
            // (pca) Added this image
            { "iphone", "29x29",   "Icon-Small@3x.png",          "3x", 87  },
            { "iphone", "40x40",   "Icon-Spotlight-40@2x.png",   "2x", 80  },
            // (pca) Added this image
            { "iphone", "40x40",   "Icon-Spotlight-40@3x.png",   "3x", 120 },
            { "iphone", "57x57",   "Icon.png",                   "1x", 57  },
            { "iphone", "57x57",   "Icon@2x.png",                "2x", 114 },

.....

Around line 1440:


.....

            images.append (var (d));
        }
        return getiOSAssetContents (images);
    }

    // (pca) removed this structure definition from getiOSLaunchImageContents to mimic AppIcon way to fill the asset
    struct ImageType
    {
        const char* orientation;
        const char* idiom;
        // (pca) Added subtype, needed to distinguish iPhone Retina4
        const char* subtype;
        const char* extent;
        const char* scale;
        // (pca) Added filename and image resolution
        const char* filename;
        const int   width;
        const int   height;
    };

    // (pca) removed this array definition from getiOSLaunchImageContents to mimic AppIcon way to fill the asset
    static Array<ImageType> getiOSLaunchImageTypes()
    {
        ImageType types[] = 
        {
            // (pca) Added subtype, filename and image resolution
            { "portrait", "iphone", NULL,      "full-screen", "2x", "LaunchImage-iphone-2x.png", 640, 960 },
            { "portrait", "iphone", "retina4", "full-screen", "2x", "LaunchImage-iphone-retina4.png", 640, 1136 },
            { "portrait", "ipad",   NULL,      "full-screen", "1x", "LaunchImage-ipad-portrait-1x.png", 768, 1024 },
            { "landscape","ipad",   NULL,      "full-screen", "1x", "LaunchImage-ipad-landscape-1x.png", 1024, 768 },
            { "portrait", "ipad",   NULL,      "full-screen", "2x", "LaunchImage-ipad-portrait-2x.png", 1536, 2048 },
            { "landscape","ipad",   NULL,      "full-screen", "2x", "LaunchImage-ipad-landscape-2x.png", 2048, 1536 }
        };
        return Array<ImageType> (types, numElementsInArray (types));
    }

    // (pca) Modified to mimic getiOSAppIconContents
    String getiOSLaunchImageContents() const
    {
        const Array<ImageType> types = getiOSLaunchImageTypes();
        var images;
        for (size_t i = 0; i < types.size(); ++i)
        {
            ImageType type = types.getUnchecked(i);
            DynamicObject::Ptr d = new DynamicObject();
            d->setProperty ("orientation", type.orientation);
            d->setProperty ("idiom", type.idiom);
            if (type.subtype) d->setProperty ("subtype", type.subtype);
            d->setProperty ("extent",  type.extent);
            d->setProperty ("minimum-system-version", "7.0");
            d->setProperty ("scale", type.scale);
            d->setProperty ("filename", type.filename);
            images.append (var (d));
        }
        return getiOSAssetContents (images);
    }

    void createiOSAssetsFolder() const
    {

.....

And finally around line 1500, 


    void createiOSAssetsFolder() const
    {
        File assets (getTargetFolder().getChildFile (project.getProjectFilenameRoot()).getChildFile ("Images.xcassets"));
        overwriteFileIfDifferentOrThrow (assets.getChildFile ("AppIcon.appiconset").getChildFile ("Contents.json"), getiOSAppIconContents());
        createiOSIconFiles (assets.getChildFile ("AppIcon.appiconset"));
        overwriteFileIfDifferentOrThrow (assets.getChildFile ("LaunchImage.launchimage").getChildFile ("Contents.json"), getiOSLaunchImageContents());
        // (pca) create the launch images
        createiOSLaunchImageFiles (assets.getChildFile ("LaunchImage.launchimage"));
        RelativePath assetsPath (assets, getTargetFolder(), RelativePath::buildTargetFolder);
        addFileReference (assetsPath.toUnixStyle());
        resourceIDs.add (addBuildFile (assetsPath, false, false));
        resourceFileRefs.add (createFileRefID (assetsPath));
    }

That's it. Hope this can help some of you :-)


#4

Thanks, much appreciated! We'll take a look at this next week.


#5

This is a short update: the CFBundleIconFile key must be added for MacOSX project, but should be removed for iOS.

So the correct modification around line 606 is:


// (pca) Do not insert this line for iOS because it causes a validation error during app publishing (error ITMS-90032)
if (!iOS)
   addPlistDictionaryKey (dict, "CFBundleIconFile", iconFile.exists() ? iconFile.getFileName() : String::empty);