美文网首页QiShare文章汇总
iOS 图标&启动图生成器(二)

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

作者: QiShare | 来源:发表于2019-04-18 18:05 被阅读59次

    级别: ★★☆☆☆
    标签:「iPhone app 图标」「图标生成」「启动图生成」「QiAppIconGenerator」
    作者: Xs·H
    审校: QiShare团队


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

    上篇文章,本篇文章介绍生成器的实现细节。

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

    一、 界面

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

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

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

    二、 资源文件

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

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

    三、 ViewController

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

    其中,imageViewplatformBoxpathField不需要响应方法。并且,platfromBox_pathField的默认/记忆数据由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();
    }
    

    这里忽略这三个控件,重点介绍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;
            }
        }];
    }
    
    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];
        }
    }
    
    - (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 {
        
        // 计算目标小图去贴合源大图所需要放大的比例
        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];
    }
    

    上述是工程的所有代码,代码较多。建议有需要的同学移步至工程源码阅读。


    小编微信:可加并拉入《QiShare技术交流群》。

    关注我们的途径有:
    QiShare(简书)
    QiShare(掘金)
    QiShare(知乎)
    QiShare(GitHub)
    QiShare(CocoaChina)
    QiShare(StackOverflow)
    QiShare(微信公众号)

    推荐文章:
    算法小专栏:“D&C思想”与“快速排序”
    iOS 避免常见崩溃(二)
    算法小专栏:选择排序
    iOS Runloop(一)
    iOS 常用调试方法:LLDB命令
    iOS 常用调试方法:断点
    iOS 常用调试方法:静态分析
    奇舞周刊

    相关文章

      网友评论

        本文标题:iOS 图标&启动图生成器(二)

        本文链接:https://www.haomeiwen.com/subject/cltdiqtx.html