級別: ★★☆☆☆
標(biāo)簽:「iPhone app 圖標(biāo)」「圖標(biāo)生成」「啟動(dòng)圖生成」「QiAppIconGenerator」
作者: Xs·H
審校: QiShare團(tuán)隊(duì)
一個(gè)完整的app都需要多種尺寸的圖標(biāo)和啟動(dòng)圖涤妒。一般情況,設(shè)計(jì)師根據(jù)開發(fā)者提供的一套規(guī)則赚哗,設(shè)計(jì)出圖標(biāo)和啟動(dòng)圖供開發(fā)人員使用她紫。但最近我利用業(yè)余時(shí)間做了個(gè)app,不希望耽誤設(shè)計(jì)師較多時(shí)間屿储,就只要了最大尺寸的圖標(biāo)和啟動(dòng)圖各一個(gè)贿讹。本想著找一下現(xiàn)成的工具,批量生成需要的的圖片够掠,但最后沒有找到民褂,只好使用Photoshop切出了不同尺寸的圖片。這期間祖屏,設(shè)計(jì)師還換過一次圖標(biāo)和啟動(dòng)圖助赞,我就重復(fù)了切圖工作,這花費(fèi)了我大量的時(shí)間袁勺。于是事后雹食,作者開發(fā)了一個(gè)mac app——圖標(biāo)&啟動(dòng)圖生成器(簡稱生成器)以提高工作效率。作者用兩篇文章分別介紹生成器的使用和實(shí)現(xiàn)細(xì)節(jié)期丰。
接上篇文章群叶,本篇文章介紹生成器的實(shí)現(xiàn)細(xì)節(jié)。
生成器的工程非常簡單钝荡,可以概括為一個(gè)界面街立、一個(gè)資源文件和一個(gè)ViewController。結(jié)構(gòu)如下圖埠通。
一赎离、 界面
生成器app只有一個(gè)界面,因?yàn)榻缑鎻?fù)雜度較小端辱,作者選用了Storyboard+Constraints的方式進(jìn)行開發(fā)梁剔。下圖顯示了界面中的控件和約束情況虽画。
其中各控件對應(yīng)的類如下所示。
控件 | 類 |
---|---|
圖片框 | NSImageView |
平臺選擇器 | NSComboBox |
路徑按鈕 | NSButton |
路徑文本框 | NSTextField |
導(dǎo)出按鈕 | NSButton |
二荣病、 資源文件
app所支持的平臺規(guī)則數(shù)據(jù)從資源文件QiConfiguration.plist
中獲取码撰。QiConfiguration.plist
相當(dāng)于一個(gè)字典,每個(gè)平臺對應(yīng)著字典的一對key
和value
;
value
是一個(gè)數(shù)組个盆,存儲(chǔ)著該平臺所需要的一組尺寸規(guī)格數(shù)據(jù)(item)脖岛;
item
是尺寸規(guī)格數(shù)據(jù)的最小單元,內(nèi)部標(biāo)記了該尺寸規(guī)格的圖片的用途颊亮、名稱和尺寸柴梆。
QiConfiguration.plist
的具體結(jié)構(gòu)如下圖所示。
三编兄、 ViewController
工程使用默認(rèn)的ViewController
管理界面轩性、資源數(shù)據(jù)和邏輯。
首先狠鸳,界面控件元素在ViewController
中對應(yīng)下圖中的5個(gè)實(shí)例揣苏。
其中,imageView
件舵、platformBox
和pathField
不需要響應(yīng)方法卸察。并且,platfromBox
和_pathField
的默認(rèn)/記憶數(shù)據(jù)由NSUserDefaults
管理铅祸。
static 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();
}
這里忽略這三個(gè)控件坑质,重點(diǎn)介紹pathButton
和exportButton
的響應(yīng)方法中的代碼邏輯。
1. - pathButtonClicked:
pathButton
的響應(yīng)方法負(fù)責(zé)打開文件目錄临梗,并回傳選擇的路徑給pathField
涡扼,以顯示出來。
代碼如下:
- (IBAction)pathButtonClicked:(NSButton *)sender {
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
openPanel.canChooseDirectories = YES;
openPanel.canChooseFiles = NO;
openPanel.title = @"選擇導(dǎo)出目錄";
[openPanel beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse result) {
if (result == NSModalResponseOK) {
self.pathField.stringValue = openPanel.URL.path;
}
}];
}
2. - exportButtonClicked:
exportButton
的響應(yīng)方法負(fù)責(zé)根據(jù)imageView
中的源圖片盟庞、platform
中選擇的平臺規(guī)則和pathField
中顯示的導(dǎo)出路徑生成圖片并打開圖片所在的文件夾吃沪。
代碼如下:
- (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 = @"請先選擇源圖片、平臺和導(dǎo)出路徑";
alert.alertStyle = NSAlertStyleWarning;
[alert addButtonWithTitle:@"確認(rèn)"];
[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<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 {
// 計(jì)算目標(biāo)小圖去貼合源大圖所需要放大的比例
CGFloat wFactor = fromImage.size.width / toSize.width;
CGFloat hFactor = fromImage.size.height / toSize.height;
CGFloat toFactor = fminf(wFactor, hFactor);
// 根據(jù)所需放大的比例什猖,計(jì)算與目標(biāo)小圖同比例的源大圖的剪切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);
// 生成即將繪制的目標(biāo)圖和目標(biāo)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];
}
上述是工程的所有代碼票彪,代碼較多。建議有需要的同學(xué)移步至工程源碼閱讀不狮。
推薦文章:
算法小專欄:“D&C思想”與“快速排序”
iOS 避免常見崩潰(二)
算法小專欄:選擇排序
iOS Runloop(一)
iOS 常用調(diào)試方法:LLDB命令
iOS 常用調(diào)試方法:斷點(diǎn)
iOS 常用調(diào)試方法:靜態(tài)分析