Xcode7 创建自定义模板

在一个多人开发的项目中,相同的代码风格,不仅增强了易读性,而且调理清晰,容易理解,在后期维护过程中,也容易修改。

因此就想写一个模版。

当然,并不是说用了自定义的模版就一定是好的,有些方法可能是我们不需要的,不想暴漏出来,那么我们可以用代码块的形式去处理。

具体使用,可查看如下连接:
http://www.jianshu.com/p/93527682d8d3

虽然有些许的错误,但是整体流程是OK的。

处理过程

接下来,看处理过程:

  1. 获取模版路径

    系统模版的路径为~/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/File Templates,File Templates下包含Apple Watch,Resource,Source,User Interface,四个目录,对应了新增文件时,iOS下的几个分级目录。比如,我们想查看CocoaTouch下对应的内容,我们可以看Cocoa Touch Class.xctemplate这个模版。

    通常情况下,我们会在Source下新增新的模版。

    将 MSTemplate.xctemplate文件夹放到Source系统模板文件夹中,就会在New File时出现在对应的选项中。 在往里面copy文件时,系统会提示你输入管理员密码,输入密码进行处理。

  2. template内容介绍

    MSTemplate.xctemplate 处理的是对UIViewController的模块修改。MSTemplate.xctemplate文件夹包含了

    假设已经将MSTemplate.xctemplate放入了Source文件夹中,查看MSTemplate.xctemplate中有TemplateIcon.png,TemplateIcon@2x.png,TemplateInfo.plis以及UIViewControllerXIBObjective-C文件夹,UIViewControllerXIBObjective-C文件夹内包含三个文件

FILEBASENAME.h,FILEBASENAME.m, FILEBASENAME.xib

(1)UIViewControllerObjective-C 文件夹
用来创建.h 和 .m文件。 其中文件夹的命名规范是[name]+Objective-C. 如果是创建swift修改为swift。

(2)UIViewControllerXibObjective-C文件夹
用来创建.h,.m和.xib文件。其中文件夹的命名规范是[name]+XibObjective-C. swift类似。

(3)TemplateIcon图片
图片是用来显示在New File的菜单上的。任意放一个自己喜欢的图片,像素138*138即可。

(4)TemplateInfo.plist配置文件。下面单独讲讲。

  1. 头文件和实现的说明。

    ___FILEBASENAME___.h代码如下
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
              //
    // ___FILENAME___
    // ___PROJECTNAME___
    //
    // Created by ___FULLUSERNAME___ on ___DATE___.
    //___COPYRIGHT___
    //

    ___IMPORTHEADER_cocoaTouchSubclass___

    @interface ___FILEBASENAMEASIDENTIFIER___ : ___VARIABLE_cocoaTouchSubclass___

    @end
 ___FILEBASENAME___.m 代码如下


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
34
35
36
37
38
39
40
41
42
43
        //
// ___FILENAME___
// ___PROJECTNAME___
//
// Created by ___FULLUSERNAME___ on ___DATE___.
//___COPYRIGHT___
//

#import "___FILEBASENAME___.h"

@interface ___FILEBASENAMEASIDENTIFIER___ ()

@end

@implementation ___FILEBASENAMEASIDENTIFIER___

#pragma mark lifecycle

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}

#pragma mark btn event

//back button clicked
- (void)doBack:(UIButton *)sender{

}
@end

xib 文件可参考系统模版去制作一个。

TemplateInfo.plist

查看简书的描述信息。
主要设置三个值,父类,子类的默认名,是否打开xib。

使用。

创建一个子类,使用MSTemplate,可快速创建一个带有自定义注释子类。

新增模版常见问题说明
  1. 目录查找错误。

    ~/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/File Templates,注意Platforms而不是Developer下的Libraty。

  2. 新建模版是显示错误位置。

    可能加入到MAC OS上去了,注意看自己在哪个部分新增模版。

  3. 模板新增成功,但是新增子类时新增不上去。

    新增成功,但是子类加不到prj上,可能是对应的xib文件没有,或者文件没有书写完成,可从系统的template复制一份,再修改代码来实现。

  4. 系统模版的修改。

    这个须特别注意,系统提供的模版不能编辑,没有写的权限,不能在原来的基础上修改。

知识点汇总(五)

基于CALayer 的动画实现

需求:

想要动态的画出一个水波纹。

思路:

基于CALayer画出基本的样式,结合动画实现动态水波纹的效果。

问题:

CABasicAnimation 的基本动画只有以下几组:

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
34
35
36
37
38
39
animationWithKeyPath的值:

transform.scale = 比例轉換
transform.scale.x = 闊的比例轉換
transform.scale.y = 高的比例轉換
transform.rotation.z = 平面圖的旋轉
opacity = 透明度

margin
zPosition

backgroundColor

cornerRadius
borderWidth


bounds
contents

contentsRect
cornerRadius
frame

hidden

mask

masksToBounds
opacity

position

shadowColor

shadowOffset

shadowOpacity
shadowRadius

特别注意,frame的设置动画是无效的,它的变动影响了bounds以及center的变化。

那么,我想要给一个自定义的属性添加动画怎么处理?

查看文档,发现一个方法:

1
2
3
4
5
6
7
8
9
 /* Method for subclasses to override. Returning true for a given
* property causes the layer's contents to be redrawn when the property
* is changed (including when changed by an animation attached to the
* layer). The default implementation returns NO. Subclasses should
* call super for properties defined by the superclass. (For example,
* do not try to return YES for properties implemented by CALayer,
* doing will have undefined results.) */

+ (BOOL)needsDisplayForKey:(NSString *)key;

这个方法可以预处理CALayer的属性,若不想识别这个属性,可返回NO,否则返回YES,其余默认的属性,可按照父类的返回返回。

具体实现

.h 的实现

1
2
3
4
5
6
7
8
 
#import <QuartzCore/QuartzCore.h>

@interface Coslayer : CALayer

@property (nonatomic) CGFloat offsetAngle;

@end

.m内的具体实现

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#import "Coslayer.h"

#import <UIKit/UIKit.h>
@implementation Coslayer

+ (BOOL)needsDisplayForKey:(NSString *)key{
if ([key isEqualToString:@"offsetAngle"]) {
return YES;
}
return [super needsDisplayForKey:key];
}

- (instancetype)init{
self = [super init];
self.backgroundColor = [UIColor yellowColor].CGColor;
self.offsetAngle = 0;
return self;
}

- (void)setOffsetAngle:(CGFloat)offsetAngle{
_offsetAngle = offsetAngle;
[self setNeedsDisplay];
}

- (void)drawInContext:(CGContextRef)ctx{
int widthPixel = (int)CGRectGetWidth(self.bounds);
if (widthPixel <1) {
return;
}
//180 作为一个波纹
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 0, 0);
CGPathMoveToPoint(path, NULL, 0, CGRectGetMidY(self.bounds));
for (int i = 0; i < widthPixel; i++) {
CGFloat y = 50 + 8*cos(2*(i*M_PI/360.0+self.offsetAngle));
if (i == 0) {
CGPathMoveToPoint(path, NULL, i, y);
}else{
CGPathAddLineToPoint(path, NULL, i, y);
}
}
CGPathAddLineToPoint(path, NULL, CGRectGetWidth(self.bounds), 0);
CGPathAddLineToPoint(path, NULL, 0, 0);
CGContextAddPath(ctx, path);
CGContextSetRGBFillColor(ctx, 0.8, 0.5, 0.7, 1);
CGContextDrawPath(ctx, kCGPathFill);
CGPathRelease(path);
}

@end

动画调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  
Coslayer *layer = [Coslayer layer];
layer.frame = CGRectMake(100, 100, 400, 400);
[self.view.layer addSublayer:layer];

CABasicAnimation *basic = [CABasicAnimation animationWithKeyPath:@"offsetAngle"];
basic.fromValue = 0;
basic.toValue = [NSNumber numberWithFloat:M_PI*2];
basic.byValue = [NSNumber numberWithFloat:M_PI];
basic.duration = 0.6;
basic.autoreverses = NO;
basic.speed = 0.4;
basic.repeatCount = HUGE_VALF;
[layer addAnimation:basic forKey:@"offsetAngle"];

CABasicAnimation 初始化实例对象时要求传入keyPath,这个就是对应的路径。

上述例子就是指offsetAngle从0到2π的过渡,过渡值为π,过渡时间为0.6,不自动转回。

repeatCount在api中已经说明,是动画的次数,若想设置为无限次的重复动画值为HUGE_VALF。

知识点汇总(四)

runtime 为系统类新增property

动态为系统类新增属性,以普通属性的方式进行访问。

事例如下,为UIView新增viewHegiht的方法。

1. 新增一个Category,文件名为UIView+Frame.h,UIView+Frame.m.

2. 在.h文件添加属性

1
@property (nonatomic) CGFloat viewHeight;

3. 在.m文件添加get,set方法以及实现

导入库:

1
#import <objc/runtime.h>

声明静态常量:

1
static NSString const *playloadObject = @"playloadObject";

添加IMP:

1
2
3
4
5
6
7
- (CGFloat)viewHeight{
return [objc_getAssociatedObject(self, &playloadObject) floatValue];
}

- (void)setViewHeight:(CGFloat)viewHeight{
objc_setAssociatedObject(self, &playloadObject, @(viewHeight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

set方法内调用runtime的方法objc_setAssociatedObject实现值与地址的绑定,注意四个参数,第一个为obj对象,第二个为参数的关联地址,第三个是id类型的对象,最后一个为objc_AssociationPolicy 。

get方法内调用runtime的方法objc_getAssociatedObject获取数值,注意返回值的内容是id类型,可自行转化为自己想要的类型。

4. 结束

以上过程完成了对UIView 的扩展,实现返回view的高度。可参照实现方法实现对类的属性的封装。

知识点汇总(二)

runtime消息转发的处理

代码在执行某一个方法时,代码执行performSelector的动作,在runtime上调用方法objc_msgSend方法(注意类方法的调用是objc_msgSendClass),执行过程如下:

1.objc_msgSend(target, selector)

target为receiver,selector为执行的方法。

Attention!!!

继承自NSObject的类都集成了一个类方法。

+(Bool)resolveInstanceMethod:(SEL)selector的方法,内部实现机制为获取实例对象的方法列表,若有,返回true,若无,返回false。

  • 当为true时,进入Event Handle处理事件;
  • 当返回为false时,说明没有实现这个方法或者返回为nil,消息进入转发Method Forwarding (消息转发);

2. - (id)forwardingTargetForSelector:(SEL)aSelector

aSelector 待处理的消息,返回一个对象,用于处理消息。

继承自NSObject的类都集成了一个实例方法。

-(id)forwardingTargetForSelector:(SEL)aSelector的方法,当类不识别消息时,执行该方法, 返回一个对象,用于处理aSelector;

  • 当为true时,将由返回的object处理事件进入objc_msgSend,继续执行,进入Event Handle处理事件;
  • 当返回为false时,说明没有实现这个方法或者返回为nil,使用最后一个挽救的方法,Normal Forwarding(正常转发);

3. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息信号,aSelector 为消息内容。

继承自NSObject的类都集成了一个实例方法。

若是类对于方法有IMP,那么调用父类的该方法就有非nil的返回,若是无IMP,则获取为nil,此时可以把把别的已经实现该方法的类的对象的信号返回,即可实现消息转发;

  • 当为true时,说明该类可以处理该方法;
  • 当为false时,说明无实现,可自行在子类实现该方法,转发消息;
  • 当为false时,且并不转发消息,那么Runtime会抛出donotRecogniseSelector的异常,程序终止运行。

4. - (void)forwardInvocation:(NSInvocation *)anInvocation

invoke NSInvocation,让target执行对应的selector.

在子类中可实现该方法,修改anInvocation的target将消息转发出去。

代码示例

为了学习并补充说明该功能,做了一个demo,展示,代码如下:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#import "ViewController.h"
#import <objc/runtime.h>

#import "UIViewController+Logger.h"
#import "UIView+Logger.h"

@interface MViewStage : UIViewController

@end

@implementation MViewStage

- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:@selector(doSomething)];
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualToString:@"doSomething"]) {
return self.parentViewController;
}
return nil;
}

//- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
// NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
// if (!sig) {
// if ([NSStringFromSelector(aSelector) isEqualToString:@"doSomething"]) {
// sig = [self.parentViewController methodSignatureForSelector:aSelector];
// }
// }
// return sig;
//
//}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
if ([NSStringFromSelector(anInvocation.selector) isEqualToString:@"doSomething"]) {
[anInvocation invokeWithTarget:self.parentViewController];
}
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

MViewStage *stage = [[MViewStage alloc] init];
[self addChildViewController:stage];
[stage loadViewIfNeeded];

}

- (void)doSomething{
NSLog(@"ViewController : doSomething ");
}

@end

我在MViewStage 执行[self performSelector:@selector(doSomething)],但是并没有定义或者实现这个方法,因此我是用如下两种解决方法:

第一种:forwardingTargetForSelector 返回了self.parentViewController;

第二种: 注销掉forwardingTargetForSelector的实现,或者返回为nil,实现了methodSignatureForSelector和forwardInvocation方法,分别返回了self.parentViewController的methodSignatureForSelector,并在forwardInvocation执行了切换target.

以上两种均OK,都可以实现消息的转发。

知识点汇总(一)

NSObject的class方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

@interface MViewStage : UIViewController

@end

@implementation MViewStage

- (void)viewDidLoad{
[super viewDidLoad];
NSLog(@"self.class = %@",[self class]);
NSLog(@"super.class = %@",[self class]);
NSLog(@"self.superClass = %@",[self superclass]);
}

@end

如图所示,我想打印出继承自UIViewController的一个子类MViewStage,打印出self.class,super.class,self.superClass,这三个的值,按照之前的认知,第一个和最后一个毋庸置疑,分别是,MViewStage和UIViewController,只有第二个,super,我认为要打印的是super的class,应该是UIViewController。但是打印结果如下:

1
2
3
2016-07-18 13:35:36.139 RunTimeLearning[6892:79426] self.class = MViewStage
2016-07-18 13:35:36.139 RunTimeLearning[6892:79426] super.class = MViewStage
2016-07-18 13:35:36.139 RunTimeLearning[6892:79426] self.superClass = UIViewController

很明显super.class打印出的内容是当前对象的类MViewStage。

!!!为什么呢?

API中查看可以看到如下描述信息:

1
2
3
4
5

- (Class)class
Description
Returns the class object for the receiver’s class.
The class object for the receiver’s class.

可以看出,class返回的类是当前selector的接受者,上例所述的MViewStage的对象,因此返回类型为MViewStage。

bug纪录(一)

问题信息描述

发现时间:2016-07-06

现象:

1
2
2016-07-06 18:18:42.237 Pudding[5035:1874119] _BSMachError: (os/kern) invalid capability (20)
2016-07-06 18:18:42.238 Pudding[5035:1874119] _BSMachError: (os/kern) invalid name (15)

描述:

在UIViewController的viewWillAppear 和 viewDidAppear 之间 在 console 段抛出如下错误,底层信息,造成耗时,耗时时间约为1秒半。

关于解决

出现原因:

可能由于viewWillAppear、viewDidLayoutSubviews、viewDidAppear方法内代码书写不规范早层。所以我采用逐段log、逐段注销的方法,发现viewWillAppear内的一段代码造成此错误的抛出。通常还是在数据量大,subview过多时抛出错误信息。

处理方法:
查看stack overflow 上弄的帖子,看到的解决方案。

采用如下两种方法均可:

(1)async一个main,代码

1
2
3
4
dispatch_async(dispatch_get_main_queue(), ^{
//代码

});

(2) dispatch_after 0.2秒执行代码

1
2
3
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//代码
});

UIContol的高亮状态

1
2
@property(nonatomic,getter=isHighlighted) BOOL highlighted;                         
// default is NO. this gets set/cleared automatically when touch enters/exits during tracking and cleared on up

高亮状态,highlighted,比如UIButton的高亮,就是当你手按下button按钮时的状态,可以放置两张不同的照片来看一下,很明显就能看到。

通常在对视觉问题的处理上,用到这个属性,但是我们H领导会有这样的习惯,尽可能的少自己创建属性,使用系统本身提供的属性,这次,在做一个UIcollectionViewCell的选中状态显示事件时,他用到了这个属性,他是在cell最顶部的一个小view上使用了这个属性,但是当我们手指按在cell上时,cell的点击事件就被激活,并将整个高亮显示的状态显示在子view上,导致出现,不该显示的时间显示状态的情况。

最初,我在看到这种使用方法的时候,我还是蛮赞叹的,节省了开发的成本,便捷偷巧的使用系统属性,然而后期还是觉得这种讨巧的处理方法,在界面的显示上并不讨巧,或者会造成视觉上的bug,从这个角度上来说,反而加大了开发力度。

故而,写出这篇文章作为备注。警醒一下那些有这个习惯的码工们,也警示自己。