关于C#:iOS 7 / Xcode 5:以编程方式访问设备启动图像

 2020-06-30 

iOS 7 / Xcode 5: Access device launch images programmatically

有什么方法可以将应用程序LaunchImage用作通用iOS应用程序中的背景,而无需将相同的图像资源放在多个位置?

我无法访问Images.xcassets中的LaunchImage文件,因此创建了两个新的图像集" Background Portrait"和" Background Landscape"(因为似乎无法将风景图像和肖像图像放入同一图像中)。 组)。

尽管此变通办法可以完成工作,但我不希望将每个图像两次包含到应用程序中。 这也具有高的维护成本。

感谢有关如何访问当前设备的LaunchImage的任何建议。

对于iOS <7,GCOLaunchImageTransition必须已完成此工作。


您可以复制/粘贴以下代码以在运行时加载应用程序的启动映像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Load launch image
NSString *launchImageName;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
    if ([UIScreen mainScreen].bounds.size.height == 480) launchImageName = @"LaunchImage-700@2x.png"; // iPhone 4/4s, 3.5 inch screen
    if ([UIScreen mainScreen].bounds.size.height == 568) launchImageName = @"LaunchImage-700-568h@2x.png"; // iPhone 5/5s, 4.0 inch screen
    if ([UIScreen mainScreen].bounds.size.height == 667) launchImageName = @"LaunchImage-800-667h@2x.png"; // iPhone 6, 4.7 inch screen
    if ([UIScreen mainScreen].bounds.size.height == 736) launchImageName = @"LaunchImage-800-Portrait-736h@3x.png"; // 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]];


您可以使用启动映像,而不必两次包含它们。关键是,当您使用资产目录时,应用程序捆绑包中包含的图像的文件名是(某种)标准化的,并且可能与您命名原始文件的内容无关。

特别是,当您使用LaunchImage图像集时,最终出现在应用程序捆绑包中的文件的名称如下

  • LaunchImage.png
  • LaunchImage@2x.png
  • LaunchImage-700@2x.png
  • LaunchImage-568h@2x.png
  • LaunchImage-700-568h@2x.png
  • LaunchImage-700-Landscape@2x~ipad.png

请特别注意,它们未命名为Default.png或该名称的任何变体。即使那就是您所说的文件。将它们放入资产目录中的一口井后,它们就会以标准名称从另一端出来。

因此[UIImage imageNamed:@"Default"]将不起作用,因为应用程序捆绑包中没有此类文件。但是,[UIImage imageNamed:@"LaunchImage"]将起作用(假设您已经很好地填充了iPhone Portrait 2x或iOS7之前版??本的iPhone Portrait 1x)。

该文档表明UIImage上的imageNamed:方法应自动神奇地选择正确的版本,但是我认为这仅适用于启动图像以外的图像集-至少我没有使它能够正确运行。 (可能只是我没有做正确的事)。

因此,根据您的实际情况,您可能需要进行一些反复试验才能获得正确的文件名。在模拟器中构建并运行该应用程序,然后您始终可以在~/Library/Application Support/iPhone Simulator的相应子目录中查找以验证应用程序捆绑包中的实际文件名是什么。

但是,再次要点是,无需包含图像文件的重复项,并且您无需对Copy Bundle Resources构建阶段进行任何调整。


大多数答案都需要根据设备类型,规模,大小等来创建映像名称。但是正如Matthew Burke指出的那样,启动映像目录中的每个映像都将重命名为" LaunchImage *",因此我们可以迭代启动过程图像并找到(对于当前设备)合适的图像。在Objective-C中,它可能看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
        }
    }
}

(请注意,此代码使用iOS 8引入的" containsString"方法。对于以前的iOS版本,请使用" rangeOfString")


Daniel Witurna出色答案的Swift版本,不需要检查所有已知设备类型或方向的列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
    }


下面是我在iOS 7.0+中测试时的结果,仅纵向调整:

1
2
3
4
5
6
3.5 inch screen: LaunchImage-700@2x.png
4.0 inch screen: LaunchImage-700-568h@2x.png
4.7 inch screen: LaunchImage-800-667h@2x.png
5.5 inch screen: LaunchImage-800-Portrait-736h@3x.png
iPad2          : LaunchImage-700-Portrait~ipad.png
Retina iPads   : LaunchImage-700-Portrait@2x~ipad.png

捆绑软件中的Info.plist包含启动映像信息,包括启动映像的名称。

目标C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (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];
}

斯威夫特4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
}


Swift中的一个简洁函数,用于在运行时获取启动映像名称:

1
2
3
4
5
6
7
8
9
10
11
func launchImageName() -> String {
    switch (UI_USER_INTERFACE_IDIOM(), UIScreen.mainScreen().scale, UIScreen.mainScreen().bounds.size.height) {
        case (.Phone, _, 480): return"LaunchImage-700@2x.png"
        case (.Phone, _, 568): return"LaunchImage-700-568h@2x.png"
        case (.Phone, _, 667): return"LaunchImage-800-667h@2x.png"
        case (.Phone, _, 736): return"LaunchImage-800-Portrait-736h@3x.png"
        case (.Pad, 1, _): return"LaunchImage-700-Portrait~ipad.png"
        case (.Pad, 2, _): return"LaunchImage-700-Portrait@2x~ipad.png"
        default: return"LaunchImage"
    }
}

Matthew Burke的答案是正确的答案。以下是我用来使此代码在iOS9 / Xcode7上运行,为iOS7及更高版本,为iPhone和iPad生成应用程序的应用允许使用的代码。

首先,详细说明一下:
在iOS8 / Xcode6中,如果您使用情节提要启动画面文件,则在应用启动时,该应用会以适合您设备的正确分辨率创建该启动画面文件的2张图像(一幅肖像,一张风景),并且您能够文件路径中的该图像。 (我相信它存储在Library / LaunchImage文件夹中)。

但是,在iOS9 / XCode 7中,不再创建该映像(尽管快照快照是在快照文件夹中拍摄的,但是其名称始终保持更改,因此不具描述性),因此,如果要在代码的其他位置使用LaunchImage,您将必须使用启动图像源(最好是通过资产目录,因为应用程序稀疏)。现在,正如马修·伯克(Matthew Burke)解释的那样,您不能仅仅通过以下方式获得该图像:

1
let launchImage = UIImage(named:"LaunchImage")

即使资产目录中的图像名称是LaunchImage,Xcode / iOS9也不允许您使用。

幸运的是,您不必在资产目录中再次包含启动映像。我要说的是幸运的,因为如果您要为所有设备制作一个应用程序,那将意味着您的应用程序下载大小增加约20MB。

那么,如何获得那些启动图像呢?好吧,这是步骤:

  • 创建启动映像并将其放入资产目录。图像名称并不重要。
  • 确保启动屏幕文件(在目标的常规设置下)为空,然后从设备和模拟器中删除您的应用。 (仅删除文件名并重新运行不会这样做,您必须先删除您的应用程序)
  • 在模拟器中运行您的应用程序,然后转到?/ Library / Application Support / iPhone Simulator文件夹,然后在此处找到您的应用程序。 (因为文件夹名称不明确,所以有点麻烦。)显示您的.app文件的软件包内容,在该文件夹中您会看到几个以" LaunchImage- ..."开头的图像文件。在我的情况下,有9张图像因为我正在为iOS7及更高版本的iPhone和iPad开发应用程序。
  • 然后,在您的代码中,您需要确定您的应用程序正在运行的设备,以及它是纵向还是横向的,然后确定要使用的图像。为了使操作更简单,我使用了以下框架:https://github.com/InderKumarRathore/DeviceGuru。请注意,它尚未包括最新的设备(iPhone 6s和iPhone 6s plus),因此您必须为此在他的快速文件中添加一行。然后,将以下代码放在vc中您想要启动图像的位置,然后进入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    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 ="LaunchImage-700@2x.png"
        let HD40 ="LaunchImage-700-568h@2x"
        let HD47 ="LaunchImage-800-667h@2x.png"
        var HD55 ="LaunchImage-800-Portrait-736h@3x.png"
        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 ="LaunchImage-800-Landscape-736h@3x.png"
            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
    }

  • 基于Daniel的出色回答,另一个更现代,更优雅的解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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
        }
    }

    这样,您可以编写:

    1
    let myLaunchImage = UIImage.launchImage

    这是基于Daniel Witurna解决方案的修改后的代码。此代码段使用谓词从捆绑包图像列表中过滤启动图像名称。谓词将潜在地避免循环的次数,以从一系列图像路径中过滤启动图像。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    -(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; }

    我不知道这是否意味着通过代码访问。但是,如果选择"项目"->"目标"->"构建阶段"->"复制捆绑包资源",则单击" +"并"添加其他",导航到Images.xcassets-> LaunchImage.launchimage并选择要使用的png然后点击"打开"。现在您可以使用[UIImage imageNamed:@"Default"];之类的图像


    如果您需要确定设备,我可以使用以下代码(虽然有点麻烦,但可以解决问题)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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");
        }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     if (IS_IPHONE_4_OR_LESS) {
        self.imageView.image = [UIImage imageNamed:@"LaunchImage-700@2x.png"];
    }
    else if (IS_IPHONE_5){
         self.imageView.image = [UIImage imageNamed:@"LaunchImage-700-568h@2x.png"];
    }
    else if (IS_IPHONE_6){
         self.imageView.image = [UIImage imageNamed:@"LaunchImage-800-667h@2x.png"];
    }
    else if (IS_IPHONE_6P){
          self.imageView.image = [UIImage imageNamed:@"LaunchImage-800-Portrait-736h@3x.png"];
    }

    创建Images.xcassets后,只需将LaunchImage重命名为Default

    如果您支持iOS5和iOS6,这将省去很多麻烦。

    "文件夹" /类别的实际名称将跟随构建的资产。
    马修·伯克(Matthew Burke)所说的一切都是真实的;)


    在项目中创建一个没有任何物理目录支持的新组。直接从LaunchImage.launchimage导入启动映像。 Voilá。


    由于" LaunchImage"资产实际上是自定义的野兽……

    我的建议是用图像(或您实际需要的子集)的副本创建二级资产目录。

    我称之为FauxLaunchImage。他们可以像您想要的那样访问它

    1
    [UIImage imageNamed:@"FauxLaunchImage"];