本文由CocoaChina译者 @唧唧歪歪 翻译,作者:Hector Matos
恶梦
想象你正在干活,你的上级要求你在工程中加入一些图片。你找到团队中的设计师(如果你们有一个的话),鼓起勇气,问他要这些图片。不过,通常他都会忙得根本没时间帮你。因为他要做的活儿比你还多。说实话,你到底见过一个设计师有多少活儿要做吗?!简直多到令人发指啊!所以你最不想做的就是成为那个再给他加活儿的人了。特别是那些活儿对设计师来说简单得让人厌烦。更不用说,设计师也要按顺序干活,你拿到那些图片也是几天之后了。所以我们还是来看看如何用Xcode 的Asset Catalog来处理这些麻烦事吧。
麻烦事No.1:“能改一下这个图片的颜色吗?”
目前为止iOS已经提供了一些相当复杂的方法来处理工程中的图片,但很不幸的是,却没有用来处理这种情况的方法。我已经数不清楚有多少次我们因为什么便捷性支持或者是客户不喜欢而改变工程的颜色了。现在在工程中全局改变UIColor已经不是什么难题了,但是我们还要改变工程中图片的颜色。这样我们就不得不回去找已经忙得要死的设计师了,就因为我们太懒而不去学学怎么用Photoshop。当然我们也可以写代码来完成,不过看起来相当复杂,而且还容易造成泄漏。在iOS7 之前,写代码改变图片的颜色和下面差不多(还有很多其他的方法):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
class func image(name: String, withColor color: UIColor) -> UIImage? { if var image = UIImage(named: name) {
// begin a new image context, to draw our colored image onto. Passing in zero for scale tells the system to take from the current device's screen scale.
UIGraphicsBeginImageContext(image.size, false , 0)
// get a reference to that context we created
let context = UIGraphicsGetCurrentContext()
// set the context's fill color
color.setFill()
// translate/flip the graphics context (for transforming from CoreGraphics coordinates to default UI coordinates. The Y axis is flipped on regular coordinate systems)
CGContextTranslateCTM(context, 0.0, image.size.height)
CGContextScaleCTM(context, 1.0, -1.0)
// set the blend mode to color burn so we can overlay our color to our image.
CGContextSetBlendMode(context, kCGBlendModeColorBurn)
let rect = CGRect(origin: CGPointZero, size: image.size)
CGContextDrawImage(context, rect, image.CGImage)
// set a mask that matches the rect of the image, then draw the color burned context path.
CGContextClipToMask(context, rect, image.CGImage)
CGContextAddRect(context, rect)
CGContextDrawPath(context, kCGPathFill)
// generate a new UIImage from the graphics context we've been drawing onto
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
return nil
}
|
在两个大版本升级之后,我还能看到这样的代码。在iOS7中,我们有了imageWithRenderingMode,这是UIImage的一个方法,参数是有三个选项的枚举值UIImageRenderingMode。
1
2
3
4
5
|
typedef NS_ENUM(NSInteger, UIImageRenderingMode) { UIImageRenderingModeAutomatic, // Use the default rendering mode for the context where the image is used
UIImageRenderingModeAlwaysOriginal, // Always draw the original image, without treating it as a template
UIImageRenderingModeAlwaysTemplate, // Always draw the image as a template image, ignoring its color information
} NS_ENUM_AVAILABLE_IOS(7_0);
|
UIImageRenderingModeAlwaysOriginal 就和字面的意思一样,这个模式告诉系统按照图片文件原来的样子渲染图片。
UIImageRenderingModeAlwaysTemplate 这是最有意思的模式。首先会扫描你的图片,然后从图片中所有不透明的像素创建一个模板。这同时也会忽略图片的所有颜色信息。你可以使用UIView子类的tintColor属性来给图片填充你选择的颜色。
UIImageRenderingModeAutomatic 这个模式由系统根据图片的使用环境来决定如何渲染图片。如果你的图片是用在比如UITabBar、UINavigationBar、UIToolbar 和UISegmentedControl这些地方,图片使用AlwaysTemplate渲染模式。图片用在其他的地方则会使用AlwaysOriginal渲染模式。
在了解了上面的内容之后,之前我们改变图片颜色的代码就可以简化成下面这样的了:
1
2
3
4
|
if var imageToChange = imageView.image?.imageWithRenderingMode(.AlwaysTemplate) {
imageView.image = imageToChange
imageView.tintColor = .redColor() //Setting the tint color is what changes the color of the image itself!
}
|
是不是很神奇?用代码改变图片的颜色,现在变得简单多了。
等等,还没完呢!其实不需要代码也可以改变图片的颜色。
从Xcode 6开始,imageWithRenderingMode已经集成到Asset Catalog里了。如果你在Asset Catalog里选择了一个图片,在右边的Attributes Inspector里,就可以像下图那样把Render As选项改成Template Image。
就是这么简单。甚至你还可以通过在Storyboard中,在Attributes Pane中改变UIImageView的tintColor属性,来改变imageView中的图片的颜色。
麻烦事No.2:“能给我这个的3X分辨率的图吗?”
这个的确很烦,因为每个app的每个设计师被问这个问题都至少一年了。貌似苹果每年都会增加一种新的屏幕分辨率,今年我也持怀疑态度。随着硬件技术的发展,苹果总是走在前沿,总是在尽可能地提高屏幕的ppi。很不幸,这意味着我们不能直接在“预览”中放大已有的图片,因为这会造成诸如图片像素化和产生锯齿等问题。通俗点,就是我们的图片变丑了,噁!每次你叫设计师出一张已有图片的3x分辨率图,某个地方就又要死一只独角兽了。这实际上也解释了为什么现在看不到这种神奇的生物了。
所以去年我在WWDC上提到的最好的消息,就是Xcode 6 及以上版本支持在Asset Catalog中使用矢量PDF了。你的设计师知道这是什么意思,但是大致上,PDF是矢量元素的事实标准。矢量文件包含一个元素的很多元数据,用来告诉系统如何渲染这些内容,而这些和屏幕分辨率无关。举个通俗易懂例子,一个圆形的矢量PDF图,当它渲染成5像素宽和渲染成5000000像素宽时是一样清晰的。
在iOS平台,Xcode是在编译时,根据你的矢量PDF图的大小,生成1x、2x和3x图。如果你的PDF图是45*45px,那么Xcode会在编译时生成下面3个PNG:
45*45px :1x设备用的(iPhone 3G and 3GS)
90*90px :2x或Retina显示设备用的(iPhone 4, 4S, 5, 5S, and 6)
135*135px :3x设备用的(iPhone 6 Plus 及以上)
这也意味着当有更高的屏幕分辨率时,Xcode可以根据已有的矢量PDF放大图片,这样自动就支持以后的设备了。还有,如果你是OS X开发者,那么矢量PDF就更好用了,OS X app完全支持矢量PDF,你可以用代码缩放图片而不会失真。
而你需要做的就是,找你的好基友设计师拿到这些矢量PDF文件,然后在Asset Catalog的Attribtues Pane中,在Scale Factor的下拉框中选择Single Vector就行了。
你可以直接把PDF拖到Asset Catalog中,然后进行设置。
麻烦事No.3:“能给我新设备的启动图吗?”
启动图对于app来说还是蛮重要的。这是启动app后最先看到的,它会给用户一个app其余部分是如何设计的第一印象。如果我看到一个设计得很糟糕的启动图,我会认为app其他地方也好不到哪去,当然这只是我的情况。对于我们那可怜的设计师来说,每次有新设备出来时,他们都知道要放大启动图来支持新设备的分辨率。对于iPhone 6 和 iPhone 6 Plus,如果你没有为这两个设备准备对应的启动图,那么app就会工作在放大模式。启动图还在Asset Catalog中,但是我建议把它拆出来,因为启动图也升级了。现在,你可以使用LaunchScreen xibs。
在工程文件中,你可以指定app在启动时加载的xib,这样你就不需要准备9张启动图了。LaunchScreen.xib还支持自动布局,这样我们就能分块构建启动屏幕了。按如下这样设置:
首先创建一个xib文件。你可以在如下图所示的地方选择Launch Screen类型的xib。
然后打开工程文件,选择app的target,在Launch Screen file处选择你的Launch Screen .xib文件。
尽可能地利用Launch Screen吧。你肯定不想被抓到在问设计师要一年后新出的手机平板的8x启动图。
麻烦事No.4:“能把这些按钮的图片拉长一点吗?”
这种情况发生的概率比你想象的要高得多。对于一张pattern image或者是有圆角的图片,考虑到有更大的屏幕,你需要重新调整图片的大小,以免图片拉伸出现失真。Natasha发布了一篇很棒的文章来说明如何编程解决这个问题,但是我们也可以在Xcode 6的Asset Catalog中搞定它。顺便说一下,我强烈建议你在继续往下读之前,看一下Natasha的文章,这样你就能理解到底发生了什么。免责声明:下面的图片等是直接从Natasha的文章中拷贝过来的。Sorry!
好了,我们继续。
在之前,一般用类似下面的代码来获得可改变大小的图片:
1
2
3
4
|
let edgeInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0) let backgroundButtonImage = UIImage(named: "purple_button" )?.resizableImageWithCapInsets(edgeInsets)
purpleButton.setBackgroundImage(backgroundButtonImage, forState: .Normal) |
这将会得到一张和下面类似的图片:
在运行时,会拉伸距离UIImageView的container的边框8像素的中间部分,这样就能保留圆角,得到下面这样的:
多亏了Xcode中Asset Catalog的slice和dice,我们不需要代码也能拉伸图片。首先在Xcode中选中图片,然后点击右下角的Show Slicing:
你现在应该能看到slicing 面板和一个按钮"Start Slicing"。
在你点击按钮之后,会显示下面的三个选项:
左边的按钮用于horizontal edge insets,右边的按钮用于vertical edge insets,中间的则是两个都有。在我们的例子中要保留圆角,所以我们按中间的按钮,告诉系统我们想要按钮的中间在水平和垂直方向拉伸。在按下按钮之后,就能看到一些可以拖动的细条,这可以设置从哪里开始拉伸图片。
系统会保留深紫色的区域,浅紫色的区域会被拉伸。
更厉害的是,Xcode自动找到了圆角,所以我们不需要设置从哪里开始拉伸图片。最后别忘了在Attribtues pane中设置图片是可拉伸的。
如果我是你的话,我就会尝试并习惯这个功能。有了这个无价之宝,你就不用再在resizableImageWithCapInsets方法中填写那些神奇的数字了,也能帮助你分离view逻辑和app逻辑。
结论
我很确定,我们开发者几乎每天都还会做很多其他事去麻烦设计师们,但至少我们能多用用这些功能,让他们稍微休息一会儿。毕竟编程能解决一切问题,何乐而不为呢?