iOS 图标&启动图生成器(二)

 
   

奇技指南

本文来自360奇舞团iOS团队QiShare投稿,原文:

https://www.jianshu.com/p/f4e78a44ddf8


前情回顾

一个完整的app都需要多种尺寸的图标和启动图。一般情况,设计师根据开发者提供的一套规则,设计出图标和启动图供开发人员使用。但最近作者利用业余时间做了个app,不希望耽误设计师较多时间,就只要了最大尺寸的图标和启动图各一个。本想着找一下现成的工具,批量生成需要的的图片,但最后没有找到,只好使用Photoshop切出了不同尺寸的图片。这期间,设计师还换过一次图标和启动图,作者就重复了切图工作,这花费了大量的时间。于是事后,作者开发了一个mac app——图标&启动图生成器(简称生成器)以提高工作效率。

作者用两篇文章分别介绍生成器的使用和实现细节。本文是第二篇。


上篇文章《iOS 图标&启动图生成器(一)》,本篇文章介绍生成器的实现细节。

生成器的工程非常简单,可以概括为一个界面一个资源文件和一个ViewController。结构如下图。

iOS 图标&启动图生成器(二)_第1张图片


01

界面

生成器app只有一个界面,因为界面复杂度较小,作者选用了Storyboard+Constraints的方式进行开发。下图显示了界面中的控件和约束情况。

iOS 图标&启动图生成器(二)_第2张图片


其中各控件对应的类如下所示。

控件
图片框 NSImageView
平台选择器 NSComboBox
路径按钮 NSButton
路径文本框 NSTextField
导出按钮 NSButton



02

资源文件

app所支持的平台规则数据从资源文件QiConfiguration.plist中获取。QiConfiguration.plist相当于一个字典,每个平台对应着字典的一对keyvalue;
value是一个数组,存储着该平台所需要的一组尺寸规格数据(item);
item是尺寸规格数据的最小单元,内部标记了该尺寸规格的图片的用途、名称和尺寸。

QiConfiguration.plist的具体结构如下图所示。

iOS 图标&启动图生成器(二)_第3张图片


03

ViewController

 
   

工程使用默认的ViewController管理界面、资源数据和逻辑。
首先,界面控件元素在ViewController中对应下图中的5个实例。

iOS 图标&启动图生成器(二)_第4张图片

其中,imageViewplatformBoxpathField不需要响应方法。并且,platfromBox_pathField的默认/记忆数据由NSUserDefaults管理。


static NSString * const selectedPlatformKey = @"selectedPlatform";static NSString * const exportedPathKey = @"exportedPath";NSString * const selectedPlatformKey = @"selectedPlatform";
static NSString * const exportedPathKey = @"exportedPath";
- (void)viewDidLoad {        [super viewDidLoad];        NSString *selectedPlatform = [[NSUserDefaults standardUserDefaults] objectForKey:selectedPlatformKey];    [_platformBox selectItemWithObjectValue:selectedPlatform];        NSString *lastExportedPath = [[NSUserDefaults standardUserDefaults] objectForKey:exportedPathKey];    _pathField.stringValue = lastExportedPath ?: NSHomeDirectory();}

[super viewDidLoad];

NSString *selectedPlatform = [[NSUserDefaults standardUserDefaults] objectForKey:selectedPlatformKey];
[_platformBox selectItemWithObjectValue:selectedPlatform];

NSString *lastExportedPath = [[NSUserDefaults standardUserDefaults] objectForKey:exportedPathKey];
_pathField.stringValue = lastExportedPath ?: NSHomeDirectory();
}

这里忽略这三个控件,重点介绍pathButtonexportButton的响应方法中的代码逻辑。


1. -pathButtonClicked:

 
   

pathButton的响应方法负责打开文件目录,并回传选择的路径给pathField,以显示出来。

代码如下:

- (IBAction)pathButtonClicked:(NSButton *)sender {        NSOpenPanel *openPanel = [NSOpenPanel openPanel];    openPanel.canChooseDirectories = YES;    openPanel.canChooseFiles = NO;    openPanel.title = @"选择导出目录";    [openPanel beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse result) {        if (result == NSModalResponseOK) {            self.pathField.stringValue = openPanel.URL.path;        }    }];}NSButton *)sender {

NSOpenPanel *openPanel = [NSOpenPanel openPanel];
openPanel.canChooseDirectories = YES;
openPanel.canChooseFiles = NO;
openPanel.title = @"选择导出目录";
[openPanel beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse result) {
if (result == NSModalResponseOK) {
self.pathField.stringValue = openPanel.URL.path;
}
}];
}


2. - exportButtonClicked

 
   

exportButton的响应方法负责根据imageView中的源图片、platform中选择的平台规则和pathField中显示的导出路径生成图片并打开图片所在的文件夹。

代码如下:

- (IBAction)exportButtonClicked:(NSButton *)sender {        NSImage *image = _imageView.image;    NSString *platform = _platformBox.selectedCell.title;    NSString *exportPath = _pathField.stringValue;        if (!image || !platform || !exportPath) {        NSAlert *alert = [[NSAlert alloc] init];        alert.messageText = @"请先选择源图片、平台和导出路径";        alert.alertStyle = NSAlertStyleWarning;        [alert addButtonWithTitle:@"确认"];        [alert beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse returnCode) {}];    }    else {        [[NSUserDefaults standardUserDefaults] setObject:platform forKey:selectedPlatformKey];        [[NSUserDefaults standardUserDefaults] synchronize];        [[NSUserDefaults standardUserDefaults] setObject:exportPath forKey:exportedPathKey];        [[NSUserDefaults standardUserDefaults] synchronize];                [self generateImagesForPlatform:platform fromOriginalImage:image];    }}NSButton *)sender {

NSImage *image = _imageView.image;
NSString *platform = _platformBox.selectedCell.title;
NSString *exportPath = _pathField.stringValue;

if (!image || !platform || !exportPath) {
NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = @"请先选择源图片、平台和导出路径";
alert.alertStyle = NSAlertStyleWarning;
[alert addButtonWithTitle:@"确认"];
[alert beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse returnCode) {}];
}
else {
[[NSUserDefaults standardUserDefaults] setObject:platform forKey:selectedPlatformKey];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSUserDefaults standardUserDefaults] setObject:exportPath forKey:exportedPathKey];
[[NSUserDefaults standardUserDefaults] synchronize];

[self generateImagesForPlatform:platform fromOriginalImage:image];
}
}
- (void)generateImagesForPlatform:(NSString *)platform fromOriginalImage:(NSImage *)originalImage {        NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"QiConfiguration" ofType:@"plist"];    NSDictionary *configuration = [NSDictionary dictionaryWithContentsOfFile:plistPath];    NSArray *items = configuration[platform];        NSString *directoryPath = [[_pathField.stringValue stringByAppendingPathComponent:platform] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];    [[NSFileManager defaultManager] createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil];        if ([platform containsString:@"AppIcons"]) {        [self generateAppIconsWithConfigurations:items fromOriginalImage:originalImage toDirectoryPath:directoryPath];    }    else if ([platform containsString:@"LaunchImages"]) {        [self generateLaunchImagesWithConfigurations:items fromOriginalImage:originalImage toDirectoryPath:directoryPath];    }}- (void)generateAppIconsWithConfigurations:(NSArray *)configurations fromOriginalImage:(NSImage *)originalImage toDirectoryPath:(NSString *)directoryPath {        for (NSDictionary *configuration in configurations) {        NSImage *appIcon = [self generateAppIconWithImage:originalImage forSize:NSSizeFromString(configuration[@"size"])];        NSString *filePath = [NSString stringWithFormat:@"%@/%@.png", directoryPath, configuration[@"name"]];        [self exportImage:appIcon toPath:filePath];    }    [[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:directoryPath isDirectory:YES]];}- (void)generateLaunchImagesWithConfigurations:(NSArray *)configurations fromOriginalImage:(NSImage *)originalImage toDirectoryPath:(NSString *)directoryPath {        for (NSDictionary *configuration in configurations) {        NSImage *launchImage = [self generateLaunchImageWithImage:originalImage forSize: NSSizeFromString(configuration[@"size"])];                NSString *filePath = [NSString stringWithFormat:@"%@/%@.png", directoryPath, configuration[@"name"]];        [self exportImage:launchImage toPath:filePath];    }    [[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:directoryPath isDirectory:YES]];}- (NSImage *)generateAppIconWithImage:(NSImage *)fromImage forSize:(CGSize)toSize  {        NSRect toFrame = NSMakeRect(.0, .0, toSize.width, toSize.height);    toFrame = [[NSScreen mainScreen] convertRectFromBacking:toFrame];        NSImageRep *imageRep = [fromImage bestRepresentationForRect:toFrame context:nil hints:nil];    NSImage *toImage = [[NSImage alloc] initWithSize:toFrame.size];        [toImage lockFocus];    [imageRep drawInRect:toFrame];    [toImage unlockFocus];        return toImage;}- (NSImage *)generateLaunchImageWithImage:(NSImage *)fromImage forSize:(CGSize)toSize {        // 计算目标小图去贴合源大图所需要放大的比例    CGFloat wFactor = fromImage.size.width / toSize.width;    CGFloat hFactor = fromImage.size.height / toSize.height;    CGFloat toFactor = fminf(wFactor, hFactor);        // 根据所需放大的比例,计算与目标小图同比例的源大图的剪切Rect    CGFloat scaledWidth = toSize.width * toFactor;    CGFloat scaledHeight = toSize.height * toFactor;    CGFloat scaledOriginX = (fromImage.size.width - scaledWidth) / 2;    CGFloat scaledOriginY = (fromImage.size.height - scaledHeight) / 2;    NSRect fromRect = NSMakeRect(scaledOriginX, scaledOriginY, scaledWidth, scaledHeight);        // 生成即将绘制的目标图和目标Rect    NSRect toRect = NSMakeRect(.0, .0, toSize.width, toSize.height);    toRect = [[NSScreen mainScreen] convertRectFromBacking:toRect];    NSImage *toImage = [[NSImage alloc] initWithSize:toRect.size];        // 绘制    [toImage lockFocus];    [fromImage drawInRect:toRect fromRect:fromRect operation:NSCompositeCopy fraction:1.0];    [toImage unlockFocus];        return toImage;}- (void)exportImage:(NSImage *)image toPath:(NSString *)path {        NSData *imageData = image.TIFFRepresentation;    NSData *exportData = [[NSBitmapImageRep imageRepWithData:imageData] representationUsingType:NSPNGFileType properties:@{}];        [exportData writeToFile:path atomically:YES];}NSString *)platform fromOriginalImage:(NSImage *)originalImage {

NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"QiConfiguration" ofType:@"plist"];
NSDictionary *configuration = [NSDictionary dictionaryWithContentsOfFile:plistPath];
NSArray<NSDictionary *> *items = configuration[platform];

NSString *directoryPath = [[_pathField.stringValue stringByAppendingPathComponent:platform] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil];

if ([platform containsString:@"AppIcons"]) {
[self generateAppIconsWithConfigurations:items fromOriginalImage:originalImage toDirectoryPath:directoryPath];
}
else if ([platform containsString:@"LaunchImages"]) {
[self generateLaunchImagesWithConfigurations:items fromOriginalImage:originalImage toDirectoryPath:directoryPath];
}
}

- (void)generateAppIconsWithConfigurations:(NSArray<NSDictionary *> *)configurations fromOriginalImage:(NSImage *)originalImage toDirectoryPath:(NSString *)directoryPath {

for (NSDictionary *configuration in configurations) {
NSImage *appIcon = [self generateAppIconWithImage:originalImage forSize:NSSizeFromString(configuration[@"size"])];
NSString *filePath = [NSString stringWithFormat:@"%@/%@.png", directoryPath, configuration[@"name"]];
[self exportImage:appIcon toPath:filePath];
}
[[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:directoryPath isDirectory:YES]];
}

- (void)generateLaunchImagesWithConfigurations:(NSArray<NSDictionary *> *)configurations fromOriginalImage:(NSImage *)originalImage toDirectoryPath:(NSString *)directoryPath {

for (NSDictionary *configuration in configurations) {
NSImage *launchImage = [self generateLaunchImageWithImage:originalImage forSize: NSSizeFromString(configuration[@"size"])];

NSString *filePath = [NSString stringWithFormat:@"%@/%@.png", directoryPath, configuration[@"name"]];
[self exportImage:launchImage toPath:filePath];
}
[[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:directoryPath isDirectory:YES]];
}

- (NSImage *)generateAppIconWithImage:(NSImage *)fromImage forSize:(CGSize)toSize {

NSRect toFrame = NSMakeRect(.0, .0, toSize.width, toSize.height);
toFrame = [[NSScreen mainScreen] convertRectFromBacking:toFrame];

NSImageRep *imageRep = [fromImage bestRepresentationForRect:toFrame context:nil hints:nil];
NSImage *toImage = [[NSImage alloc] initWithSize:toFrame.size];

[toImage lockFocus];
[imageRep drawInRect:toFrame];
[toImage unlockFocus];

return toImage;
}

- (NSImage *)generateLaunchImageWithImage:(NSImage *)fromImage forSize:(CGSize)toSize {

// 计算目标小图去贴合源大图所需要放大的比例
CGFloat wFactor = fromImage.size.width / toSize.width;
CGFloat hFactor = fromImage.size.height / toSize.height;
CGFloat toFactor = fminf(wFactor, hFactor);

// 根据所需放大的比例,计算与目标小图同比例的源大图的剪切Rect
CGFloat scaledWidth = toSize.width * toFactor;
CGFloat scaledHeight = toSize.height * toFactor;
CGFloat scaledOriginX = (fromImage.size.width - scaledWidth) / 2;
CGFloat scaledOriginY = (fromImage.size.height - scaledHeight) / 2;
NSRect fromRect = NSMakeRect(scaledOriginX, scaledOriginY, scaledWidth, scaledHeight);

// 生成即将绘制的目标图和目标Rect
NSRect toRect = NSMakeRect(.0, .0, toSize.width, toSize.height);
toRect = [[NSScreen mainScreen] convertRectFromBacking:toRect];
NSImage *toImage = [[NSImage alloc] initWithSize:toRect.size];

// 绘制
[toImage lockFocus];
[fromImage drawInRect:toRect fromRect:fromRect operation:NSCompositeCopy fraction:1.0];
[toImage unlockFocus];

return toImage;
}

- (void)exportImage:(NSImage *)image toPath:(NSString *)path {

NSData *imageData = image.TIFFRepresentation;
NSData *exportData = [[NSBitmapImageRep imageRepWithData:imageData] representationUsingType:NSPNGFileType properties:@{}];

[exportData writeToFile:path atomically:YES];
}

上述是工程的所有代码,代码较多。建议有需要的同学移步至工程源码阅读(点击阅读原文查看)。

地址:

https://github.com/QiShare/QiAppIconGenerator


相关阅读

不会PS如何快速得到自己想要的图片——iOS图标&启动图生成器


界世的你当不

只做你的肩膀

iOS 图标&启动图生成器(二)_第5张图片 iOS 图标&启动图生成器(二)_第6张图片

 360官方技术公众号 

技术干货|一手资讯|精彩活动

空·


你可能感兴趣的:(iOS,移动开发)