iOS 7 / Xcode 5: Access device launch images programmatically
Asked Answered
B

16

41

Is there any way to use the apps LaunchImage as a background in an universal iOS app without putting the same image resources in multiple places?

I wasn't able to access the LaunchImage files in Images.xcassets, so I created two new Image Sets "Background Portrait" and "Background Landscape" (since there seems to be no way to put landscape and portrait images into the same set).

While this workaround does the jobs, I would hate to include every image into the app twice. This also has a high maintenance cost.

Any advice on how to access the LaunchImage for the current device is appreciated.

GCOLaunchImageTransition must have done the job for iOS < 7.

Baronage answered 16/10, 2013 at 17:35 Comment(2)
Hi Timm, did you find a solution for this issue yet? I have the same problem.Fania
just tried GCOLaunchImageTransition, still seems to work.Orangery
E
42

You can use the launch images without having to include them twice. The key is that when you use an asset catalog, the file names of the images that are included in the app bundle are (sort of) standardized and may not be related to what you've named the original files.

In particular, when you use the LaunchImage image set, the files that end up in the application bundle have names like

etc. Note, in particular, they are not named Default.png or any variation of that. Even if that's what you called the files. Once you've dropped them in one of the wells in the asset catalog, they come out the other end with a standard name.

So [UIImage imageNamed:@"Default"] won't work because there is no such file in the app bundle. However, [UIImage imageNamed:@"LaunchImage"] will work (assuming you've filled either the iPhone Portrait 2x well or the pre iOS7 iPhone Portrait 1x well).

The documentation indicates that the imageNamed: method on UIImage should auto-magically select the correct version, but I think this only applies to image sets other than the launch image--at least I've not gotten it to work quite correctly (could just be me not doing something right).

So depending on your exact circumstances, you might need to do a little trial and error to get the correct file name. Build and run the app in the simulator and then you can always look in the appropriate subdirectory of ~/Library/Application Support/iPhone Simulator to verify what the actual file names in the app bundle are.

But again, the main point is that there is no need to include duplicates of the image files and you don't need to make any adjustments to the Copy Bundle Resources build phase.

Emmanuel answered 23/10, 2013 at 1:48 Comment(6)
i've upvoted this answer … but i'm also interested in being able to see the image in my .xib, and choosing "LaunchImage" in my .xib works on the device (and probably on the simulator), but just shows the big blue question mark in the .xib . any easy resolution to this part of it?Has
So, did anyone made [UIImage imageNamed:@"LaunchImage"] work?Timmerman
For my tamarin app it, unfortunately, didn'tTimmerman
Which one is for which device ?Tertian
Use [email protected] for iPhone XCommemorate
@SebastiaandeWeert on the latest Xcode/SDK it seems that the iPhone X splash screen is named LaunchImage-1100-Portrait-2436h.Anschluss
C
56

You can copy/paste the following code to load your app's launch image at runtime:

// Load launch image
NSString *launchImageName;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
    if ([UIScreen mainScreen].bounds.size.height == 480) launchImageName = @"[email protected]"; // iPhone 4/4s, 3.5 inch screen
    if ([UIScreen mainScreen].bounds.size.height == 568) launchImageName = @"[email protected]"; // iPhone 5/5s, 4.0 inch screen
    if ([UIScreen mainScreen].bounds.size.height == 667) launchImageName = @"[email protected]"; // iPhone 6, 4.7 inch screen
    if ([UIScreen mainScreen].bounds.size.height == 736) launchImageName = @"[email protected]"; // iPhone 6+, 5.5 inch screen
}
else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
    if ([UIScreen mainScreen].scale == 1) launchImageName = @"LaunchImage-700-Portrait~ipad.png"; // iPad 2
    if ([UIScreen mainScreen].scale == 2) launchImageName = @"LaunchImage-700-Portrait@2x~ipad.png"; // Retina iPads
}
self.launchImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:launchImageName]];
Crepe answered 5/12, 2014 at 17:44 Comment(1)
Thanks. This solution worked for me, unlike the accepted answer from Matthew BurkeAdler
E
42

You can use the launch images without having to include them twice. The key is that when you use an asset catalog, the file names of the images that are included in the app bundle are (sort of) standardized and may not be related to what you've named the original files.

In particular, when you use the LaunchImage image set, the files that end up in the application bundle have names like

etc. Note, in particular, they are not named Default.png or any variation of that. Even if that's what you called the files. Once you've dropped them in one of the wells in the asset catalog, they come out the other end with a standard name.

So [UIImage imageNamed:@"Default"] won't work because there is no such file in the app bundle. However, [UIImage imageNamed:@"LaunchImage"] will work (assuming you've filled either the iPhone Portrait 2x well or the pre iOS7 iPhone Portrait 1x well).

The documentation indicates that the imageNamed: method on UIImage should auto-magically select the correct version, but I think this only applies to image sets other than the launch image--at least I've not gotten it to work quite correctly (could just be me not doing something right).

So depending on your exact circumstances, you might need to do a little trial and error to get the correct file name. Build and run the app in the simulator and then you can always look in the appropriate subdirectory of ~/Library/Application Support/iPhone Simulator to verify what the actual file names in the app bundle are.

But again, the main point is that there is no need to include duplicates of the image files and you don't need to make any adjustments to the Copy Bundle Resources build phase.

Emmanuel answered 23/10, 2013 at 1:48 Comment(6)
i've upvoted this answer … but i'm also interested in being able to see the image in my .xib, and choosing "LaunchImage" in my .xib works on the device (and probably on the simulator), but just shows the big blue question mark in the .xib . any easy resolution to this part of it?Has
So, did anyone made [UIImage imageNamed:@"LaunchImage"] work?Timmerman
For my tamarin app it, unfortunately, didn'tTimmerman
Which one is for which device ?Tertian
Use [email protected] for iPhone XCommemorate
@SebastiaandeWeert on the latest Xcode/SDK it seems that the iPhone X splash screen is named LaunchImage-1100-Portrait-2436h.Anschluss
A
41

Most answers require to create an image name depending on device type, scale, size etc. But as Matthew Burke pointed out, each image inside the launch image catalog will be renamed to "LaunchImage*" and therefore we are able to iterate through our launch images and find the (for the current device) appropriate image. In Objective-C it could look like this:

NSArray *allPngImageNames = [[NSBundle mainBundle] pathsForResourcesOfType:@"png"
                                        inDirectory:nil];

for (NSString *imgName in allPngImageNames){
    // Find launch images
    if ([imgName containsString:@"LaunchImage"]){
        UIImage *img = [UIImage imageNamed:imgName];
        // Has image same scale and dimensions as our current device's screen?
        if (img.scale == [UIScreen mainScreen].scale && CGSizeEqualToSize(img.size, [UIScreen mainScreen].bounds.size)) {
            NSLog(@"Found launch image for current device %@", img.description);
            break;
        }
    }
}

(Please note that this code uses the "containsString" method introduced with iOS 8. For previous iOS versions use "rangeOfString")

Appall answered 17/10, 2014 at 12:26 Comment(8)
I would think that, in order to avoid searching every time, the resulting imgName (or its lastPathComponent) could be stored as a local User Default.Battista
@Battista I thought about your suggestion and probably you are right. The defined launch image will always have the same name, even if you change it, and the user defaults are device bound so this should be fine too. However, I am not sure how costly the search is. Probably it's just a trade-off between speed and storage.Appall
imageNamed didn't work for me on iOS 7 - instead you can use imageWithContentsOfFileInsistency
@Insistency I am actually using the function with deployment target iOS 7 but compiling against the iOS 8 SDK. According to Apple's documentation "imageNamed" is available since iOS 2.0 (developer.apple.com/library/ios/documentation/UIKit/Reference/…). The only difference is, that "imageNamed" caches the image in background and "imageWithContentsOfFile" doesn't, which "will keep your single-use image out of the system image cache, potentially improving the memory use characteristics of your app." (Apple Doc)Appall
This is by far the best solutionLied
Much prefer this solution over any others here. Just added a Swift version of this answer to help anyone needing one.Louvain
This solution made me realize that the name used to reference the image has nothing to do with the actual filenames in the catalogue. It's a combination of the collection name (Brand Assets in my case) plus some "magic" suffixes. This solution would be more foolproof from future changes made by Apple, since the actual image name syntax is undocumented.Sulfonation
One easy optimization is to break out of the for loop when the match is found.Sulfonation
G
10

Below is the result when I test in iOS 7.0+, only portrait oritation:

3.5 inch screen: [email protected]
4.0 inch screen: [email protected]
4.7 inch screen: [email protected]
5.5 inch screen: [email protected]
iPad2          : LaunchImage-700-Portrait~ipad.png
Retina iPads   : LaunchImage-700-Portrait@2x~ipad.png
Gluconeogenesis answered 15/9, 2014 at 8:38 Comment(0)
L
10

A Swift version of the excellent answer by Daniel Witurna that doesn't require checking against a list of all known device types or orientations.

func appLaunchImage() -> UIImage? {

        let allPngImageNames = Bundle.main.paths(forResourcesOfType: "png", inDirectory: nil)

        for imageName in allPngImageNames
        {
            // make sure that the image name contains the string 'LaunchImage' and that we can actually create a UIImage from it.

            guard
                imageName.contains("LaunchImage"), 
                let image = UIImage(named: imageName) 
                else { continue }

            // if the image has the same scale AND dimensions as the current device's screen...

            if (image.scale == UIScreen.main.scale) && (image.size.equalTo(UIScreen.main.bounds.size))
            {
                return image
            }
        }

        return nil
    }
Louvain answered 19/7, 2016 at 13:22 Comment(1)
Though xcode (thankfully) modernized a bunch of naming conventions, this foundation is spectacular in xcode 9, swift 3, iOS 10.3 (for maintaining a seamless 'book cover' from app launch LaunchImage to user's first tap). Thank you!! :)Humo
D
8

Info.plist in bundle contains launch image information, including the name of launch image.

Objective-C:

- (UIImage *)getCurrentLaunchImage {
    CGSize screenSize = [UIScreen mainScreen].bounds.size;

    NSString *interfaceOrientation = nil;
    if (([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown) ||
        ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortrait)) {
        interfaceOrientation = @"Portrait";
    } else {
        interfaceOrientation = @"Landscape";
    }

    NSString *launchImageName = nil;

    NSArray *launchImages = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"UILaunchImages"];
    for (NSDictionary *launchImage in launchImages) {
        CGSize launchImageSize = CGSizeFromString(launchImage[@"UILaunchImageSize"]);
        NSString *launchImageOrientation = launchImage[@"UILaunchImageOrientation"];

        if (CGSizeEqualToSize(launchImageSize, screenSize) &&
            [launchImageOrientation isEqualToString:interfaceOrientation]) {
            launchImageName = launchImage[@"UILaunchImageName"];
            break;
        }
    }

    return [UIImage imageNamed:launchImageName];
}

Swift 4:

func getCurrentLaunchImage() -> UIImage? {

    guard let launchImages = Bundle.main.infoDictionary?["UILaunchImages"] as? [[String: Any]] else { return nil }

    let screenSize = UIScreen.main.bounds.size

    var interfaceOrientation: String
    switch UIApplication.shared.statusBarOrientation {
    case .portrait,
         .portraitUpsideDown:
        interfaceOrientation = "Portrait"
    default:
        interfaceOrientation = "Landscape"
    }

    for launchImage in launchImages {

        guard let imageSize = launchImage["UILaunchImageSize"] as? String else { continue }
        let launchImageSize = CGSizeFromString(imageSize)

        guard let launchImageOrientation = launchImage["UILaunchImageOrientation"] as? String else { continue }

        if
            launchImageSize.equalTo(screenSize),
            launchImageOrientation == interfaceOrientation,
            let launchImageName = launchImage["UILaunchImageName"] as? String {
            return UIImage(named: launchImageName)
        }
    }

    return nil
}
Disafforest answered 4/1, 2018 at 9:9 Comment(1)
Swift 4: This works for Portrait, but not for landscape. You need to change the screenSize to the following line: let screenSize = UIScreen.main.fixedCoordinateSpace.bounds.size. Added Swift 5 with fixEyesore
S
2

A concise function in Swift for getting the launch image name at runtime:

func launchImageName() -> String {
    switch (UI_USER_INTERFACE_IDIOM(), UIScreen.mainScreen().scale, UIScreen.mainScreen().bounds.size.height) {
        case (.Phone, _, 480): return "[email protected]"
        case (.Phone, _, 568): return "[email protected]"
        case (.Phone, _, 667): return "[email protected]"
        case (.Phone, _, 736): return "[email protected]"
        case (.Pad, 1, _): return "LaunchImage-700-Portrait~ipad.png"
        case (.Pad, 2, _): return "LaunchImage-700-Portrait@2x~ipad.png"
        default: return "LaunchImage"
    }
}
Selfsustaining answered 10/9, 2015 at 9:7 Comment(0)
P
1

I don't know if this is what you mean with access through code. But if you select your "project->target->build phases->copy bundle resources" there click the '+' and "add other" navigate to your Images.xcassets->LaunchImage.launchimage and select whatever png's you want to use and click "open". Now you can use the image like [UIImage imageNamed:@"Default"];

Plumage answered 16/10, 2013 at 20:38 Comment(2)
While "Images.xcassets" is already in the "Copy Bundle Resources" section, I can't see it when clicking + to add individual files. And currently "[UIImage imageNamed:@"Default"];" does not work for me.Baronage
You need to click first '+' then "add other" then navigate to your Images.xcassets->LaunchImage.launchimage. Images.xcassets is just a folder.Plumage
S
1

If you need to determine the device, I use the following code (it's a little bit quick and dirty but it does the trick)

if( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ){

    CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    if( screenHeight < screenWidth ){
        screenHeight = screenWidth;
    }

    if( screenHeight > 480 && screenHeight < 667 ){
        DLog(@"iPhone 5/5s");
    } else if ( screenHeight > 480 && screenHeight < 736 ){
        DLog(@"iPhone 6");
    } else if ( screenHeight > 480 ){
        DLog(@"iPhone 6 Plus");
    } else {
        DLog(@"iPhone 4/4s");
    }
}
Spectroheliograph answered 16/10, 2014 at 9:23 Comment(0)
K
1

Matthew Burke's answer is the correct answer. Below is the code I'm using to get this working for iOS9 / Xcode7, building an app for iOS7 and up, for iPhone and iPad, landscape allowed.

First, to elaborate a bit more: In iOS8 / Xcode6, if you were using a storyboard Launch Screen File, on app startup, the app would create 2 images (one portrait, one landscape) of that launch screen file in the correct resolution for your device and you were able to get that image from the file path. (I believe it was stored in Library/LaunchImage folder).

However in iOS9/XCode 7 this image is not created anymore (although a snapshot is taken in the snapshots folder, but that has a undescriptive name that changes all the time), so if you want to use your LaunchImage somewhere else in your code, you'll have to use a Launch Image Source (through asset catalog preferably, because of App Thinning). Now, as Matthew Burke is explaining you can't get to that image just by doing:

let launchImage = UIImage(named: "LaunchImage")

Even though the image name in your asset catalog is LaunchImage, Xcode/iOS9 won't let you.

Luckily you don't have to include your launch images again in your asset catalog. I'm saying luckily because that would mean about a 20MB increase of your App Download size if you're making an app for all devices.

So, how to get to those launch images than? Well, here are the steps:

  1. Create your launch images and put them in your asset catalog. Name of the images is not important.
  2. Make sure your Launch Screen File (under your target's general settings) is empty and remove your app from your device and simulator. (just deleting the filename and re-running won't do it, you'll have to remove your app first)
  3. Run your app in the simulator and go to the ~/Library/Application Support/iPhone Simulator folder and find your app there. (It's a bit of a hassle as the folder names are indescriptive.) Show the package content for your .app file and in there you'll see several image files starting with "LaunchImage- ..." In my case there were 9 images as I'm making an app for iPhone and iPad for iOS7 and up.
  4. Then, in your code you'll need to determine what device your app is running on and if it's in portrait or landscape and then decide which image to use. To make this a bit easier I used this framework: https://github.com/InderKumarRathore/DeviceGuru . Be ware, it didn't include the latest devices yet (iPhone 6s and iPhone 6s plus) so you'll have to add a line in his swift file for that. Then, put below piece of code in the vc where you want your launchImage and there you go:

    func launchImage() -> UIImage? {
        if let launchImageName = launcheImageName() {
            print(launchImageName)
            return UIImage(named: launchImageName)
        }
        else {
            print("no launch image")
            return nil
        }
    }
    
    func launcheImageName() -> String? {
        let HD35 = "[email protected]"
        let HD40 = "LaunchImage-700-568h@2x"
        let HD47 = "[email protected]"
        var HD55 = "[email protected]"
        var padHD = "LaunchImage-700-Portrait@2x~ipad.png"
        var pad = "LaunchImage-700-Portrait~ipad.png"
    
        if UIDevice.currentDevice().orientation == UIDeviceOrientation.LandscapeLeft || UIDevice.currentDevice().orientation == UIDeviceOrientation.LandscapeRight {
            HD55 = "[email protected]"
            padHD = "LaunchImage-700-Landscape@2x~ipad.png"
            pad = "LaunchImage-700-Landscape~ipad.png"
        }
    
        let hardware = hardwareString()
        if (hardware == "iPhone1,1")            { return HD35 }
        if (hardware == "iPhone1,2")            { return HD35 }
        if (hardware == "iPhone2,1")            { return HD35 }
        if (hardware == "iPhone3,1")            { return HD35 }
        if (hardware == "iPhone3,2")            { return HD35 }
        if (hardware == "iPhone3,3")            { return HD35 }
        if (hardware == "iPhone4,1")            { return HD35 }
        if (hardware == "iPhone5,1")            { return HD40 }
        if (hardware == "iPhone5,2")            { return HD40 }
        if (hardware == "iPhone5,3")            { return HD40 }
        if (hardware == "iPhone5,4")            { return HD40 }
        if (hardware == "iPhone6,1")            { return HD40 }
        if (hardware == "iPhone6,2")            { return HD40 }
        if (hardware == "iPhone7,1")            { return HD55 }
        if (hardware == "iPhone7,2")            { return HD47 }
        if (hardware == "iPhone8,1")            { return HD55 }
        if (hardware == "iPhone8,2")            { return HD47 }
    
        if (hardware == "iPod1,1")              { return HD35 }
        if (hardware == "iPod2,1")              { return HD35 }
        if (hardware == "iPod3,1")              { return HD35 }
        if (hardware == "iPod4,1")              { return HD35 }
        if (hardware == "iPod5,1")              { return HD40 }
    
        if (hardware == "iPad1,1")              { return pad }
        if (hardware == "iPad1,2")              { return pad }
        if (hardware == "iPad2,1")              { return pad }
        if (hardware == "iPad2,2")              { return pad }
        if (hardware == "iPad2,3")              { return pad }
        if (hardware == "iPad2,4")              { return pad }
        if (hardware == "iPad2,5")              { return pad }
        if (hardware == "iPad2,6")              { return pad }
        if (hardware == "iPad2,7")              { return pad }
        if (hardware == "iPad3,1")              { return padHD }
        if (hardware == "iPad3,2")              { return padHD }
        if (hardware == "iPad3,3")              { return padHD }
        if (hardware == "iPad3,4")              { return padHD }
        if (hardware == "iPad3,5")              { return padHD }
        if (hardware == "iPad3,6")              { return padHD }
        if (hardware == "iPad4,1")              { return padHD }
        if (hardware == "iPad4,2")              { return padHD }
        if (hardware == "iPad4,3")              { return padHD }
        if (hardware == "iPad4,4")              { return padHD }
        if (hardware == "iPad4,5")              { return padHD }
        if (hardware == "iPad4,6")              { return padHD }
        if (hardware == "iPad4,7")              { return padHD }
        if (hardware == "iPad4,8")              { return padHD }
        if (hardware == "iPad5,3")              { return padHD }
        if (hardware == "iPad5,4")              { return padHD }
    
        if (hardware == "i386")                 { return HD55 }
        if (hardware == "x86_64")               { return HD55 }
        if (hardware.hasPrefix("iPhone"))       { return HD55 }
        if (hardware.hasPrefix("iPod"))         { return HD55 }
        if (hardware.hasPrefix("iPad"))         { return padHD }
    
        //log message that your device is not present in the list
        logMessage(hardware)
    
        return nil
    }
    
Ketchan answered 22/9, 2015 at 2:12 Comment(1)
Wow this annoying, but thanks for sharing your answer. Confirmed that this works...Shindig
R
1

Here is the modified code based on Daniel Witurna solution. This code snippet uses predicate to filter the launch image name from the list of bundle images. Predicate will potentially avoid number of loops to filter the launch image from an array of image paths.

-(NSString *)getLaunchImageName{

NSArray *allPngImageNames = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:nil];
NSString *expression=[NSString stringWithFormat:@"SELF contains '%@'",@"LaunchImage"];

NSArray *res = [allPngImageNames filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:expression]];

NSString *launchImageName;
for (launchImageName in res){
    {
        UIImage *img = [UIImage imageNamed:launchImageName];
        // Has image same scale and dimensions as our current device's screen?
        if (img.scale == [UIScreen mainScreen].scale && CGSizeEqualToSize(img.size, [UIScreen mainScreen].bounds.size)) {
            NSLog(@"Found launch image for current device %@", img.description);
            break;
        }

    }

}
return launchImageName; }
Ralaigh answered 14/2, 2017 at 8:55 Comment(0)
D
1

Another more modern and elegant solution based on the excellent answer by Daniel:

extension UIImage {
    static var launchImage: UIImage? {
        let pngs = Bundle.main.paths(forResourcesOfType: "png", inDirectory: nil)
        return pngs
            .filter({$0.contains("LaunchImage")})
            .compactMap({UIImage(named: $0)})
            .filter({$0.size == UIScreen.main.bounds.size})
            .first
    } 
}

That way you can just write:

let myLaunchImage = UIImage.launchImage
Dialectic answered 26/4, 2019 at 14:21 Comment(0)
N
0

Once you have created the Images.xcassets, just rename the LaunchImage to Default.

This will save a lot of trouble if you are supporting iOS5 and iOS6.

The actual name of the "folder" / category will follow into the asset on build. Everything else is true what Matthew Burke said ;)

Norvil answered 7/2, 2014 at 1:24 Comment(0)
T
0

Create a new Group in your project, not backed by any physical directory. Import into that Group your launch images, directly from LaunchImage.launchimage. Voilá.

Torras answered 10/3, 2015 at 22:52 Comment(0)
N
0
 if (IS_IPHONE_4_OR_LESS) {
    self.imageView.image = [UIImage imageNamed:@"[email protected]"];
}
else if (IS_IPHONE_5){
     self.imageView.image = [UIImage imageNamed:@"[email protected]"];
}
else if (IS_IPHONE_6){
     self.imageView.image = [UIImage imageNamed:@"[email protected]"];
}
else if (IS_IPHONE_6P){
      self.imageView.image = [UIImage imageNamed:@"[email protected]"];
}
Nettienetting answered 31/5, 2016 at 13:8 Comment(0)
I
-1

Since the "LaunchImage" asset is actually a custom beast …

My suggestion is to create a secondary asset catalog with duplicates of the images (or the subset you actually need).

I call mine FauxLaunchImage. They you can access it like you wanted to

[UIImage imageNamed:@"FauxLaunchImage"];
Illhumored answered 2/4, 2014 at 17:15 Comment(2)
This solution is not answering the question since it is asked to access the LaunchImage without duplicated assets.Top
My answer is No. You should not try to access the launch image. If you figure out how to access it now, Apple is likely to change it out from under you. If one image the size of the screen is killing your app size, something else is wrong.Illhumored

© 2022 - 2024 — McMap. All rights reserved.