创建一个跟分辨率无关的 iOS 8 应用

jopen 10年前

自从iOS退出依赖,png就被用来制作图标。这已经是如此简单了,那为什么还会有人会要考虑其他的解决方案呢? 一般的制作图标的工作流程是这样的:“我想要新添加一个按钮,这个action需要一个图标,设计师把图标发email给我,我们就完工了.” 如此设计师把这项工作安排到了他的工作流之中,然后按部就班完成了这项工作,之后你就会受到一封Email,里面有2个png文件(retina 和 非 retina 屏幕)

等等... 这个图标需在改改颜色,以满足我们想要按钮呈现高亮状态的需求. 因此你重复了上述流程,并获得了另外2个png。实际上,我们还会继续需要让这个按钮在某些特定的情况下呈现禁用状态,因此我们又会需要禁用状态的图标. 好吧,iOS现在又支持@3x分辨率的了, 因此我会需要再输出这些图标及其各种状态的文件… 而设计师现在很忙,短时间内不能为我完成这项工作了?! 如今不能获得这些资源文件的我如何让我的应用适配新的屏幕尺寸呢?

任何曾今为那些以开发移动应用为目标的团队工作过的人,好像大都遇到过上述其中一个或者全部的状况. 它可能会是非常令人沮丧的,也会让你的团队白白浪费许多时间. 在Craftsy这里,我们向后退一步,看看我们尝试真正要去完成的是什么,还要看看我们是否错失了另外一种可能的更好的方法. 而当我说后退一步的时候,我的意思是做一次大的退步.

我们尝试要去完成的是什么?

目标:在屏幕上绘制一个图标,使其像素完美的适配那个屏幕的分辨率.

在最基本的层面上,这个目标就是我们现 在正在做的. 然后问题就会是,我们是否使用了最有效率和更加务实的方式完成了这个目标? 让我们来看看我们的选择。我们可以继续去构建并维护一个相当简单的图像库,并让从开发和设计两头都承担保持图像库更新的工作量. 或者我们还可以采用一种动态的方式,只去踏踏实实的处理绝对必要的变化和工作,并将时间专注的花在其它问题的解决上.

进入PaintCode, 它是一个 OSX 应用程序,其名字正如你所预想的其功能. 它能解析从PhotoShop创建或导入的矢量图片资源,然后将其”绘制“成一个编码库. 我不会花太多时间向你推销 PaintCode, 我会建议你上网试试他们的在线解决方案. 不过未来在完成移除我们对于图像的依赖这项任务方面,会证明使用它是必要的。看待PaintCode及其价格的一个好的方式就是,就好像它是为你的团队雇 佣的一个开发者. 你要问你自己的是 “你会需要雇佣一个开发者,它可以编写完美的可控代码,却只需要开发应用成本负担一次性的花费?” 好了,那就是我所想的.

创建一个跟分辨率无关的 iOS 8 应用

PaintCode,  正在改变开着人员同设计师协同工作的方式.

设计师们其实拥有的大量的诱人数据,这些数据都隐藏域他们的photoshop文件之中。这些数据被称作矢量图形. 为什么矢量图形很重要呢 ? 简单来讲, iOS的世界里现在正面临的,与分辨率和不同状态效果有关问题,在设计界早就都解决了. 当设计团队创建一个图标的时候,他们很可能会采用一种基于矢量图形的,使用了线条和贝赛尔曲线的方法. 这就能很好的解决创建“分辨率独立 ”的问题. 那样如果我们需要适配屏幕尺寸为1000dpi的图标,或者只是20dpi的缩略图,它都可输出来,而且看上去都是像素完美的!

那么我们就要问了,如果开发者可以简单的胜任这种现有的技术,会怎么样呢? 那就会使得我们不用再去过多考虑像素问题了. 虽然这常常成为一种可行的方式, 但和只是持续的绘制和输出图形比起来,使用编程的方式用代码绘制矢量图形需要花费更多的时间,直到 PaintCode 出现才让收益大过支出. 并且,大多数的程序员们都没有进行细节设计的好眼力,所以最终的成果大有可能不会符合预期. 因此,设计团队的存在其实可以促进 PaintCode Objective-C 文件的输出.

而通过管理起开发者和设计师间的所有这些代码,PaintCode一举消除了所有这些问题 ! 设计师只要将他的矢量图形从Photoshop导入PaintCode中,做一些小维护以方便同开发者的对接, 瞧, 开发人员现在可以绘制符合任何分辨率需求的图形资源了.

这一功能开始改变了设计师和开发者之间的沟通方式. 标准一旦建立起来,交谈的内容立刻从 “你能把资源发Email给我吗 ?” 变成了 “让我知道你什么时候把那个图标迁入了git ”. 有时候甚至都不要交流;设计师调整完图标之后,应用程序就会帮他把新的版本绘制出来.

那么是不是我只要买了PaintCode,一切就都会变得更简单了呢?

可叹的是,并不是这样的. 事情并没有那样简单,PaintCode仅仅只是一个工具,而你如何去利用好这个工具将最终决定你是否能成功. 而长远看来,将PaintCode和Xcode恰当的组合在一起使用,我们就能够省下大量和时间和美金. 下面我将会描述一下我们如何使用PaintCode实现对图标资源和图标不同状态的管理. 

第一步 - 指定一个设计师能配合的方案

开发人员必须是第一个参与进去的,并为团队设定好PaintCode如何为他们工作. PaintCode 可以加入变量,开发人员可以在最后把这些变量加进去. 我们首先要处理的就是图标的尺寸.

如果你使用的是正方形,而正方不需要两 个维度数值(两个维度的值相等),因此图标很容易扩展 - 它们只需要一个维度. 因此我们想PaintCode新增了一个公共的(可以用编码复制) 数值,它被叫做 “size”. 从设计的角度讲,我们不能直接使用这个数值, 因此我们想PaintCode添加了一个表达式,来基于那个尺寸(size)计算出要绘制的区域:

PaintCode:
makeSize(size, size)

我们只需要传入一个尺寸就行了,因为帧的原点总是会在 0,0. 现在,我们遵守的标准如下:

  1. 每一个图标画图都必须包含一个帧作为根

  2. 这个作为根帧的原点必须是在 0,0

  3. 根帧的尺寸必须被绑定到来自用代码所传入的参数计算出来的尺寸值

设计将需要符合帧的标准,而现在让我们假定所有的东西都是基于那个根帧被绘制出来的, 因此是可以无限扩展的.

现在,我们需要通过编写代码来改变图标的颜色。不幸的是,PaintCode当前还不支持直接传入颜色。不过,就像我之前说的,PaintCode 是一个工具,一切取决于你怎么使用。要处理颜色,我们创建红,绿,蓝和Alpha4个值。在PaintCode中,我们就可以使用如下的表达式:

makeColor(red, green, blue, alpha)

这样就可以在PaintCode中使用传入的颜色了。同时,我们的协议改成:
 

绘制的图标必须和代码库生成的颜色变量绑定。

好了,我们已经可以通过代码获取图标的尺寸和颜色。这种方法对于应用中一般的图标绘制很有用,不过对于按钮来说又该如何处理呢?

在iOS中,我们所熟知的按钮有4种状态,分别是Normal(常规状态),Highligted(高亮状态),Selected(被选择状态)和 Disable(禁用状态)。从PaintCode的角度看,我们希望在设计和用户体验上能够展现出这些不同的状态。为了解决这个问题,我们添加了一个新 的public参数,叫做drawType。有了这个参数,图标在绘制的时候就有了多种类型的选择。我们可以创建4个表达式来决定绘制哪一种状态:

Normal
drawType < 1 || drawType > 3

Highlighted

drawType == 1

Selected

drawType == 2

Disabled

drawType == 3

每个表达式都会产生一个布尔值。我们把这个布尔值和图标路径的可见度关联起来。也就是说,当drawType是0的时候,只有Normal的图标可以绘制出来。drawType等于1的时候会隐藏Normal的图标,同时显示Highlighted的图标,以此类推。

现在我们标准里就有了一个叫做drawType的可选参数:

如果图标需要基于其状态展现不同的外观,那就把每个图标的可见性绑定到所需的状态上. 而普通的Normal则是默认的状态.

现在从代码的角度,我们得到了下面这些东西:
- (void) draw(IconName)WithSize: (CGFloat)size red:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha drawState:(CGFloat)drawState;

这样下去开发者就拥有了对要绘制的图标的尺寸,整体颜色以及状态的控制能力. 没有必要传入设备的尺寸,因为这完全是自动处理的. 现在开发者可以把外观下发到设计团队,由他们来做出符合标准的设计.

第二部 - 在标准范围内进行设计工作

设计团队将资源文件从Photoshop导入PaintCode,并让资源文件的规格符合上述的设计标准.

  1. 添加一块画布来管理图标

  2. 添加一个根帧到附上了由计算得出了尺寸,原点则在0,0位置的画布上

  3. 图标需要作为一个组绘制在根帧的范围内, 如有必要,附件会适当的以帧对其. 
    创建一个跟分辨率无关的 iOS 8 应用
    (例如,这个图片左右撑开(支撑),上下则弹性(弹簧)对齐)

  4. 颜色被附着到了图标上
    创建一个跟分辨率无关的 iOS 8 应用

  5. 如有必要,这一过程可以在不同的状态下重复,而这些组的可见性被附着到了状态的值上 (普通Normal, 高亮Highlighted, 等等.)
    创建一个跟分辨率无关的 iOS 8 应用

现在,没有任何实际的代码编写,PaintCode帮助生成类似于下面的东西给开发人员编码解决:

+ (void)drawRecoStateIconWithSize: (CGFloat)size red: (CGFloat)red green: (CGFloat)green blue: (CGFloat)blue alpha: (CGFloat)alpha drawType: (CGFloat)drawType;  {     //// Variable Declarations     CGSize sizeCalc = CGSizeMake(size, size);     UIColor* fillColor = [UIColor colorWithRed: red green: green blue: blue alpha:alpha];     BOOL normal = drawType < 1 || drawType > 3;     BOOL selected = drawType == 2;     //// Frames     CGRect frame = CGRectMake(0, 0, sizeCalc.width, sizeCalc.height);     //// Subframes     CGRect recoOrange = CGRectMake(CGRectGetMinX(frame) - 0.01, CGRectGetMinY(frame) + floor(CGRectGetHeight(frame) * 0.11995 - 0.48) + 0.98, CGRectGetWidth(frame) + 0.02, floor(CGRectGetHeight(frame) * 0.88022 + 0.5) - floor(CGRectGetHeight(frame) * 0.11995 - 0.48) - 0.97);       //// Reco Orange     {         if (normal)         {             //// defaultType Drawing             UIBezierPath* defaultTypePath = UIBezierPath.bezierPath;             [defaultTypePath moveToPoint: CGPointMake(CGRectGetMinX(recoOrange) + 0.00000 * CGRectGetWidth(recoOrange), CGRectGetMinY(recoOrange) + 0.99740 * CGRectGetHeight(recoOrange))];             [defaultTypePath addLineToPoint: CGPointMake(CGRectGetMinX(recoOrange) + 0.00000 * CGRectGetWidth(recoOrange), CGRectGetMinY(recoOrange) + 0.16623 * CGRectGetHeight(recoOrange))];              ...etc...             [defaultTypePath closePath];             [fillColor setFill];             [defaultTypePath fill];         }                     if (selected)         {              //// type2 Drawing                     UIBezierPath* type2Path = UIBezierPath.bezierPath;             [type2Path moveToPoint: CGPointMake(CGRectGetMinX(recoOrange) + 0.00000 * CGRectGetWidth(recoOrange), CGRectGetMinY(recoOrange) + 1.00000 * CGRectGetHeight(recoOrange))];                      [type2Path addLineToPoint: CGPointMake(CGRectGetMinX(recoOrange) + 0.00000 * CGRectGetWidth(recoOrange), CGRectGetMinY(recoOrange) + 0.16623 * CGRectGetHeight(recoOrange))];              ...etc...             [type2Path closePath];             [fillColor setFill];             [type2Path fill];         }     }  }

第三步 - 适应代码由PaintCode产生

一旦你有你产生PaintCode实现,这真的取决于你希望如何弥合到你的代码。一种选择是直接执行带有参数的方法。我们选择了选项2,包装方法,简化了一些从PaintCode要求.

例如,在色彩传递的时候,它更方便使用的UIColor不是一个接一个打出来的RGBA值。因此,对于RGBA转换器来说一个简单的UIColor是一款 不错的选择。如果我们需要,我们还具有把这些方法渲染成图像的能力。使用包装,让我们在弥补某些基本空白给上有了更多的选择,以延长由PaintCode 生成的代码,我会强烈建议采取这种方法.

PaintCode 所提供的其它优势

除了图片路径之外,PaintCode 也是一个可以引用其它资源的很棒的资源库. 我们还用它来管理和组织下面这些东西:

  • 颜色 - PaintCode 提供了一个很好的调色板界面. 我们使用的是 [PaintCodeLibrary colorCustomOrange], 而不是[UIColor colorWithRed/Blue/Green/Alpha] .

  • 渐变 - 用PaintCode沟通像渐变和覆遮罩这样的设计元素容易了很多. 你可以用编程的方式编辑透明度值,使渐变更加的精确,且容易修改.

  • 阴影 - 阴影可以被存储并且也能够被开放访问, 这就允许设计能控制整个通用的阴影.

  • Swift:因为PaintCode只是在生成你使用的代码,把它转换成一种新的语言(像Swift)就只要点击一下按钮这么简单. 

这一切值得吗?

当然值得。从解决“依赖”中释放自己是一个巨大的进步,在一个生态系统之上,有效节省了团队开发iOS应用的时间,还解决了现在头痛的事情。

PaintCode在之前一段的时间里,是不太实际的,但是现在可以了。这种方式让你的团队把注意力更多地集中在问题上,他们应该解决问题而不是重构,去调节新的屏幕和分辨率,去把焦点集中在建立一个更稳定,更效率的应用上吧。