iOS 圖標(biāo)&啟動(dòng)圖生成器(二)

級別: ★★☆☆☆
標(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)著字典的一對keyvalue;
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件舵、platformBoxpathField不需要響應(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)介紹pathButtonexportButton的響應(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)分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末降铸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子摇零,更是在濱河造成了極大的恐慌推掸,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異终佛,居然都是意外死亡俊嗽,警方通過查閱死者的電腦和手機(jī)雾家,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門铃彰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人芯咧,你說我怎么就攤上這事牙捉。” “怎么了敬飒?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵邪铲,是天一觀的道長。 經(jīng)常有香客問我无拗,道長带到,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任英染,我火速辦了婚禮揽惹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘四康。我一直安慰自己搪搏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布闪金。 她就那樣靜靜地躺著疯溺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哎垦。 梳的紋絲不亂的頭發(fā)上囱嫩,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音漏设,去河邊找鬼墨闲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛愿题,可吹牛的內(nèi)容都是我干的损俭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼潘酗,長吁一口氣:“原來是場噩夢啊……” “哼杆兵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仔夺,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤琐脏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體日裙,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吹艇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昂拂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片受神。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖格侯,靈堂內(nèi)的尸體忽然破棺而出鼻听,到底是詐尸還是另有隱情,我是刑警寧澤联四,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布撑碴,位于F島的核電站,受9級特大地震影響朝墩,放射性物質(zhì)發(fā)生泄漏醉拓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一收苏、第九天 我趴在偏房一處隱蔽的房頂上張望亿卤。 院中可真熱鬧,春花似錦倒戏、人聲如沸怠噪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽傍念。三九已至,卻和暖如春葛闷,著一層夾襖步出監(jiān)牢的瞬間憋槐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工淑趾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浪南,地道東北人慰照。 一個(gè)月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親派敷。 傳聞我的和親對象是個(gè)殘疾皇子曼追,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容