可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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
- LaunchImage.png
- LaunchImage@2x.png
- LaunchImage-700@2x.png
- LaunchImage-568h@2x.png
- LaunchImage-700-568h@2x.png
- LaunchImage-700-Landscape@2x~ipad.png
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.
回答2:
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 = @"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]];
回答3:
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")
回答4:
Below is the result when I test in iOS 7.0+, only portrait oritation:
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
回答5:
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 = NSBundle.mainBundle().pathsForResourcesOfType("png", inDirectory: nil)
for imageName in allPngImageNames
{
guard imageName.containsString("LaunchImage") else { continue }
guard 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.mainScreen().scale) && (CGSizeEqualToSize(image.size, UIScreen.mainScreen().bounds.size))
{
return image
}
}
return nil
}
回答6:
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
}
回答7:
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 "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"
}
}
回答8:
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"];
回答9:
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");
}
}
回答10:
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:
- Create your launch images and put them in your asset catalog. Name of the images is not important.
- 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)
- 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.
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 = "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
}
回答11:
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"];
}
回答12:
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; }
回答13:
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 ;)
回答14:
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á.
回答15:
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
回答16:
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"];