使用UIBezierPath和CAShapeLayer画各种图形

本文系转载

原作者: J0hnnny

原文地址: http://www.jianshu.com/p/c5cbb5e05075

CAShapeLayer是CALayer的子类,但是比CALayer更灵活,可以画出各种图形,当然,你也可以使用其他方式来画,随你。

杂谈

CAShapeLayer 中,也可以像 CALayer 一样指定它的 frame 来画,就像这样:

1
2
3
4
let layer = CAShapeLayer()
layer.frame = CGRectMake(110, 100, 150, 100)
layer.backgroundColor = UIColor.blackColor().CGColor
view.layer.addSublayer(layer)

然后你就会得到如图这样的黑色矩形

但是,CAShapeLayer 有一个神奇的属性 path 用这个属性配合上 UIBezierPath 这个类就可以达到超神的效果。

UIBezierPath 顾名思义,这是用贝塞尔曲线的方式来构建一段弧线,你可以用任意条弧线来组成你想要的形状,比如,你想用它来和上面一样画一个矩形,那就可以这样子来做:

1
2
3
4
5
let path = UIBezierPath(rect: CGRectMake(110, 100, 150, 100))
let layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor.blackColor().CGColor
view.layer.addSublayer(layer)

要注意的是,这里就不要用backgroundColor 这个属性了,而要使用 fillColorstrokeColor ,前者代表设置这个 Layer 的填充色,后者代表设置它的边框色

1
2
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor

可以试一下把上面代码设置颜色的部分改成这个样子,那么运行程序的时候就会是这种样子

###玩一下UIBezierPath

在说回 UIBezierPath ,在 UIBezierPath 的 init 方法中,就有很多方便你画各种图形的方法,比如你可以画一个带圆角的图形

1
2
3
4
5
let path = UIBezierPath(roundedRect: CGRectMake(110, 100, 150, 100), cornerRadius: 50)
let layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor

还可以指定起始角和半径画圆

1
2
3
4
5
6
7
8
let radius: CGFloat = 60.0
let startAngle: CGFloat = 0.0
let endAngle: CGFloat = CGFloat(M_PI * 2)
let path = UIBezierPath(arcCenter: view.center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
let layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor

在这里涉及到角度的问题,起始角和结束角,这里的角度使使用弧度制来表示,这里我收藏了一张图片,以方便参考

怎么画曲线

贝塞尔曲线的画法是由起点、终点、控制点三个参数来画的,为了解释清楚这个点,我写了几行代码来解释它

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
let startPoint = CGPointMake(50, 300)
let endPoint = CGPointMake(300, 300)
let controlPoint = CGPointMake(170, 200)

let layer1 = CALayer()
layer1.frame = CGRectMake(startPoint.x, startPoint.y, 5, 5)
layer1.backgroundColor = UIColor.redColor().CGColor

let layer2 = CALayer()
layer2.frame = CGRectMake(endPoint.x, endPoint.y, 5, 5)
layer2.backgroundColor = UIColor.redColor().CGColor

let layer3 = CALayer()
layer3.frame = CGRectMake(controlPoint.x, controlPoint.y, 5, 5)
layer3.backgroundColor = UIColor.redColor().CGColor

let path = UIBezierPath()
let layer = CAShapeLayer()

path.moveToPoint(startPoint)
path.addQuadCurveToPoint(endPoint, controlPoint: controlPoint)

layer.path = path.CGPath
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor

view.layer.addSublayer(layer)
view.layer.addSublayer(layer1)
view.layer.addSublayer(layer2)
view.layer.addSublayer(layer3)

我很随意的定义了三个点,为了清楚显示它们的位置,我放了三个矩形在上面以便观察,然后调用 path.moveToPoint(startPoint) 让它移动到起始点,然后调用path.addQuadCurveToPoint(endPoint, controlPoint: controlPoint) 这个方法告诉它结束点和控制点,这样它就能画出一条有弧度的线条了,如果把fillColor设置一个颜色,那么它就会变成一个很丑的形状了,示例图如下

控制点决定了它的曲率,曲线的顶点不等于控制点的位置,具体可以看一下贝塞尔曲线的定义,你还可以使用两个控制点来画,两个控制点可以使用方法 path.addCurveToPoint(endPoint, controlPoint1: controlPoint, controlPoint2: controlPoint2)来搞定

这样它会是这个样子

再来说说 CAShapeLayer

CAShapeLayer 是个神奇的东西,给它一个path它就能变成你想要的形状,它还有很多可以玩的地方。综合使用可以组合成不同的动画,比如下面这样

这三个动画就是使用了 strokeEnd strokeStart lineWidth 三个属性,第一个动画用了strokeEnd这个属性的值范围是0-1,动画显示了从0到1之间每一个值对这条曲线的影响,strokeStart的方法则是相反的,如果把这两个值首先都设置成0.5然后慢慢改变成0和1的时候就会变成第二个动画,配合lineWidth则曲线会慢慢变粗,这里的很多属性都是支持动画的。

示例代码

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
private func animation1() {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 2
layer.addAnimation(animation, forKey: "")
}

private func animation2() {
layer.strokeStart = 0.5
layer.strokeEnd = 0.5

let animation = CABasicAnimation(keyPath: "strokeStart")
animation.fromValue = 0.5
animation.toValue = 0
animation.duration = 2

let animation2 = CABasicAnimation(keyPath: "strokeEnd")
animation2.fromValue = 0.5
animation2.toValue = 1
animation2.duration = 2

layer.addAnimation(animation, forKey: "")
layer.addAnimation(animation2, forKey: "")
}

private func animation3() {
let animation = CABasicAnimation(keyPath: "lineWidth")
animation.fromValue = 1
animation.toValue = 10
animation.duration = 2
layer.addAnimation(animation, forKey: "")
}

应用一下

前一阵子在仿时光网这个APP,其中有一个Layer的形状很怪异,是这样的

很明显它可以用 CAShapeLayer + UIBezierPath 来做,思路大概是这样,先移动到左上方的位置,然后向下划线,然后往右划线,然后往上划线,还剩一个盖子,这个盖子就用一个控制点控制曲率,非常简单,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
let finalSize = CGSizeMake(CGRectGetWidth(view.frame), 400)
let layerHeight = finalSize.height * 0.2
let layer = CAShapeLayer()
let bezier = UIBezierPath()
bezier.moveToPoint(CGPointMake(0, finalSize.height - layerHeight))
bezier.addLineToPoint(CGPointMake(0, finalSize.height - 1))
bezier.addLineToPoint(CGPointMake(finalSize.width, finalSize.height - 1))
bezier.addLineToPoint(CGPointMake(finalSize.width, finalSize.height - layerHeight))
bezier.addQuadCurveToPoint(CGPointMake(0,finalSize.height - layerHeight),
controlPoint: CGPointMake(finalSize.width / 2, (finalSize.height - layerHeight) - 40))
layer.path = bezier.CGPath
layer.fillColor = UIColor.blackColor().CGColor
view.layer.addSublayer(layer)

就能画出这样的形状来

再来一个复杂点的,微信下拉拍小视频的那只眼睛很有趣,来模仿一下那个效果吧,它是这样的

首先你得画出这只眼睛,这是眼睛包括5个部分组成(这个是用OC写的)

1
2
3
4
5
@property (strong, nonatomic) CAShapeLayer *eyeFirstLightLayer;
@property (strong, nonatomic) CAShapeLayer *eyeSecondLightLayer;
@property (strong, nonatomic) CAShapeLayer *eyeballLayer;
@property (strong, nonatomic) CAShapeLayer *topEyesocketLayer;
@property (strong, nonatomic) CAShapeLayer *bottomEyesocketLayer;

然后,还是通过 UIBezierPathCAShapeLayer 这样的老套路来画,代码较多

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
- (CAShapeLayer *)eyeFirstLightLayer {
if (!_eyeFirstLightLayer) {
_eyeFirstLightLayer = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:CGRectGetWidth(self.frame) * 0.2
startAngle:(230.f / 180.f) * M_PI
endAngle:(265.f / 180.f) * M_PI
clockwise:YES];
_eyeFirstLightLayer.borderColor = [UIColor blackColor].CGColor;
_eyeFirstLightLayer.lineWidth = 5.f;
_eyeFirstLightLayer.path = path.CGPath;
_eyeFirstLightLayer.fillColor = [UIColor clearColor].CGColor;
_eyeFirstLightLayer.strokeColor = [UIColor whiteColor].CGColor;
}
return _eyeFirstLightLayer;
}

- (CAShapeLayer *)eyeSecondLightLayer {
if (!_eyeSecondLightLayer) {
_eyeSecondLightLayer = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:CGRectGetWidth(self.frame) * 0.2
startAngle:(211.f / 180.f) * M_PI
endAngle:(220.f / 180.f) * M_PI
clockwise:YES];
_eyeSecondLightLayer.borderColor = [UIColor blackColor].CGColor;
_eyeSecondLightLayer.lineWidth = 5.f;
_eyeSecondLightLayer.path = path.CGPath;
_eyeSecondLightLayer.fillColor = [UIColor clearColor].CGColor;
_eyeSecondLightLayer.strokeColor = [UIColor whiteColor].CGColor;

}
return _eyeSecondLightLayer;
}

- (CAShapeLayer *)eyeballLayer {
if (!_eyeballLayer) {
_eyeballLayer = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:CGRectGetWidth(self.frame) * 0.3
startAngle:(0.f / 180.f) * M_PI
endAngle:(360.f / 180.f) * M_PI
clockwise:YES];
_eyeballLayer.borderColor = [UIColor blackColor].CGColor;
_eyeballLayer.lineWidth = 1.f;
_eyeballLayer.path = path.CGPath;
_eyeballLayer.fillColor = [UIColor clearColor].CGColor;
_eyeballLayer.strokeColor = [UIColor whiteColor].CGColor;
_eyeballLayer.anchorPoint = CGPointMake(0.5, 0.5);

}
return _eyeballLayer;
}

- (CAShapeLayer *)topEyesocketLayer {
if (!_topEyesocketLayer) {
_topEyesocketLayer = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, CGRectGetHeight(self.frame) / 2)];
[path addQuadCurveToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame) / 2)
controlPoint:CGPointMake(CGRectGetWidth(self.frame) / 2, center.y - center.y - 20)];
_topEyesocketLayer.borderColor = [UIColor blackColor].CGColor;
_topEyesocketLayer.lineWidth = 1.f;
_topEyesocketLayer.path = path.CGPath;
_topEyesocketLayer.fillColor = [UIColor clearColor].CGColor;
_topEyesocketLayer.strokeColor = [UIColor whiteColor].CGColor;
}
return _topEyesocketLayer;
}

- (CAShapeLayer *)bottomEyesocketLayer {
if (!_bottomEyesocketLayer) {
_bottomEyesocketLayer = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, CGRectGetHeight(self.frame) / 2)];
[path addQuadCurveToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame) / 2)
controlPoint:CGPointMake(CGRectGetWidth(self.frame) / 2, center.y + center.y + 20)];
_bottomEyesocketLayer.borderColor = [UIColor blackColor].CGColor;
_bottomEyesocketLayer.lineWidth = 1.f;
_bottomEyesocketLayer.path = path.CGPath;
_bottomEyesocketLayer.fillColor = [UIColor clearColor].CGColor;
_bottomEyesocketLayer.strokeColor = [UIColor whiteColor].CGColor;

}
return _bottomEyesocketLayer;
}

然后更改一下某些属性的值,方便稍后的动画

1
2
3
4
5
6
7
8
9
- (void)setupAnimation {
self.eyeFirstLightLayer.lineWidth = 0.f;
self.eyeSecondLightLayer.lineWidth = 0.f;
self.eyeballLayer.opacity = 0.f;
_bottomEyesocketLayer.strokeStart = 0.5f;
_bottomEyesocketLayer.strokeEnd = 0.5f;
_topEyesocketLayer.strokeStart = 0.5f;
_topEyesocketLayer.strokeEnd = 0.5f;
}

最后根据 UIScrollViewcontentOffset 来控制各种属性,办法较笨,但管用。

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
- (void)animationWith:(CGFloat)y {
CGFloat flag = self.frame.origin.y * 2.f - 20.f;
if (y < flag) {
if (self.eyeFirstLightLayer.lineWidth < 5.f) {
self.eyeFirstLightLayer.lineWidth += 1.f;
self.eyeSecondLightLayer.lineWidth += 1.f;
}
}

if(y < flag - 20) {
if (self.eyeballLayer.opacity <= 1.0f) {
self.eyeballLayer.opacity += 0.1f;
}

}

if (y < flag - 40) {
if (self.topEyesocketLayer.strokeEnd < 1.f && self.topEyesocketLayer.strokeStart > 0.f) {
self.topEyesocketLayer.strokeEnd += 0.1f;
self.topEyesocketLayer.strokeStart -= 0.1f;
self.bottomEyesocketLayer.strokeEnd += 0.1f;
self.bottomEyesocketLayer.strokeStart -= 0.1f;
}
}

if (y > flag - 40) {
if (self.topEyesocketLayer.strokeEnd > 0.5f && self.topEyesocketLayer.strokeStart < 0.5f) {
self.topEyesocketLayer.strokeEnd -= 0.1f;
self.topEyesocketLayer.strokeStart += 0.1f;
self.bottomEyesocketLayer.strokeEnd -= 0.1f;
self.bottomEyesocketLayer.strokeStart += 0.1f;
}
}

if (y > flag - 20) {
if (self.eyeballLayer.opacity >= 0.0f) {
self.eyeballLayer.opacity -= 0.1f;
}
}

if (y > flag) {
if (self.eyeFirstLightLayer.lineWidth > 0.f) {
self.eyeFirstLightLayer.lineWidth -= 1.f;
self.eyeSecondLightLayer.lineWidth -= 1.f;
}
}
}

最后

总之使用 UIbezierPathCAShapeLayer 可以画出你想要的任何形状,没有它做不到,只有你想不到,搞定了它们你就可以轻松定制你想要的任何控件了。

Crash捕捉工具-Fabric

上篇文章提到了多种调试记录错误日志的方法。我现在在用的是Fabric,是Twitter的一个工具,目前来看效果不错。下面来看下如何集成并使用。

前往官网,,先注册账号。

Fabric安装包下载链接:http://pan.baidu.com/s/1qX0Ab96 密码:b8sq

下载安装


打开Fabric并选中项目,然后在Crashlytics选项点击Installed



复制白色部分到xcode中


继续点击Next

可拖拽按钮至Xcode中,拖拽成功之后可发现项目中多了Fabric.framework和Crashlytics.framwork。这一步也可以使用pod安装

安装完后按如下集成代码

至此集成完毕。

默认只收集release模式下的错误日志,怎么在Debug模式下也收集呢?可以这样设置

这样在调试和发布后如有遇到崩溃,都会在官网显示,并发往注册邮箱。



注意:Fabric的原理还是把发布后的dsym上传后对它进行定位,显示出错误的位置。由于版本和系统可能并不一致,集成过程如果步骤有偏差需按照Fabric上显示的的步骤集成。


如有任何疑问或问题请联系我:fishnewsdream@gmail.com,欢迎交流,共同提高!

Objective-C/Swift技术开发交流群201556264,讨论何种技术并不受限,欢迎各位大牛百家争鸣!

微信公众号OldDriverWeekly,欢迎关注并提出宝贵意见

老司机iOS周报,欢迎关注或订阅

刚刚在线工作室,欢迎关注或提出建设性意见!

刚刚在线论坛, 欢迎踊跃提问或解答!

如有转载,请注明出处,谢谢!

iOS崩溃调试的多种方法

在iOS开发调试及上线后,程序都经常需要调试,简单点来说开发时打断点,复杂的情况下需要分析crash文件,尤其是上线后也需要修复崩溃的bug。

获取崩溃信息有多种方法。一般来说有如下几种

  • 自己实现应用内崩溃收集,也可上传服务器

  • 使用苹果提供的Carsh崩溃收集服务

  • Xcode自带的查看设备崩溃信息

  • 使用第三方统计工具

自己实现应用内崩溃收集

苹果提供了异常处理的类,NSException,这个类可以创建及获取一个异常对象。

使用苹果提供的Carsh崩溃收集服务

苹果在Xcode中为我们集成了崩溃统计功能,在Window->Organizer->Crashes中可以看到

但是,苹果自带的这个崩溃统计工具需要在手机上额外设置。
设置->隐私->诊断与用量->诊断与用户数据(iOS8以下在通用中设置),选择自动发送并与开发者分享即可。

Xcode自带的查看设备崩溃信息

打开Xcode,选择Windows-> Devices -> 选择自己的手机 -> View Device Logs 就可以查看我们的崩溃信息。

如果手机上的应用是这台电脑打包安装的,这样崩溃信息已经为我们符号化好了,点击去之后,如果还是没有符号化完毕 ,我们选择文件,然后右击选择Re-Sysbomlicate就可以。如果是使用其他电脑进行的打包,我们可以在这里面将Crash文件导出,自己通过命令行的方式进行解析。

使用第三方统计工具

之前使用过友盟,还需要使用工具自行解析,感觉并不是很好,还有比如百度统计,BugHD,Fabric等很多。崩溃收集统计函数应该只进行一次调用,如果用第三方的话也最好只用一个第三方,这样我们获取崩溃统计信息的途径也是唯一的。第三方统计工具并不是用的越多越好,使用多个崩溃收集第三方会导致NSSetUncaughtExceptionHandler()函数指针的恶意覆盖,导致有些第三方不能收到崩溃信息。

我现在在用的是Fabric,感觉相当不错,下篇文章再做讲解。另外BugHD也还是不错,就是延迟太久才能收到.使用BugHD,需前往官网注册,创建项目,然后集成代码,整个流程非常简单。

1.下载并导入 SDK 下载 SDK

将下载包中的 BugHD.framework 文件夹拖到 Xcode 项目中,在应用设置中, Build Phases -> Link Binary With Libraries 里添加SystemConfiguration.framework,当然也可以通过Cocoapods安装,官网那个有详细的使用文档。

2 在 AppDelegate.m 中导入头文件:

#import \ //导入头文件
然后在

application:didFinishLaunchingWithOptions: 方法中加入一行:

[BugHD handleCrashWithKey:@”你的GENERAL_KEY”]; 即可。

GENERAL_KEY在创建项目时会自动生成。默认是在release模式下词汇收集错误信息,会将错误信息显示在官网并会发到注册邮箱里。Debug模式下也希望收到错误信息的话,需要设置

捕捉到的错误信息

但是这个错误信息延迟的太久,可能需要半天甚至一天的时间才能收集到。

dSYM

上述中提到的dSYM,是一种文件格式。我们每次Archive之后,都会生成一个dSYM文件,也就是符号集。符号集是我们打包之后,和.app文件同级的后缀名为.dSYM的文件,这个文件必须使用Xcode进行打包才有。每一个.dSYM文件都有一个UUID,和.app文件中的UUID对应,代表着是一个应用。而.dSYM文件中每一条崩溃信息也有一个单独的UUID,用来和程序的UUID进行校对。符号集中存储着文件名、方法名、行号的信息,是和可执行文件的16进制函数地址对应的,通过分析崩溃的.Crash文件可以准确知道具体的崩溃信息。必须使用当前应用打包的电脑所生成的dSYM文件,其他电脑生成的文件可能会导致分析不准确。

崩溃分析

这个崩溃分析我自己并没有实现,觉得比较繁琐,在使用友盟的时候,也需要使用错误分析工具来实现。以下,是网上找的一个方法,可以自行参考。

通过Mac自带的命令行工具解析Crash文件需要具备三个文件

  • symbolicatecrash,Xcode自带的崩溃分析工具,使用这个工具可以更精确的定位崩溃所在的位置,将0x开头的地址替换为响应的代码和具体行数。

  • 我们打包时产生的dSYM文件。

  • 崩溃时产生的Crash文件

在解析崩溃信息的时候,首先在桌面上建立一个Crash文件夹,然后将.Crash、.dSYM、symbolicatecrash放在这个文件夹中,这样进入这个文件夹下,直接一行命令就解决了。

symbolicatecrash我们可以在下面路径下可以找到,我用的是Xcode7,其他版本Xcode路径可能不一样,请自行查找。

1
/Applications/Xcode.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash

然后Window->Organizer->Archives中,选中archive的版本右击,选择Show in Finder就可以获取dSYM文件了。

将.Crash、.dSYM、symbolicatecrash三个文件都放在我们在桌面建立的Crash文件夹中。

开启命令行工具,进入崩溃文件夹中

cd /Users/username/Desktop/崩溃文件夹

使用命令解析Crash文件

./symbolicatecrash ./.crash ./.app.dSYM > symbol.crash

如果上面命令不成功,使用命令检查一下环境变量

xcode-select -print-path

返回结果:

/Applications/Xcode.app/Contents/Developer/

如果不是上面的结果,需要使用下面命令设置一下导出的环境变量,然后重复上面解析的操作。

export DEVELOPER_DIR=/Applications/XCode.app/Contents/Developer

解析完成后会生成一个新的.Crash文件,这个文件中就是崩溃详细信息。图中红色标注的部分就是我们代码崩溃的部分。

注意,以下情况不会有崩溃信息产生:

  • 内存访问错误(不是野指针错误)

  • 低内存,当程序内存使用过多会造成系统低内存的问题,系统会将程序内存回收

  • 因为某种原因触发看门狗机制


如有任何疑问或问题请联系我:fishnewsdream@gmail.com,欢迎交流,共同提高!

Objective-C/Swift技术开发交流群201556264,讨论何种技术并不受限,欢迎各位大牛百家争鸣!

微信公众号OldDriverWeekly,欢迎关注并提出宝贵意见

老司机iOS周报,欢迎关注或订阅

刚刚在线工作室,欢迎关注或提出建设性意见!

刚刚在线论坛, 欢迎踊跃提问或解答!

如有转载,请注明出处,谢谢!

谷歌浏览器翻墙插件

红杏已经彻底无法使用了,新推出了SS(Shadowsocks),价格如下


虽说不是很贵,然而也并不是很便宜。

其他的一些vpn比如云梯、Astrill等也均需要付费,稳定性也不太好。价格如下

我现在在用的是Astrill VPN, pc端和移动端均有astrill软件可供下载,比较方便,性能也还说的过去。当然金钱紧张的我肯定是不会买这么奢侈的东西了,用的之前一朋友的。

以上,都需要钱的,有没有免费的呢,有,叫“壁虎漫步”。

壁虎漫步是chrome的插件,和红杏有点像,没有会员功能,目前是免费的。试了下,效果可以接受。

插件下载地址 密码:rcan
下载 密码be3q

把下载好的插件拖到chrome浏览器,需要注册账号。

使用壁虎漫步的过程中,可以开启“自动切换模式”,遇到有打开障碍或者速度很慢的网站,就一键“添加规则”,将域名加入到壁虎漫步的规则列表中。这样的话,当访问在规则列表中的网站的时候,就会自动启用壁虎;而访问不在规则列表中的网站时,就自动使用直接连接。既不影响国内网站的速度,又不影响国外网站的访问,只需要初次遭遇访问障碍的时候添加规则,后续不用进行任何操作。

亲测可用,虽然有点不稳定,谁让它是免费的呢。

之前不知道什么原因下载了蓝灯(lantern),一直没理会,近来正好听一小伙伴提及,查了下,竟然可以翻墙,而且用法极其简单,直接打开运行即可使用。

Lantern是免费的桌面程序,用户可以流畅的访问被封锁的互联网站点。Lantern可以自动检测一个网站是否被封锁,对被封锁的网站,Lantern通过自有的服务器或未封锁地区的用户运行的Lantern来提供访问。对未封锁的网站不做影响。下载安装Lantern后,会在右上方的菜单栏出现。只要这个图标在,就表示Lantern正在运行,就可以用浏览器访问被封锁网站,如果不想使用,一定要正常退出Lantern,点击图标选择‘quit lantern’。如果是非正常退出,可能会使浏览器的代理设置改不过来,导致浏览器无法上网,解决办法是重新运行Lantern再正常退出即可。

Lantern下载

蓝灯也不能用了,讲道理,很是蛋疼

然而有什么问题是能难住天朝广大同胞的呢,我想是没有

使用萤火虫firefly也可以翻墙,使用方法和蓝灯一样

Firefly下载


如有任何错误之处或疑问,请联系我:fishnewsdream@gmail.com,欢迎交流,共同提高!

Objective-C/Swift技术开发交流群201556264,讨论何种技术并不受限,欢迎各位大牛百家争鸣!

微信公众号OldDriverWeekly,欢迎关注并提出宝贵意见

老司机iOS周报,欢迎关注或订阅

刚刚在线工作室,欢迎关注或提出建设性意见!

刚刚在线论坛, 欢迎踊跃提问或解答!

如有转载,请注明出处,谢谢!

Git相关命令汇总

Git命令庞大繁杂,有必要做个备份以供参考。这里仅列出最基础的部分命令。
先上个图感受下

  • 合并本地分支

    git merge local_branch_name

  • 合并远程仓库

    git merge origin/remote_branch_name

  • 查看当前分支与远程分支的对应关系

    git branch -vv

  • 列举所有配置

    git config -l

  • 比较暂存区和版本库差异

    git diff --cached

  • 将工作区做的修改暂存到一个git栈中
    git stash

  • 查看栈中所有暂存

    git stash list

  • 将暂存栈中指定编号恢复到工作区

    git stash apply stash_number (stash_numer:暂存编号 注意:暂存还在栈中)

  • 将栈顶的暂存恢复到工作区

    git stash pop

  • 清空暂存栈

    git stash clear

  • 查看远程服务器仓库状态

    git remote show origin

  • 删除远程仓库地址

    git remote rm

  • 修改远程地址

    git remote set-url origin git@github.com:robbin/robbin

  • 添加远程仓库地址

    git remote add origin git@github:robbin/robbin_site.git

  • 查看当前没有被合并的分支

    git branch --no-merged

  • 创建远程分支

    git push origin local_branch_name:remote_branch_name

  • 创建本地分支与远程分支关联(当前没有创建对应的本地分支)

    git checkout --track origin/remote_branch_name

    git checkout -b local_branch_name origin/remote_branch_name

  • 将本地分支与远程分支关联(已创建本地分支)

    git branch --set-upstream-to=origin/remote_branch_name

    git branch -u origin/remote_branch_name

  • 修改上一次的提交信息

    git commit --amend

  • 修改历史提交信息

    git rebase -i HEAD~20

  • 取消暂存

    git reset HEAD file_name

  • 撤销对文件的修改

    git checkout --file_name

  • 本地代码库回滚

    git reset --hard commit_id (回滚到commit-id,将commit-id之后提交的commit都去除)

    git reset --hard HEAD~3 (将最近3次的提交回滚)

  • 远程代码库回滚

    应用场景:系统发布后发现问题,需要回滚到某一个commit-id,再重新发布

    原理:先将本地分支退回到某个commit-id,删除远程分支,再重新push本地分支

    操作步骤

    1. git checkout the_branch

    2. git pull

    3. git checkout the_branch_backup (备份一下这个分支当前的情况)

    4. git reset --hard the_commit_id (把the_branch本地回滚到the_commit_id)

    5. git push origin :the_branch (删除远程the_banch分支)

    6. git push origin the_branch (用回滚后的本地分支重新建立远程分支)

    7. git push origin :the_branch_backup (如果前面成功了,删除这个备份分支)


如有任何错误之处或疑问,请联系我:fishnewsdream@gmail.com,欢迎交流,共同提高!

Objective-C/Swift技术开发交流群201556264,讨论何种技术并不受限,欢迎各位大牛百家争鸣!

微信公众号OldDriverWeekly,欢迎关注并提出宝贵意见

老司机iOS周报,欢迎关注或订阅

刚刚在线工作室,欢迎关注或提出建设性意见!

刚刚在线论坛, 欢迎踊跃提问或解答!

如有转载,请注明出处,谢谢!

iOS开发的一些奇巧淫技

“奇巧淫技”并非是一些稀奇古怪的技术要点,也不是什么所谓的黑科学,只是一些平时可能会用到但有些又并不是很容易查询得到的小tips.


如何查看真机沙盒文件?

一:安装iFunBox或IExployer

二:在xcode的上部导航栏里,选择window -> Devices,找到目标app ,这样我们只能看到沙盒的目录结构,看不到实体文件。然后点击Download Container ,下载后保存到桌面,右键显示包内容即可。

如何使用命令行进行打包?

一: 使用原生xcodebuild、xcrun 打开终端 cd到当前项目,使用如下两条命令即可。

如果项目没有使用cocoapods:

xcodebuild -project 项目名.xcodeproj -target 项目名 -configuration Release(生成项目名.app文件)

xcrun -sdk iphoneos -v PackageApplication ./build/Release-iphoneos/项目名.app -o ~/Desktop/项目名.ipa(-o前为.app文件路径,-o后为生成的.ipa路径)

如果使用cocoapods:

xcodebuild -workspace Test.xcworkspace -scheme Test -configuration Release

xcrun -sdk iphoneos -v PackageApplication 项目名.app路径 -o ~/Desktop/Demo.ipa

二:使用xctool进行自动打包,是FaceBook开源的一个命令行工具,可使用brew安装(安装命令:sudo brew install xctool)

如果项目没有使用cocoapods:

xctool -scheme Demo archive -archivePath “Demo/“(会生成.xcarchive文件)

xcrun -sdk iphoneos -v PackageApplication ./Demo.xcarchive/Products/Applications/Demo.app -o ~/Desktop/Demo.ipa

如果使用cocoapods:

xctool -workspace Demo.xcworkspace -scheme Demo archive -archivePath “Demo”

xcrun -sdk iphoneos -v PackageApplication ./Demo.xcarchive/Products/Applications/Demo.app -o ~/Desktop/Demo.ipa

Xcode历史版本下载地址

https://developer.apple.com/downloads/

加急审核渠道链接(并非只是加急,包括向苹果申诉、修改appstore上相关信息等)

https://developer.apple.com/appstore/contact/appreviewteam/index.html
也可以直接从ITunes右下角contact us也可以,但是无法填写相关文字描述

Autolayout及SizeClass示意图



窗口中有多个responder,如何快速释放键盘

一:[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];

二:
[self.view endEditing:YES];

如何去除UITableView中Group样式cell的边框

一:

UIView *tempView = [[UIView alloc] init];

[cell setBackgroundView:tempView];

[cell setBackgroundColor:[UIColor clearColor]];

二:

tableView.separatorColor=[UIColor clearColor];

如何解决colorWithPatternImage设置view背景色太占内存问题

self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"main_landscape.jpg"]];

切换成

self.view.layer.contents =[UIImage imageNamed:@"name.png"].CGImage;

App升级后如何删除NSUserDefaults全部数据

APP升级后,UserDefaults中原有的plist是不会删除的,除非用户卸载APP

NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];

[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];

如何修改图片颜色

先设置图片的渲染模式为UIImageRenderingModeAlwaysTemplate,再设置tintcolor

self.imageView.image = [[UIImage imageNamed:@"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];

self.imageView.tintColor = [UIColor redColor];

也可以在Asset catelog中设置render as

如何放大图片使之不失真且不产生锯齿

在Asset Catalog的Attribtues Pane中设置Single(前提是图片必须是矢量图) Vector为SingleVector

如何在拉伸图片的时候使之不变形

一:stretchableImageWithLeftCapWidth: topCapHeight:

二:在Xcode中选中图片,然后点击右下角的Show Slicing:

Storyboard中如何正确设置控件透明度

在直接设置alpha的情况下,如果上面有文字的话会随着透明度的变大而变得越来越不清楚。

此时应该这样设置 Background →0ther →0pacity

[UIScreen mainScreen].bounds获取屏幕大小不对的问题

#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width

#define iPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242,2208),[[UIScreen mainScreen] currentMode].size) : NO)

在标准模式下

6+:

[[UIScreen mainScreen] currentMode].size为{1242,2208}

[UIScreen mainScreen].bounds.size为{414,736}

6:

[[UIScreen mainScreen] currentMode].size为{750,1334}

[UIScreen mainScreen].bounds.size为{375,667}

放大模式下(设置->显示与亮度->显示模式)

6+:

[[UIScreen mainScreen] currentMode].size为{1125,2001}

[UIScreen mainScreen].bounds.size为{375,667}

6:

[[UIScreen mainScreen] currentMode].size为{640,1136}

[UIScreen mainScreen].bounds.size为{320,568}

Mac模式下如何将视频生成Gif文件

一:使用GifBrewery直接将视频转为gif文件(安装GifBreweryw密码:www.macx.cn)

二:安装ffmpeg ( brew install ffmpeg)使用ffmpeg使视频转为多张图片

( ffmpeg -i news.mov -r 10 -f image2 文件夹名/%05d.png)

然后使用PicGIF (将多张图片生成gif文件)


以上,用代码直接展示并不方便,所以依次列出来,其他技巧均在Demo中体现,欢迎下载。

下载链接:https://github.com/ChinaFishNews/StrangeSkills.git


如有任何错误之处或疑问,请联系我:fishnewsdream@gmail.com,欢迎交流,共同提高!

Objective-C/Swift技术开发交流群201556264,讨论何种技术并不受限,欢迎各位大牛百家争鸣!

微信公众号OldDriverWeekly,欢迎关注并提出宝贵意见

老司机iOS周报,欢迎关注或订阅

刚刚在线工作室,欢迎关注或提出建设性意见!

刚刚在线论坛, 欢迎踊跃提问或解答!

如有转载,请注明出处,谢谢!

写在开篇

一直以来都有记录的习惯,尤其是做开发,这个习惯必不可少。中学时代老师经常提醒我们要常做笔记,记录一些疑难要点、好词好句和曾经做错的题目,当时不以为然,后来也证明不听老师话的结果,惨不忍睹!

之前一直在用印象笔记,粗略查了下里面记录了上千篇,至少有一半不是我自己总结的,还有四分之一现在看来都不值一提,剩下的四分之一才感觉依然很有价值。相关Demo写了少说也有几百个,良莠不齐。

做iOS开发也有两年多了,感觉也到了该回馈社会的时候了,请原谅我使用”回馈社会”这么装逼的字眼。依然记得刚开始工作时的阵痛,虽然只有不到两个月的时间。万事开头难,尤其是做技术,没人指引对于新手来说的确是痛苦的,尽管成长的很快。在此感谢入行之初帮助过我的那些前辈,尤其是上海的那哥们和苏州的一妹子,虽然他们未必看得到。一直以来也都有在尝试帮助他人,不过也都是在公司内部或相关技术交流群里,范围终归有限。所以从现在开始尝试写些博客,尽自己绵薄之力。很多网上的文章都是互相转载未经查证,或者炫技卖弄隐藏关键点,让人作呕!也希望更多的博主能保证文章质量,杜绝浮躁之风,尽管没什么卵用。

接下来有时间的话会陆续上传或撰写一些技术要点和生活感悟,有些技术要点可能在现在看来都不算一个点了,但是对于新人来说也许依然会感到陌生,重要的是,我喜欢这种可以看到自己成长的过程!

网络文明依然需要大家共同努力,希望尽量做到谨慎拍砖,砖要轻拍,拍之有理。但是谁又会理会呢?

读万卷书不如行万里路,行万里路不如阅人无数,阅人无数不如名师指路,名师指路不如自己去悟!共勉!


如有任何疑问或问题请联系我:fishnewsdream@gmail.com,欢迎交流,共同提高!

Objective-C/Swift技术开发交流群201556264,讨论何种技术并不受限,欢迎各位大牛百家争鸣!

微信公众号OldDriverWeekly,欢迎关注并提出宝贵意见

老司机iOS周报,欢迎关注或订阅

刚刚在线工作室,欢迎关注或提出建设性意见!

刚刚在线论坛, 欢迎踊跃提问或解答!

如有转载,请注明出处,谢谢!

本站总访问量 本文总阅读量