人类通过不断制造先进的工具来改造世界。学习Mac软件开发,制造我们自己的生产力工具吧。
无论是iOS还是OSX App开发完成,发布前都需要设置各种尺寸大小的AppIcon图标。
通常的做法是设计师做一张1024*1024的大图,按App的要求缩成不同大小的图,然后工程师在逐个加到Xcode项目中。
我们希望能开发一个图片适配自动化工具自动实现这个过程。
这是iOS/iPad应用图标
OSX应用图标
点击AppIcon右键菜单上的Show In Finder
看到AppIcon.appiconset是一个文件夹,里面是不同大小的图标。另外有一个Contents.json文件来描述AppIcon中包括的图片的大小和路径.
这是iOS项目中的Contents.json内容。
Contents.json中的idiom分别为iPhone、iPad、Mac表示不同的系统平台。
通过上面的分析,我们想法是先对1024*1024的大图按不同平台的要求进行缩放;
然后在生成对应的Contents.json内容;最后把各个图标和Contents.json copy到AppIcon指定的目录下。
这样设计师做一张大图,通过我们的工具自动化完成缩放裁剪,自动添加到项目路径中,替代之前手工一个个添加到项目的繁琐的过程。
使用Xcode新建一个OSX应用命名为ImageAassetAutomator。MainMenu.xib界面设计如下:
界面上红色区域为ImageView,可以从Finder中拖动图片文件到这个区域,ImageView支持拖放事件,能自动识别文件的路径。
下面ComboBox下拉选择列表,可以选择iPhone,iPad,iPhone+iPad,OSX 四种模式。
Export按钮,点击后让用户选择一个路径,根据从ComboBox选择的平台,生成相应尺寸的图片和图片资源Contents.json,将这些图片资源文件复制到用户指定的路径中.
新建一个DragImageZone类,继承NSImageView,实现NSDraggingDestination拖放协议。
定义DragImageZoneDelegate协议,图片拖放完成后通过delegate将图片路径返回。
DragImageZone类的接口定义
#import <Cocoa/Cocoa.h>
@protocol DragImageZoneDelegate <NSObject>
@optional
-(void)didFinishDragWithFile:(NSString*)filePath;
@end;
@interface DragImageZone : NSImageView<NSDraggingDestination>
@property(weak) id<DragImageZoneDelegate> delegate;
@end
DragImageZone类的实现
#import "DragImageZone.h"
@implementation DragImageZone
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
[self registerForDraggedTypes:[NSArray arrayWithObjects:
NSFilenamesPboardType, nil]];
[self dropAreaFadeOut];
}
return self;
}
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self registerForDraggedTypes:[NSArray arrayWithObjects:
NSFilenamesPboardType, nil]];
[self dropAreaFadeOut];
}
return self;
}
- (void)dropAreaFadeIn {
[self setAlphaValue:1.0];
}
- (void)dropAreaFadeOut {
[self setAlphaValue:0.2];
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
NSLog(@"drag operation entered");
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
[self dropAreaFadeIn];
if (sourceDragMask & NSDragOperationLink) {
return NSDragOperationLink;
} else if (sourceDragMask & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
return NSDragOperationNone;
}
- (void) draggingExited: (id <NSDraggingInfo>) info {
NSLog(@"drag operation finished");
[self dropAreaFadeOut];
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard = [sender draggingPasteboard];
NSLog(@"drop now");
[self dropAreaFadeOut];
if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
NSInteger numberOfFiles = [files count];
if(numberOfFiles>0)
{
NSString *filePath = [files objectAtIndex:0];
if(self.delegate){
[self.delegate didFinishDragWithFile:filePath];
}
return YES;
}
else{
NSLog(@"drag file num =0 return!");
}
}
else{
NSLog(@"pboard types(%@) not register!",[pboard types]);
}
return YES;
}
@end
NSDraggingDestination协议中实现3个的方法说明:
(NSDragOperation)draggingEntered:(id
返回支持的拖放操作的类型,对于图片操作这里返回的是NSDragOperationLink
(void) draggingExited: (id
拖放结束的处理
(BOOL)performDragOperation:(id
最关键的方法,返回拖放的文件路径
另外在在类的init方法中,注册了拖放类型为NSFilenamesPboardType。
[self registerForDraggedTypes:[NSArray arrayWithObjects:
NSFilenamesPboardType, nil]];
更多Drag and Drop编程#Drag/Drop操作#章节。
通过plist文件对iPhone、iPad、OSX 定义如下
以每个平台的图片大小为key,value对应为1x,2x,3x的数组。
通过NSImage的Category类扩展来实现指定大小裁剪和保存到指定路径
NSImage+Catgory.h 头文件定义
@interface NSImage (Catgory)
-(NSImage*)reSize:(NSSize)resize;
-(void)saveAtPath:(NSString*)path;
@end
NSImage+Catgory.m 实现
#import "NSImage+Catgory.h"
@implementation NSImage (Catgory)
- (void)saveAtPath:(NSString*)path{
NSData *imageData = [self TIFFRepresentation];
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:1.0] forKey:NSImageCompressionFactor];
imageData = [imageRep representationUsingType:NSPNGFileType properties:imageProps];
[imageData writeToFile:path atomically:NO];
}
- (NSImage*)reSize:(NSSize)resize{
float resizeWidth = resize.width/2;
float resizeHeight = resize.height/2;
NSImage *resizedImage = [[NSImage alloc] initWithSize: NSMakeSize(resizeWidth, resizeHeight)];
NSSize originalSize = [self size];
[resizedImage lockFocus];
[self drawInRect: NSMakeRect(0, 0, resizeWidth, resizeHeight) fromRect: NSMakeRect(0, 0, originalSize.width, originalSize.height) operation: NSCompositeSourceOver fraction: 1.0];
[resizedImage unlockFocus];
return resizedImage;
}
@end
根据用户选择的平台是iPhone、iPad、iPhone+iPad、OSX,从Plist 图片尺寸规格中获取到配置参数,依次生成类似下面的字典对象。最后转换为string写入到文件。
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_mac_512.png",
"scale" : "1x"
}
默认情况下用户点击Export按钮,裁剪图片和json文件会生成到用户当前document目录.
对于Export按钮,我们定义成为DragExportButton类,可以支持导出文件路径的拖放选择。当用户从一个需要增加AppIcon的Xcode工程中直接拖放Images.xcassets目录到Export按钮,可以记录这个Images.xcassets的文件目录路径。点击Export按钮后能把裁剪图片和json文件直接生成到Images.xcassets目录,实现真正的一键导入。
App全球发行推向国际市场一个重要的事情就是语言文字信息国际化。
我们来看看在Xcode中App国际化的处理过程。
先需要创建Localizable.strings文本文件。File菜单创建文件,选择Strings File模版创建,输入Localizable.strings名字完成字符国际化文件创建。
查看Localizable.strings文件内容,里面是空的。后边面板的Localization部分包括Base和English 2项。
Base表示默认的国际化定义。English表示当前项目支持的语言。每个语言名字前面的勾选框表示是否已经生成了国际化语言文件。
我们对english勾选一下,让系统生成english的语言包。这时候会发现工程目录数中Localizable.strings节点多了2个子节点Base和English,表示生成了语言包。
接下来我们增加其他的国际化语言。点击Project中info面板区,看到Localizations部分,再点击
下面的+按钮,出现多语言选择列表,从这里可以选择几十种国际化语言。
我们点击选择增加一个French(fr)法语国际化。我们只对Localizable.strings文件做国际化,因此取消掉列表中的MainMenu.xib的选择。
这时候回到工程目录数中点击Localizable.strings,再看右边的信息面板的localization中多了French项。再勾选这个French项,会在工程中生成French的国际化语言文件。
如果想增加其他语言,继续重复之前的过程。如果要增加10多种国家主流市场的话,还是非常繁琐的过程。
我们希望能自动化语言增加的过程,一次性就能增加多种语言的支持。这就是我们对国际化自动化的目标要求。
一次性选择多个语言界面的原型
最终项目中的生成好的多语言。
另外更高一层,还可以对语言包的翻译,借助于google翻译实现从标准的base语言包,自动完成多语言的翻译。
我们打开Xcode国际化完成的项目文件的工程目录
可以看到每个国际化语言都单独有一个目录,名称为语言的简写加上lproj的后缀名,目录里面都是Localizable.strings 文件。
灵光一现我们知道怎么做了嘛?
好了我们就照猫画虎吧,让用户一次性选择多个语言,按Xcode希望的命名规则生成每个语言的文件夹,文件夹里面在放Localizable.strings文件。
我们可以新建一个项目,增加Localizable.strings文件文件,做好Base和English 2个基本的国际化。然后打开这个工程文件目录,找到国际化Base.lproj, copy一份重命名为fr.lproj,然后在到Xcode中看这个新项目工程目录数的Localizable.strings里面是不是增加了French 法语呢?
当然事情远远不是这么简单,令我们失望的是,工程里面根本没有新加的这个French语言!
继续分析,找到工程文件右键菜单点击显示包内容,里面有个project.pbxproj文件.
使用TextEditor打开后搜索.strings字符串,看到Localizable.strings节点,里面children部分包括了全部的国际化语言。
42D7AC661BA53A1900A06108 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
42D7AC651BA53A1900A06108 /* Base */,
42D7AC671BA53A1B00A06108 /* en */,
C7B3F6368313A4CF1D681C97 /* ca */,
06C6FD41AEC6E183E80A430A /* cs */,
ECF571A5B5FF57D1F05AD025 /* da */,
19DBC8F1B74EDC8940D57812 /* de */,
A67D1AD135AE6795C6E42B69 /* el */,
4C5084CA63F410186567BF80 /* es */,
8EDD34B539B91FBED8B33535 /* fi */,
5EC046661FCF814B3B75652E /* fr */,
3D157C274FEAD6F70AC57E00 /* he */,
46C8CC8A951CFE2BA14B102B /* hr */,
DFAEF7A8E90402F5136B0F9D /* hu */,
6A1760F945632553A133B15B /* it */,
63E8C42A03774C21F4F87CAA /* ja */,
B31ECD799EADEA078C6D3087 /* ko */,
11F63A44627932DAFEFAF3C8 /* ms */,
589D3278FCAFD388CE4E0ABF /* nb */,
EC3D7200D0AF7022196B8B75 /* nl */,
7503FE3C3B4B40D91A1674A4 /* pl */,
859A1CC5010CB89EB2445A8C /* pt */,
77B19E78D9BCD3C92B7130B5 /* pt-PT */,
E4AAC19B903035BC6CCC1EAA /* ro */,
78A311EBF5E2CD5502DFD655 /* ru */,
B78E65D1CCD064C9066C502F /* sk */,
EC3C11AF4CDA79457668676A /* sv */,
FA3BEFDD82AEEDC6BAE989D5 /* th */,
DA891D4E9EC4C322DA0A9171 /* tr */,
5C72DBFF453FA88CAC965C9D /* uk */,
CAFB93E4D6E60490C7D93AD0 /* vi */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
这样看来我们需要将国际化多语言信息添加到工程文件信息里面。
这样我们的思路明确了,需要做2件事情 1)创建多语言文件夹和string文件;2)将多语言信息增加到工程文件信息里面。
第一步已经很容易实现了,下面看看第2步怎么做吧?
对一个工程文件iOSAppIcon.xcodeproj其实是一个文件夹,里面project.pbxproj是工程核心文件,可以转换为NSMutableDictionary进行操作。
对于工程文件的操作,开源的第三方库xcodeEditor提供了非常方便的API,我们直接使用就能对工程文件进行各种操作。
XCProject表示工程类对象,XCSourceFile代表文件类对象,XCGroup代表工程组类对象
1.初始化工程类操作类XCProject,参数为工程路径
XCProject *project = [XCProject projectWithFilePath:filePath];
2.获取工程中Localizable.strings文件所在的group和已经添加的国际化语言
获取到工程中所有的Localizable.strings文件,为XCSourceFile对象;在根据XCSourceFile的key获取到文件归属的XCGroup。
NSString *kKeyStringsFile = @"Localizable.strings";
NSArray *files = [self.project.files filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(XCSourceFile *evaluatedObject, NSDictionary *bindings) {
return [[NSSet setWithObjects:kKeyStringsFile, nil] containsObject:[evaluatedObject.name lastPathComponent] ];
}]];
for(XCSourceFile *file in files)
{
NSString *fullpath =[file pathRelativeToProjectRoot];
//组信息
XCGroup *group = [self.project groupForGroupMemberWithKey:file.key];
XCGroup *parentGroup = [[self.project groupForGroupMemberWithKey:file.key] parentGroup];
//语言名称信息
NSString *language = [[[file.name stringByDeletingLastPathComponent] lastPathComponent] stringByDeletingPathExtension];
}
3.增加新语言到group
只要增加一个新语言的引用给语言所在的XCGroup,保存project就完成了语言的增加。
NSString *relretivePath = [NSString stringWithFormat:@"%@.lproj/%@",lang.name,fileName];
[self.group addPlistFileReferenceWithPath:relretivePath name:lang.name ];
[self.project save];