Improve fetching the optimal icon of an app for the update view

This commit is contained in:
Andreas Linde
2014-06-12 14:39:16 +02:00
parent 7963fdf5a7
commit 760a8d07b8
8 changed files with 189 additions and 67 deletions

View File

@@ -99,25 +99,7 @@
}
- (BOOL)isPreiOS7Environment {
static BOOL isPreiOS7Environment = YES;
static dispatch_once_t checkOS;
dispatch_once(&checkOS, ^{
// we only perform this runtime check if this is build against at least iOS7 base SDK
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
// runtime check according to
// https://developer.apple.com/library/prerelease/ios/documentation/UserExperience/Conceptual/TransitionGuide/SupportingEarlieriOS.html
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
isPreiOS7Environment = YES;
} else {
isPreiOS7Environment = NO;
}
#else
isPreiOS7Environment = YES;
#endif
});
return isPreiOS7Environment;
return bit_isPreiOS7Environment();
}
- (NSString *)getDevicePlatform {

View File

@@ -46,6 +46,10 @@ NSString *bit_appName(NSString *placeHolderString);
NSString *bit_UUIDPreiOS6(void);
NSString *bit_UUID(void);
NSString *bit_appAnonID(void);
BOOL bit_isPreiOS7Environment(void);
NSString *bit_validAppIconStringFromIcons(NSArray *icons);
NSString *bit_validAppIconFilename(NSBundle *bundle);
/* UIImage helpers */
UIImage *bit_roundedCornerImage(UIImage *inputImage, NSInteger cornerSize, NSInteger borderSize);

View File

@@ -216,6 +216,114 @@ NSString *bit_appAnonID(void) {
return appAnonID;
}
BOOL bit_isPreiOS7Environment(void) {
static BOOL isPreiOS7Environment = YES;
static dispatch_once_t checkOS;
dispatch_once(&checkOS, ^{
// we only perform this runtime check if this is build against at least iOS7 base SDK
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
// runtime check according to
// https://developer.apple.com/library/prerelease/ios/documentation/UserExperience/Conceptual/TransitionGuide/SupportingEarlieriOS.html
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
isPreiOS7Environment = YES;
} else {
isPreiOS7Environment = NO;
}
#else
isPreiOS7Environment = YES;
#endif
});
return isPreiOS7Environment;
}
/**
Find a valid app icon filename that points to a proper app icon image
@param icons NSArray with app icon filenames
@return NSString with the valid app icon or nil if none found
*/
NSString *bit_validAppIconStringFromIcons(NSArray *icons) {
if (!icons) return nil;
if (![icons isKindOfClass:[NSArray class]]) return nil;
BOOL useHighResIcon = NO;
BOOL useiPadIcon = NO;
if ([UIScreen mainScreen].scale == 2.0f) useHighResIcon = YES;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) useiPadIcon = YES;
NSString *currentBestMatch = nil;
float currentBestMatchHeight = 0;
float bestMatchHeight = 0;
if (bit_isPreiOS7Environment()) {
bestMatchHeight = useiPadIcon ? (useHighResIcon ? 144 : 72) : (useHighResIcon ? 114 : 57);
} else {
bestMatchHeight = useiPadIcon ? (useHighResIcon ? 152 : 76) : 120;
}
for(NSString *icon in icons) {
// Don't use imageNamed, otherwise unit tests won't find the fixture icon
// and using imageWithContentsOfFile doesn't load @2x files with absolut paths (required in tests)
NSData *imgData = [[NSData alloc] initWithContentsOfFile:icon];
UIImage *iconImage = [[UIImage alloc] initWithData:imgData];
if (iconImage) {
if (iconImage.size.height == bestMatchHeight) {
return icon;
} else if (iconImage.size.height < bestMatchHeight &&
iconImage.size.height > currentBestMatchHeight) {
currentBestMatchHeight = iconImage.size.height;
currentBestMatch = icon;
}
}
}
return currentBestMatch;
}
NSString *bit_validAppIconFilename(NSBundle *bundle) {
NSString *iconFilename = nil;
NSArray *icons = nil;
icons = [bundle objectForInfoDictionaryKey:@"CFBundleIconFiles"];
iconFilename = bit_validAppIconStringFromIcons(icons);
if (!iconFilename) {
icons = [bundle objectForInfoDictionaryKey:@"CFBundleIcons"];
if (icons && [icons isKindOfClass:[NSDictionary class]]) {
icons = [icons valueForKeyPath:@"CFBundlePrimaryIcon.CFBundleIconFiles"];
}
iconFilename = bit_validAppIconStringFromIcons(icons);
}
// we test iPad structure anyway and use it if we find a result and don't have another one yet
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
icons = [bundle objectForInfoDictionaryKey:@"CFBundleIcons~ipad"];
if (icons && [icons isKindOfClass:[NSDictionary class]]) {
icons = [icons valueForKeyPath:@"CFBundlePrimaryIcon.CFBundleIconFiles"];
}
NSString *iPadIconFilename = bit_validAppIconStringFromIcons(icons);
if (iPadIconFilename && !iconFilename) {
iconFilename = iPadIconFilename;
}
}
if (!iconFilename) {
NSString *tempFilename = [bundle objectForInfoDictionaryKey:@"CFBundleIconFile"];
if (tempFilename) {
iconFilename = bit_validAppIconStringFromIcons(@[tempFilename]);
}
}
if (!iconFilename) {
iconFilename = bit_validAppIconStringFromIcons(@[@"Icon.png"]);
}
return iconFilename;
}
#pragma mark UIImage private helpers

View File

@@ -294,59 +294,21 @@
}
[self updateAppStoreHeader];
NSString *iconString = nil;
NSArray *icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFiles"];
if (!icons) {
icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIcons"];
if ((icons) && ([icons isKindOfClass:[NSDictionary class]])) {
icons = [icons valueForKeyPath:@"CFBundlePrimaryIcon.CFBundleIconFiles"];
NSString *iconFilename = bit_validAppIconFilename([NSBundle mainBundle]);
if (iconFilename) {
BOOL addGloss = YES;
NSNumber *prerendered = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIPrerenderedIcon"];
if (prerendered) {
addGloss = ![prerendered boolValue];
}
if (!icons) {
iconString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"];
if (!iconString) {
iconString = @"Icon.png";
}
if (addGloss && [self.updateManager isPreiOS7Environment]) {
_appStoreHeader.iconImage = [self addGlossToImage:[UIImage imageNamed:iconFilename]];
} else {
_appStoreHeader.iconImage = [UIImage imageNamed:iconFilename];
}
}
if (icons) {
BOOL useHighResIcon = NO;
BOOL useiPadIcon = NO;
if ([UIScreen mainScreen].scale == 2.0f) useHighResIcon = YES;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) useiPadIcon = YES;
for(NSString *icon in icons) {
iconString = icon;
UIImage *iconImage = [UIImage imageNamed:icon];
if (
(iconImage.size.height == 57 && !useHighResIcon && !useiPadIcon) ||
(iconImage.size.height == 114 && useHighResIcon && !useiPadIcon) ||
(iconImage.size.height == 120 && useHighResIcon && !useiPadIcon) ||
(iconImage.size.height == 72 && !useHighResIcon && useiPadIcon) ||
(iconImage.size.height == 76 && !useHighResIcon && useiPadIcon) ||
(iconImage.size.height == 144 && !useHighResIcon && useiPadIcon) ||
(iconImage.size.height == 152 && useHighResIcon && useiPadIcon)
) {
// found!
break;
}
}
}
BOOL addGloss = YES;
NSNumber *prerendered = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIPrerenderedIcon"];
if (prerendered) {
addGloss = ![prerendered boolValue];
}
if (addGloss && [self.updateManager isPreiOS7Environment]) {
_appStoreHeader.iconImage = [self addGlossToImage:[UIImage imageNamed:iconString]];
} else {
_appStoreHeader.iconImage = [UIImage imageNamed:iconString];
}
self.tableView.tableHeaderView = _appStoreHeader;
BITStoreButtonStyle buttonStyle = BITStoreButtonStyleDefault;

View File

@@ -44,6 +44,8 @@
1E1127C916580C87007067A2 /* buttonRoundedRegular@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E1127C116580C87007067A2 /* buttonRoundedRegular@2x.png */; };
1E1127CA16580C87007067A2 /* buttonRoundedRegularHighlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E1127C216580C87007067A2 /* buttonRoundedRegularHighlighted.png */; };
1E1127CB16580C87007067A2 /* buttonRoundedRegularHighlighted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E1127C316580C87007067A2 /* buttonRoundedRegularHighlighted@2x.png */; };
1E494AEC19491943001EFF74 /* AppIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E494AEA19491943001EFF74 /* AppIcon.png */; };
1E494AED19491943001EFF74 /* AppIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E494AEB19491943001EFF74 /* AppIcon@2x.png */; };
1E49A43C1612223B00463151 /* BITFeedbackComposeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E49A42D1612223B00463151 /* BITFeedbackComposeViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
1E49A43F1612223B00463151 /* BITFeedbackComposeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E49A42E1612223B00463151 /* BITFeedbackComposeViewController.m */; };
1E49A4421612223B00463151 /* BITFeedbackListViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E49A42F1612223B00463151 /* BITFeedbackListViewCell.h */; };
@@ -226,6 +228,8 @@
1E1127C316580C87007067A2 /* buttonRoundedRegularHighlighted@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "buttonRoundedRegularHighlighted@2x.png"; sourceTree = "<group>"; };
1E20A57F181E9D4600D5B770 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/HockeySDK.strings; sourceTree = "<group>"; };
1E36D8B816667611000B134C /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/HockeySDK.strings; sourceTree = "<group>"; };
1E494AEA19491943001EFF74 /* AppIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AppIcon.png; sourceTree = "<group>"; };
1E494AEB19491943001EFF74 /* AppIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIcon@2x.png"; sourceTree = "<group>"; };
1E49A42D1612223B00463151 /* BITFeedbackComposeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITFeedbackComposeViewController.h; sourceTree = "<group>"; };
1E49A42E1612223B00463151 /* BITFeedbackComposeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITFeedbackComposeViewController.m; sourceTree = "<group>"; };
1E49A42F1612223B00463151 /* BITFeedbackListViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITFeedbackListViewCell.h; sourceTree = "<group>"; };
@@ -601,6 +605,8 @@
1EA1170216F53B49001C015C /* Fixtures */ = {
isa = PBXGroup;
children = (
1E494AEA19491943001EFF74 /* AppIcon.png */,
1E494AEB19491943001EFF74 /* AppIcon@2x.png */,
1EA1170316F53B49001C015C /* StoreBundleIdentifierUnknown.json */,
1EA1170816F53E3A001C015C /* StoreBundleIdentifierKnown.json */,
1E70A22F17F2F982001BB32D /* live_report_empty.plcrash */,
@@ -949,12 +955,14 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1E494AEC19491943001EFF74 /* AppIcon.png in Resources */,
1EA1170C16F54A64001C015C /* HockeySDKResources.bundle in Resources */,
1E5A459B16F0DFC200B55C04 /* InfoPlist.strings in Resources */,
1E70A23417F2F982001BB32D /* live_report_signal.plcrash in Resources */,
1EA1170416F53B49001C015C /* StoreBundleIdentifierUnknown.json in Resources */,
1E70A23317F2F982001BB32D /* live_report_exception.plcrash in Resources */,
1E70A23217F2F982001BB32D /* live_report_empty.plcrash in Resources */,
1E494AED19491943001EFF74 /* AppIcon@2x.png in Resources */,
1EA1170916F53E3A001C015C /* StoreBundleIdentifierKnown.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@@ -90,5 +90,63 @@
assertThatInteger([resultString length], equalToInteger(36));
}
- (void)testValidAppIconFilename {
NSString *resultString = nil;
NSBundle *mockBundle = mock([NSBundle class]);
NSString *validIconPath = [[NSBundle bundleForClass:self.class] pathForResource:@"AppIcon" ofType:@"png"];
NSString *validIconPath2x = [[NSBundle bundleForClass:self.class] pathForResource:@"AppIcon@2x" ofType:@"png"];
// No valid icons defined at all
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFiles"]) willReturn:nil];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons"]) willReturn:nil];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons~ipad"]) willReturn:nil];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFile"]) willReturn:@"invalidFilename.png"];
resultString = bit_validAppIconFilename(mockBundle);
assertThat(resultString, nilValue());
// CFBundleIconFiles contains valid filenames
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFiles"]) willReturn:@[validIconPath, validIconPath2x]];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons"]) willReturn:nil];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons~ipad"]) willReturn:nil];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFile"]) willReturn:nil];
resultString = bit_validAppIconFilename(mockBundle);
assertThat(resultString, notNilValue());
// CFBundleIcons contains valid dictionary filenames
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFiles"]) willReturn:@[@"invalidFilename.png"]];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons"]) willReturn:@{@"CFBundlePrimaryIcon":@{@"CFBundleIconFiles":@[validIconPath, validIconPath2x]}}];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons~ipad"]) willReturn:nil];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFile"]) willReturn:nil];
// CFBundleIcons contains valid ipad dictionary and valid default dictionary filenames
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFiles"]) willReturn:@[@"invalidFilename.png"]];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons"]) willReturn:@{@"CFBundlePrimaryIcon":@{@"CFBundleIconFiles":@[validIconPath, validIconPath2x]}}];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons~ipad"]) willReturn:@{@"CFBundlePrimaryIcon":@{@"CFBundleIconFiles":@[validIconPath, validIconPath2x]}}];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFile"]) willReturn:nil];
resultString = bit_validAppIconFilename(mockBundle);
assertThat(resultString, notNilValue());
// CFBundleIcons contains valid filenames
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFiles"]) willReturn:@[@"invalidFilename.png"]];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons"]) willReturn:@[validIconPath, validIconPath2x]];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons~ipad"]) willReturn:nil];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFile"]) willReturn:nil];
resultString = bit_validAppIconFilename(mockBundle);
assertThat(resultString, notNilValue());
// CFBundleIcon contains valid filename
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFiles"]) willReturn:@[@"invalidFilename.png"]];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons"]) willReturn:nil];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIcons~ipad"]) willReturn:nil];
[given([mockBundle objectForInfoDictionaryKey:@"CFBundleIconFile"]) willReturn:validIconPath];
resultString = bit_validAppIconFilename(mockBundle);
assertThat(resultString, notNilValue());
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB