UI分析工具Reveal的使用

没想到三年后还得再编辑一次

详情参见官网,提供了三个集成方法

考虑到对代码的零侵入性,首选断点集成(如果打了全局断点,则Reveal是不可用的),具体也不知道是哪个版本的Reveal开始使用的了

参考官网的步骤,依然显示无法连接

解决方法如下:

  1. 在终端中使用命令

    dns-sd -B _reveal._tcp local

    可以查看本机的reveal号码

  2. 使用命令查看监听Reveal的端口号

    dns-sd -L Reveal--Reveal号码 _reveal._tcp.

  3. 使用命令查看连接

    curl -s -D - http://localhost:端口号/application -o /dev/null

  4. 最后是改Host,Mac的Host文件在在/etc/hosts中,加入3行:

1
2
3
4
5
127.0.0.1 localhost

255.255.255.255 broadcasthost

::1 localhost

官网下载地址: http://revealapp.com (试用期30天)

在真正玩这个之前,听到了很多对Reveal的褒奖,不需要重写代码可以改变用户页面,实时修改页面布局,查看任意APP的UI等。

只是想感受一下的话,可以简单的集成一下。下载安装好后打开

将Reveal.framework导入项目,并需要添加libz.tbd库,在Other Linker Flags项增加Any iOS Simulator SDK项

使用模拟器和真机都可以,但是使用真机的话需要和电脑处在同一网络环境下。

然后使用模拟器运行项目,打开Reveal

上图左侧就是目录结构,中间是UI展示,右侧是类似于Xcode右侧的导航展示,可随意修改,并且会让模拟器的UI一并修改,虽然再次运行会恢复。

虽然Xcode自带有Debug View Hierarchy,但是相对较弱。在Reveal虽然可以修改UI但是感觉也不是很方便,也可能是因为接触不多没接触到精髓,但是使用Reveal来查看UI层次结构的确更为方便,尤其是刚接手一个新的项目,点击控件的同时会显示对应的类和相关的值。

因为试用期30天,网上有看到延长试用时间的方法,是进入~/Library/Preferences/这个目录,打开com.ittybittyapps.Reveal.plist文件,删除IBAApplicationPersistenceData这一项。然后再打开Reveal,会在右上角看到试用提示,再次变成30天。这只是延长使用时间,没能真正破解。还可以修改电脑上的系统时间,设为之前的时间,再次打开也可以继续使用,但是每次都要修改时间再改回来觉得比较麻烦,希望能找到好的破解方法。

有人研究出了用Reveal来观看任意app的UI方法,这个属于逆向工程的范畴了,也是接下来需要研究的课题!

网上的另外一种集成方法是

在当前用户目录新建一个文件.lldbinit,位于~/.lldbinit,LLDB每次启动的时候都会加载这个文件
在.lldbinit中输入如下内容
1
command alias reveal_load_sim expr (void*)dlopen("/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib", 0x2);
1
command alias reveal_load_dev expr (void*)dlopen([(NSString*)[(NSBundle*)[NSBundle mainBundle] pathForResource:@"libReveal" ofType:@"dylib"] cStringUsingEncoding:0x4], 0x2);
1
command alias reveal_start expr (void)[(NSNotificationCenter*)[NSNotificationCenter defaultCenter] postNotificationName:@"IBARevealRequestStart" object:nil];
1
command alias reveal_stop expr (void)[(NSNotificationCenter*)[NSNotificationCenter defaultCenter] postNotificationName:@"IBARevealRequestStop" object:nil];

上述文件创建了4个命令:reveal_load_sim,reveal_load_dev, reveal_start 和 reveal_stop

  • reveal_load_sim 这个只在iOS模拟器上有效。它从Reveal的应用程序bundle中找到并加载libReveal.dylib(请确保你把Reveal安装到了系统的Application文件夹,如果你换地方了,你修改上述的文件)。

  • reveal_load_dev 这个命令在iOS设备和模拟器上都有效。不过,它需要你在Build Phase中的的Copy Bundle Resources中加上libReveal.dylib,请确保没有放到Link Binary With Libraries这个地方。

  • reveal_start 这个命令发出一个通知启动Reveal Server。

  • reveal_stop 这个命令发出一个通知停止Reveal Server。

打开工程后,在appdeleget中的didfinished:方法中打下断点,并按下图编辑断点,重新运行下应用即可

Reveal也可以在真机调试中链接真机,需要在上图将reveal_load_sim改为reveal_load_dev,并且需要将reveal的动态链接库一并添加进工程中:

  • 点击revel菜单的”help->show reveal library in finder”在finder中查找reveal的动态链接库:libReveal.dylib,并将其拖入工程

  • 然后调整libReveal.dylib的引用方式,需要将libReveal.dylib文件拷贝到Sandbox中,但是我们通常在引入libReveal.dylib的时候Xcode默认是以Link Binary With Libraries的方式的,实际上应该是Copy Bundle Resources,所以应该先将libReveal.dylib从Link Binary With Libraries中移除掉,然后在Copy Bundle Resources中添加。


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

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

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

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

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

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

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

Markdown在线编辑

MarkDwon是一种轻量级的标记语法,容易排版,可轻松转化为Pdf、Html格式文件,支持MarkDwon的编辑器有很多,包括像Markdown Pro、Mou、MacDwon、MacHub等等。

提供一个Markdown Pro的下载地址:http://pan.baidu.com/s/1c0Pk4oS 密码:sehk

如何编辑使用呢?

在此提供两个官方案例:

https://www.zybuluo.com/mdeditor#fn:code

https://www.zybuluo.com/mdeditor#fn:code

模仿案例上的格式基本迅速就可以掌握使用方法

里面用到的图片,我是上传到七牛云存储上,之前尝试过上传到百度云或者直接放在当前目录下,有时并不能显示成功,放在七牛云上下载显示的很快,基本没出现过问题。


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

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

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

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

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

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

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

Hexo博客托管在Coding上

昨天GitCafe官网上发布声明,截至2016年5月31日停止所有服务,届时上面的资料和项目都将被永久删除,需要尽快将资料和项目迁移至Coding,只是因为被Coding收购了。操了个蛋,之前的博客就是托管在GitCafe上和GitHub上的,好在还有GitHub做后盾,不用太担心。虽然会一直服务到5月31号,不过还是决定将 博客迁移到Coding上。

前面的一些安装步骤就此省略,和托管在GitCafe和GitHub上的步骤一样,如果不太清楚可以参考之前的博文,之前已经安装了则可直接进行下面的步骤。

到Coding.net官网创建一个项目,名字可以随意,不过我还是创建了一个和用户名一样的项目名。

然后到Hexo的安装目录中打开_cinfig.yml文件,找到deploy项,按如下修改

1
2
3
4
5
deploy:
type: git
repo:
github: git@github.com:ChinaFishNews/ChinaFishNews.github.io.git
coding: https://git.coding.net/ChinaFish/ChinaFish.git

我还是将博客同时托管在Coding和GitHub两个平台上,无他,只因更相信GitHub。这里面的ChinaFishChinaFishNews都是第一个是用户名,第二个是项目名,之前的设置是

1
2
3
4
5
deploy:
type: git
repo:
github: git@github.com:ChinaFishNews/ChinaFishNews.github.io.git
# gitcafe: git@gitcafe.com:ChinaFishNews/ChinaFishNews.git,gitcafe-pages

可惜GitCafe被收购了。

然后打开终端使用下面命令push到仓库

1
2
3
hexo generate

hexo deploy

这一步可能会报错 ERROR Deployer not found: git 使用如下命令

1
npm install hexo-deployer-git --save

然后回到Coding的仓库下面进行如下配置

按照如图,点击检测,会提醒没有检测到环境,不过直接点强制开启就可以了

访问域名这一项可以随意填写,应用内存选的稍微大一些,点击显示高级选项

将运行环境设置为Html,没设置前在部署的时候会导致失败。其他的不用填写,点击一键部署,可能会部署失败。如果部署失败可以查看日志或者查看常见部署问题解答。至此,配置就结束了,可以使用chinafishnews.coding.io访问博客了,chinafishnews是刚才填写的域名。

但是这样的话,每次写文章发布后都需要手动到这里部署,比较麻烦。可以使用webhook来自动部署。如下图所示

点击新建Hook

上面 的输入框中是填你的博客域名,也就是前面部署的时候那个域名地址,然后在后面加上/_

下面的输入框是输入token,直接填写{ {TOKEN} }就可以了

然后点击环境变量,变量名填WEBHOOK_TOKEN,值为{ {TOKEN} }。然后回到控制台重新启动应用就可以了。这样就实现了自动部署。

另外,之前的域名指向的是GitCafe,现在被Coding收购,GitCafe的功能激也都基本被保留,只是需要重新解析域名,但是,也有一些改动。如下图添加域名

注意,部署分支的coding-pages需改成master,这是因为Hexo中_config.yml文件中的的coding源默认是master。否则无法成功。

然后到阿里云重新解析域名

注意,gitcafe中填写的是chinafishnews.gitcafe.io,chinafishnews是gitcafe上自己的用户名,而coding上需改成ChinaFish.coding.me,chinafish是coding上自己的用户名,上图中也有提示已经绑定的域名(CNAME记录指向ChinaFish.coding.me),稍等片刻便可以使用chinafish.news访问博客了。


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

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

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

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

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

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

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

iOS程序后台无限运行

让我们的应用一直在后台运行不被系统kill掉,这种需求并不多见。在我呆的上家公司里,期间有段时间研究了很多稀奇古怪的问题,比如safari和app互通、监测当前手机打开哪些程序、监测手机app各自运行时间、程序一直运行不被系统杀死等等很多,当然有些并没有研究出结果,也许本来就实现不了。这个后台无限运行,想上App Store肯定是会被拒的,在用企业证书分发的app有些可能会用到,之前用到这个的场景是因为积分墙刷榜,这个程序要在后台一直运行,来监测其他app的行为,如注册、登陆、认证等,这就要这个app持续运行。鉴于苹果对iOS系统的封闭政策,我们的APP在进入后台能做的事情相对有限。一般只有几秒的时间留给用户进行一些清理和数据保存的时间,当然可以使用开启新任务向系统借点时间,但这时间也是有限的, 不会超过10分钟,10分钟后无论怎么向系统申请继续后台,系统会强制挂起App,挂起所有后台操作、线程,直到用户再次点击App之后才会继续运行。但是系统也预留了一些特殊应用可以实现真后台的方法,包括VoIP、GPS、Audio等。

这里使用了MMPDeepSleepPreventer类,这个类就是通过一直播放无声音乐来实现永久后台运行。一般来说在播放音乐的时候,如果打开其他播放音乐的应用,那之前的音乐就会被关闭,但是MMPDeepSleepPreventer这个类使用了定时器每5秒播放一次,所以不会被中断,除非手动kill掉项目。

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

把上述问价导入项目,添加AVFoundation.framework、AudioToolbox.framework

在Info.plist中设置添加Required background modes键值,设置为App provides Voice over IP services

另外在Bulid Phases中将MMPDeepSleepPreventer.m类设置为-fno-objc-arc

然后需在AppDelegate中设置

1
2
#import <AVFoundation/AVFoundation.h>
#import "MMPDeepSleepPreventer.h"

1
2
MMPDeepSleepPreventer * soundBoard =  [MMPDeepSleepPreventer new];
[soundBoard startPreventSleep];

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

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

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

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

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

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

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

UIImage的相关操作

最近看了下JSQMessage的源码,一个比较优雅的UI库,代码很赞,只用了少量的图片,牵扯了一些UIImage的相关操作。虽然控件比较基本 ,但是一些用法还是不是很清楚,希望接下来能有时间去整理下其他的基础控件。此次仅为UIImage的一些相关操作。

Github下载地址: https://github.com/ChinaFishNews/UIImageDemo

通过颜色生成纯色图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+(UIImage *)imageWithColor:(UIColor *)color
withSize:(CGSize)size{

CGRect rect = CGRectMake(0, 0, size.width, size.height);

UIGraphicsBeginImageContext(rect.size);

CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetFillColorWithColor(context, [color CGColor]);

CGContextFillRect(context, rect);

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return image;
}

通过图片生成圆形图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+(UIImage *)circualImage:(UIImage *)image
withDiameter:(NSUInteger)diameter{

CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter);
UIImage *newImage = nil;

UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale);
{
UIBezierPath *imgPath = [UIBezierPath bezierPathWithOvalInRect:frame];
[imgPath addClip];
[image drawInRect:frame];

newImage = UIGraphicsGetImageFromCurrentImageContext();

}
UIGraphicsEndImageContext();

return newImage;
}

通过图片或颜色生成圆形图片(如传入颜色则通过颜色生成)

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
+(UIImage *)circualarImage:(UIImage *)image
withDiameter:(NSUInteger)diameter
highlightedColor:(UIColor *)highlightedColor{

CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter);
UIImage *newImage = nil;

UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale);
{
CGContextRef context = UIGraphicsGetCurrentContext();

UIBezierPath *imgPath = [UIBezierPath bezierPathWithOvalInRect:frame];
[imgPath addClip];
[image drawInRect:frame];

if (highlightedColor != nil) {
CGContextSetFillColorWithColor(context, highlightedColor.CGColor);
CGContextFillEllipseInRect(context, frame);
}
newImage = UIGraphicsGetImageFromCurrentImageContext();

}
UIGraphicsEndImageContext();

return newImage;
}

在已有图片上添加文字

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
+(UIImage *)addTextInImage:(UIImage *)image
text:(NSString *)text
textColor:(UIColor *)textColor
font:(UIFont *)font{

CGRect frame = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height);

NSDictionary *attributes = @{ NSFontAttributeName : [UIFont systemFontOfSize:33],
NSForegroundColorAttributeName : textColor };

CGRect textFrame = [text boundingRectWithSize:image.size
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil];

CGPoint frameMidPoint = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
CGPoint textFrameMidPoint = CGPointMake(CGRectGetMidX(textFrame), CGRectGetMidY(textFrame));

CGFloat dx = frameMidPoint.x - textFrameMidPoint.x;
CGFloat dy = frame.size.height - textFrameMidPoint.y*2;;
CGPoint drawPoint = CGPointMake(dx, dy);

UIImage *newImage = nil;
UIGraphicsBeginImageContext(frame.size);
{
[image drawInRect:CGRectMake(0, 0, frame.size.width, frame.size.height)];

[text drawAtPoint:drawPoint withAttributes:attributes];

newImage = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();

return newImage;
}

通过颜色生成图片并添加文字

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
+(UIImage *)imageWithText:(NSString *)text
backgroundColor:(UIColor *)backgroundColor
textColor:(UIColor *)textColor
font:(UIFont *)font
size:(CGSize )size{

CGRect frame = CGRectMake(0.0f, 0.0f, size.width, size.height);

NSDictionary *attributes = @{ NSFontAttributeName : font,
NSForegroundColorAttributeName : textColor };

CGRect textFrame = [text boundingRectWithSize:frame.size
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil];

CGPoint frameMidPoint = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
CGPoint textFrameMidPoint = CGPointMake(CGRectGetMidX(textFrame), CGRectGetMidY(textFrame));

CGFloat dx = frameMidPoint.x - textFrameMidPoint.x;
CGFloat dy = frameMidPoint.y - textFrameMidPoint.y;
CGPoint drawPoint = CGPointMake(dx, dy);
UIImage *newimage = nil;

UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale);
{
CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, frame);
[text drawAtPoint:drawPoint withAttributes:attributes];

newimage = UIGraphicsGetImageFromCurrentImageContext();

}
UIGraphicsEndImageContext();

return newimage;
}

图片叠加

1
2
3
4
5
6
7
+(void)maskView:(UIImageView *)imageView withImage:(UIImage *)image{

UIImageView *imageViewMask = [[UIImageView alloc] initWithImage:image];
imageViewMask.frame = CGRectMake(0, 0, imageView.frame.size.width, imageView.frame.size.height);

imageView.layer.mask = imageViewMask.layer;
}

或者使用另外一种方法

1
2
3
4
5
6
7
8
- (void)drawRect:(CGRect)rect {

UIImage *imageOne = [UIImage imageNamed:@"bubble_min"];
UIImage *imageTwo = [UIImage imageNamed:@"goldengate"];

[imageOne drawInRect:rect];
[imageTwo drawInRect:CGRectInset(rect, 0.0f, 0.0f) blendMode:kCGBlendModeSourceIn alpha:1];
}

水平旋转图片

1
2
3
4
5
6
+(UIImage *)horizontallyFlippedImageFromImage:(UIImage *)image{

return [UIImage imageWithCGImage:image.CGImage
scale:image.scale
orientation:UIImageOrientationUpMirrored];
}

修改图片颜色

//kCGBlendModeOverlay能保留灰度信息,kCGBlendModeDestinationIn能保留透明度信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+ (UIImage *)imageColorChanged:(UIImage *)image
tintColor:(UIColor *)tintColor
blendMode:(CGBlendMode)blendMode{

UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0f);
[tintColor setFill];
CGRect bounds = CGRectMake(0, 0, image.size.width, image.size.height);
UIRectFill(bounds);

[image drawInRect:bounds blendMode:blendMode alpha:1.0f];

if (blendMode != kCGBlendModeDestinationIn) {
[image drawInRect:bounds blendMode:kCGBlendModeDestinationIn alpha:1.0f];
}
UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

return tintedImage;
}

方法使用:


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

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

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

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

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

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

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

iOS开发使用AVPlayer、MoviePlayer播放本地、网络视频

Github下载地址: https://github.com/ChinaFishNews/VideoPlay.git

Demo中使用的ALMoviePlayerController是一个框架,继承了系统的MPMoviePlayerController,做了一些扩展和封装 。

代码我就不过多解读了,相对是比较清晰的,仅是为了实现播放功能,虽然很简陋且还有一些问题。

其中在使用AVPlayer播放视频的时候滑动进度条稍微有些不灵敏,之前在做项目的时候因为是使用Swift语言实现的,所以功能没法直接拿出来,就用OC也勉强实现了这个功能。项目中的解决方法是通过在进度条上画了热点,判断当前点击的点或滑动停止的点占总长度的百分比,从而定位到正确的播放位置。代码如下:

画出热点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func drawHotField()
{
let width:CGFloat = self.view.bounds.size.width
let height:CGFloat = self.view.bounds.size.height

self.buttonMaintain = CGPathCreateMutable()

if self.whetherPortrate == true
{
CGPathMoveToPoint(buttonMaintain,nil, 38, height-60)
CGPathAddLineToPoint(buttonMaintain, nil, width-170, height-60)
CGPathAddLineToPoint(buttonMaintain, nil, width-170, height)
CGPathAddLineToPoint(buttonMaintain, nil, 38, height)
CGPathCloseSubpath(self.buttonMaintain)
}else
{
CGPathMoveToPoint(buttonMaintain,nil, 60, 65)
CGPathAddLineToPoint(buttonMaintain, nil, 60, height-150)
CGPathAddLineToPoint(buttonMaintain, nil, 0, height-150)
CGPathAddLineToPoint(buttonMaintain, nil, 0,65 )
CGPathCloseSubpath(self.buttonMaintain)
}
Logger.info("pathPoint: \(self.buttonMaintain)")
}

点击

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
func controlViewClicked(tapGesture:UITapGestureRecognizer)
{
self.drawHotField()
Logger.info("tap:---------------")
let tapPoint:CGPoint = tapGesture.locationInView(self.view)

if CGPathContainsPoint(self.buttonMaintain,nil,tapPoint,false) == true
{
var currentX = tapPoint.x
var percent = (currentX-38)/self.progessSlider!.frame.size.width

if self.whetherPortrate != true
{
currentX = tapPoint.y
percent = (currentX-65)/self.progessSlider!.frame.size.width
}

let duration: CMTime = self.player!.currentItem!.duration
var currentSecond: Float64 = CMTimeGetSeconds(duration)
currentSecond = currentSecond * Float64(percent)
self.progessSlider?.value = Float( currentSecond / CMTimeGetSeconds(self.player!.currentItem!.duration))
self.player?.currentItem!.seekToTime(CMTimeMakeWithSeconds(Float64(Float(CMTimeGetSeconds(duration)) * self.progessSlider!.value), Int32(CMTimeGetSeconds(duration))))
self.progressLabel?.text = self.getCurrentTime(currentSecond)

self.judgeWhetherEnd()
}
}

滑动

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
func controlViewPaned(panGesture:UIPanGestureRecognizer)
{
self.drawHotField()
Logger.info("pan:---------------")
let tapPoint:CGPoint = panGesture.locationInView(self.view)

if panGesture.state == UIGestureRecognizerState.Began || panGesture.state == UIGestureRecognizerState.Changed
{
if CGPathContainsPoint(self.buttonMaintain,nil,tapPoint,false) == true
{
var currentX = tapPoint.x
var percent = (currentX-38)/self.progessSlider!.frame.size.width

if self.whetherPortrate != true
{
currentX = tapPoint.y
percent = (currentX-65)/self.progessSlider!.frame.size.width
}

let duration: CMTime = self.player!.currentItem!.duration
var currentSecond: Float64 = CMTimeGetSeconds(duration)
currentSecond = currentSecond * Float64(percent)
self.progessSlider?.value = Float( currentSecond / CMTimeGetSeconds(self.player!.currentItem!.duration))
self.player?.currentItem!.seekToTime(CMTimeMakeWithSeconds(Float64(Float(CMTimeGetSeconds(duration)) * self.progessSlider!.value), Int32(CMTimeGetSeconds(duration))))
self.progressLabel?.text = self.getCurrentTime(currentSecond)

self.judgeWhetherEnd()
}
}s
}


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

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

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

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

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

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

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

遍地的奢华与奇缺的教养

本文系转载

作者:梁文道

来源:新华网思客精选

原题:《什么是奢华,什么是教养》

今天的中国,无论你走到哪里,几乎都能看见“奢华”这两个字。每一本时尚生活杂志都在不厌其烦地告诉你有关奢华的故事,每一个商品广告都试图让你感到它要卖的商品有多奢华。
于是房子是奢华的,车子是奢华的,大衣是奢华的,手表是奢华的,皮鞋也是奢华的,就连内裤也可以很奢华,乃至于我刚刚吃过的涮羊肉也标榜自己的用料十分奢华。本来这种东西是可以见怪不怪的,正所谓奢华见惯亦平常。

可是有一天,我在杂志上看到一篇介绍英国手工定制鞋的文章,作者先是不断渲染英国绅士的低调含蓄,一两千字之后笔锋忽然一转,他还是未能免俗地要大谈这鞋子有多奢华,并将其定位为“低调的奢华”。然后把绅士等同于品位,再将品位等同于奢华。许多媒体早就在“奢华”和“品位”之间画上等号了,但现在有人进一步连“绅士”也挂了上去,这就让我觉得有些刺眼了。

我的生活奢华不起,我的言行也离绅士甚远,可我总算读过不少传说中的英国绅士写的东西,在我的印象中,绅士和奢华根本是两个完全不同的概念。且看19世纪英国绅士之间的通信,关于绅士的品位,他们是这么说的:“×××的家朴实无华,真是难得的好品位。”“他是那种老派的绅士,一件大衣穿了20年。”他们会称赞一个人的朴实和惜物,低调而不张扬,却绝对不会把看得见的奢华当作品位,尤其不会把它视为绅士的品位。

就以一双手工制作的顶级皮鞋来说吧,它是很贵,但它可以穿上一二十年,这里头的学问不只是它自身的质量,更是你穿它、用它的态度。

首先,你会珍惜它,所以走路的姿势是端正的,不会在街上看见什么都随便踢一脚。其次,你愿意花点时间和心思去护理它,平常回家脱下来不忘为它拂尘拭灰,周末则悠悠闲闲地替它抹油补色,权当一种调剂身心的休息活动(就算他有佣人,他也宁愿自己动手)。所以这双鞋能够穿得久,10年之后,它略显老态,但不腐旧,看得出是经过了不错的照料,也看得出其主人对它的爱惜。

这叫作绅士

不一定喜欢昂贵的身外物,但一定不随便花钱,朝秦暮楚。他的品位不在于他买了什么,而在于他的生活风格甚至为人;他拥有的物质不能说明他,他拥有物质的方式才能道出他是个怎么样的人。

当然,一个人不能做物质的奴隶,但他的人格、性情或许可以借着物质偶尔散发出来。简单地讲,这就是教养

“教养”是一个何其古老、于今天何其陌生的词啊。这个词本来才是品位的绝配,不过,由于教养困难,奢华容易,我们今天才会把品位许给了奢华,让空洞的、无止境的消费去遮掩教养的匮乏。久而久之,甚至开始有人以为,英国的传统绅士皆以奢华为人生第一目标。如果你觉得“教养”太过抽象,我可以为你举一些没有教养的好例子。开着一部奔驰车在街上横冲直撞,觉得行人全是活该被吓死的贱民,这是没有教养的。手上戴着伯爵表,然后借醉酒臭骂上错菜的服务员小妹妹,这也是没有教养的。教养不必来自家教,更不是贵族的专利,上进的绅士更看重后天的自我养成。

然而,如今有力奢华地招摇过市之辈多如过江之鲫,甘于谦逊、力求品格善美的人却几不可闻,岂不可叹?我只不过是在北京一家火锅店见着它用“奢华”二字形容自己的材料,便忍不住发出这一大堆牢骚,这自然也是没有教养的表现。


奢华和教养的分界点在哪里?一个向外——求胜。一个向内——求安。无时无刻不在和他人相比,自然就倾慕奢华。无时无刻不在要求自己进步,自然就有了教养

真正的奢华是内心的高尚,这不是昂贵的物品所能装饰出来的。所以如何去做一个高尚的人,这才是我们最应该要去购买的“奢侈品”,当你拥有了这件专属于你的私人定制品时,你所散发出的气质会遮挡每一件价值不菲的身外之物。

在这个最美好,同时也是最物质的年代,我们都应变得物质。但这里所说的物质我们不能狭隘的只看它表面的意思、不能只想到现在的社会风气所熏陶出来的“物质”。现代社会的物质更准确的理解就是梁文道老师在文章中所说的“一个人的人格、性情或许可以借着物质偶尔散发出来。简单地讲,这就是教养。”

如果有了教养,你的内心自然会变得高尚,内心高尚的人,请你自信的告诉世人,你就是一件无与伦比的奢侈品。

父母尚在苟且,而你却在炫耀诗和远方

本文系转载

作者:入江之鲸

来源:入江之鲸(rujiangzhijing001)

前段时间,和朋友聊天,他陪一个弟弟在北海道旅行。我问他玩得是不是很开心。他告诉我,他和弟弟不是一路人,所以旅途并不是很愉快。

他细细跟我讲道,弟弟缠着爸妈要去日本玩,他妈不放心,便邀请我朋友跟着弟弟过去。

他的弟弟,家境不算富裕,刚上大学也没有能力自己赚钱,却有着挥金如土的本事。就拿一件小事来讲吧,日本物价很贵,一片哈密瓜要三十人民币左右。

朋友问我:“你能够认同自己还不能挣钱,家里也不是很有钱,眼都不眨只是因为口渴了,不肯买水却一口气吃了三片哈密瓜的小孩吗?”

这孩子,让我想到一句印象很深刻的话:父母尚在苟且,你却在炫耀诗和远方。

身边这样的人,挺多的。

我另外一个朋友,家庭条件很一般,却把日子过得很“高级”。

她嫌单位盒饭难吃,每天中午出去下馆子,下午还必定订一杯十几块钱的奶茶外送。和她一起出去逛街,她总会拉着我吃人气很高、价格也很昂贵的餐厅。和她旅游,她对景区里各式物价虚高的食物和纪念品,向来都是潇洒地买买买,花钱如流水。臭豆腐不算好吃,她尝了一块,吐出来,嫌恶地皱皱眉,扔了。

我都不敢劝她花钱别太大手大脚。每次试图奉劝她,她都不服气地斜乜着眼,搬出她的有两句名言,理直气壮地开腔。

第一句,“女孩子,要富养”;第二句,“出来玩,就一定要开心,别太在乎钱。”似乎我要是劝她适当地节约,倒显得我太抠门太小气了。

我本以为她必定家境殷实,直到有一次去她家里。她住在城郊的民房里,老旧潮湿,又窄又小,从一楼上二楼,要从一个很陡峭的楼梯爬上去。

她的奶奶穿着她高中时的校服外套,坐在家里拣菜。她问奶奶怎么不去打牌,老人家说道:这两天输了几十块钱,今天不高兴去了。

我借用他们家卫生间,奶奶不忘嘱咐我,要用桶里盛的洗过拖把的水冲,别按按钮,水一冲哗啦啦的,浪费钱。

正是这样节俭的老人,却把自己靠卖菜一块一块攒来的积蓄,尽数交给孙女,任由孙女挥霍。

中午和她爸爸妈妈一起吃饭,她爸表态,不指望她赚钱养家,她赚的那点工资,给自己吃穿用度就好了。

后来,那位朋友约我假期一起去旅游,向我提起冬天上下班很冷,她准备买车,家里人也同意给她买。听到这些,我都只能笑笑,不知道该回应些什么。

有句笑话这样说,“我视金钱如粪土,爸妈视我如化粪池。”

我们这一代,不少人如此。

前段时间网上讨论孩子究竟该穷养还是富养,提倡富养的人问:男孩要穷养?你孩子跟你多大仇啊?

我也想问问那些拿着父母的血汗钱挥霍无度的子女:孩子要富养?你爸妈欠你多少钱啊?

我认识一个男生,他从上大学后到工作前的所有花销,都是向父母打了欠条的。偶尔出行旅游花的钱,也是靠自己兼职打工赚来的。工作后,他就从每个月的工资里抽钱一笔笔地偿还父母。

孩子成年后,父母已经没有了抚养义务,压根不必探讨穷养、富养的话题。

可现实情况是,不少人结了婚,还让爸妈背房贷。

如果你和我一样,出身于平凡的家庭,那么你应该很清楚,父母挣来的每一分钱,都很不容易。

当父母在烈日炎炎下满头大汗地从事体力劳作时,当父母在小小格子间里腰酸背痛地从事脑力劳动时,你一顿大餐就消费掉他们一天的薪水,真的不会有一丝丝愧疚吗?

当父母被领导大呼小叫的时候,当父母被客户呼来喝去的时候,你却在呼朋引伴、潇洒度日,真的不会于心不忍吗?

当你用iPhone、iPad、Mac把自己全副武装的时候,父母却连买个10块钱100M的流量包都要思忖好久,最后还没舍得买;

当你穿着一身说得出名字的品牌,一双鞋就要几千块的时候,父母却在穿着被你淘汰的旧鞋,他们不懂你说的品牌,你还笑他们落伍;

当你觉得你的知识、素养、视野都远超父母,因此嫌弃父母“没见过世面”的时候,有没有想过,其实,正是父母托举着你到更高的地方,你才有机会看到了更大的世界?

你活得青春无敌、你过得光鲜亮丽,却看不见你身后,默默供养着你的父母,为了让你过上更好的生活,还在向这个世界低声下气。

别在缺钱的时候才想起父母,他们不是ATM,他们胸膛上有温度,他们心跳里有感情。

—— 爸妈爱我们,爱得不容易。

iOS开发如何集成并使用环信SDK

现在环信SDK(EaseMobSDK)更新到了3.0,我开始使用的时候只是2.3或者2.4的版本。使用的时候在视频聊天的时候体验不太好,经常看不到对方,后来更新到了最新的版本,改善了很多。不过UI我使用的依然是之前的版本,不得不说之前的UI满满的iOS6即视感。

首先到官网上注册应用

需要上传一个开发和发布环境下的p12文件,在发送消息时需使用推送。

下载SDK后按照官方文档导入项目,做好配置。

在AppDelegate+EaseMob中做了初始化和推送的一些操作。

方法里面的代码,在官方Demo中有所体现,按照自己项目的需求自行改动。

在项目首页,加了个扩展类BNHomePageViewController+EaseMob,主要处理通讯中的一些全局事件,诸如好友请求、加群申请、消息推送、挤退下线等,首页这个类在整个生命周期中一直存在。代码如下

具体方法里的实现代码在官网Demo中也有所体现,按照自己项目的需求自行改动。有些方法并不是必须的,项目中我只用到了私聊和聊天室功能,群相关功能没有用到,所以加群请求等方法不需要实现。

这样一些配置就实现了,接下来进行功能开发。

登陆

1
2
3
4
5
6
7
8
9
10
[[EaseMob sharedInstance].chatManager 
asyncLoginWithUsername:response[@"username"]
password:response[@"password"]
completion:^(NSDictionary *loginInfo, EMError *error) {
if (!error) {

}else{
NSLog(@"error=%@",error);
}

} onQueue:nil]
;

这里我是在当前项目登陆之后调用的环信登陆,环信登陆成功之后才进入的首页。这样就有可能环信如果挂掉,就进入不到首页,停留在登录页面。可以先登陆本应用,进入首页之后再调用环信的登陆方法,这样也会遇到一个问题,进入首页后环信如果一直登陆不上去,那环信相关的功能也都使用不了。而且环信的登陆需要较强的网络,类似于登陆、视频聊天等还是有小部分几率是不成功的。使用第三方SDK这种问题难以避免,只能寄希望与它们的SDK能够给力点。

退出

1
2
3
4
5
6
7
[[EaseMob sharedInstance].chatManager 
asyncLogoffWithUnbindDeviceToken:YES
completion:^(NSDictionary *info, EMError *error){

[(AppDelegate *)[UIApplication sharedApplication].delegate goToLoginPage];

} onQueue:nil]
;

退出环信登陆后跳回登陆页面,其实还有个退出本应用的操作。这一步也会面临登陆同样的问题,即操作失败。这样在登陆的时候就需要先判断当前是否已登陆,如果已登陆则先退出再重新登录。

挤退下线

1
2
3
4
5
6
7
8
9
- (void)didLoginFromOtherDevice
{
[[EaseMob sharedInstance].chatManager asyncLogoffWithUnbindDeviceToken:NO completion:^(NSDictionary *info, EMError *error) {

[(AppDelegate *)[UIApplication sharedApplication].delegate goToLoginPage];
[PublicAlertUnit showNormalAlert:@")didLoginFromOtherDevice"];

} onQueue:nil];
}

didLoginFromOtherDevice这个方法官方有提供,是个长连接,一旦该账号在别的设备登陆就会调用这个方法。didRemovedFromServer这个方法是账号从服务器上被删除会调用的方法。

统计未读消息数

1
2
3
4
5
6
7
8
9
10
11
-(void)setupUnreadMessageCount
{
NSArray *conversations = [[[EaseMob sharedInstance] chatManager] conversations];

NSInteger unreadCount = 0;

for (EMConversation *conversation in conversations) {
if (conversation.conversationType == eConversationTypeChat) {
unreadCount += conversation.unreadMessagesCount;
}
}

拿到的会话有可能是群聊或聊天室,所以需要判断类型,这里只统计私聊

得到最后消息的时间

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
NSMutableArray *ret = nil;

NSArray* sorte = [conversations sortedArrayUsingComparator:
^(EMConversation *obj1, EMConversation* obj2){
EMMessage *message1 = [obj1 latestMessage];
EMMessage *message2 = [obj2 latestMessage];
if(message1.timestamp > message2.timestamp) {
return(NSComparisonResult)NSOrderedAscending;
}else {
return(NSComparisonResult)NSOrderedDescending;
}
}];

ret = [[NSMutableArray alloc] initWithArray:sorte];


if (ret.count != 0) {
EMConversation *conversation = ret[0];
time = [self lastMessageTimeByConversation:conversation];
}

-(NSString *)lastMessageTimeByConversation:(EMConversation *)conversation
{
NSString *ret = @"";
EMMessage *lastMessage = [conversation latestMessage];;
if (lastMessage) {
ret = [NSDate formattedTimeFromTimeInterval:lastMessage.timestamp];
}
return ret;
}

这里需要显示最后一条消息的时间,所以先把会话时间排序,再分别获取每个会话最后一条消息的时间

保存头像

1
2
3
4
5
NSString *chatter = conversation.chatter;
chatController = [[ChatViewController alloc] initWithChatter:chatter conversationType:conversation.conversationType];
chatController.delelgate = self;
chatController.title = title;
[self.navigationController pushViewController:chatController animated:YES];

环信并不处理用户头像,是为了减少对用户信息的入侵。所以用户头像需我们自己处理,这里用的是我们应用中用户的头像。根据用户id请求我门自己的后台链接,请求成功后将头像存储本地,一旦本地存储,则不再请求,只需要从中获取。在退出会话页则删除本地存储,原因是当前用户可能会更改头像,每次进入聊天页面都需要从新请求一次,也可以不删除本地头像存储,但在用户更换头像的时候更新本地存储。但是,你无法知道对方(你与之聊天的人)什么时候更换了头像,也有可能是在聊天过程中更换了头像,所以可以在发送消息的时候传入头像,接收方再解析显示。

透传消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EMChatCommand *cmdChat = [[EMChatCommand alloc] init];
EMCommandMessageBody *body = [[EMCommandMessageBody alloc] initWithChatObject:cmdChat];
EMMessage *message = [[EMMessage alloc] initWithReceiver:receiver bodies:@[body]];

NSDictionary *dic=@{@"BidId":[NSString stringWithFormat:@"%ld",self.bidID]};

message.ext = @{@"Customer Key":dic,
};

message.messageType = eMessageTypeChat;

[[EaseMob sharedInstance].chatManager asyncSendMessage:message progress:nil prepare:^(EMMessage *message, EMError *error) {

} onQueue:nil completion:^(EMMessage *message, EMError *error) {

}
} onQueue:nil];

透传消息有别于正常的收发消息,类似于推送,对方不在线的时候接收方法是didReceiveOfflineCmdMessages,对方在线接收方法是didReceiveCmdMessage。应用场景是我需要在双方聊天的时候传送一些商品信息,在聊天页上显示商品信息,所以就使用了透传,对方在接收消息后保存在本地,打开聊天页的时候需遍历本地数据库判断是否应该显示保存的商品信息。

加入聊天室

1
2
3
4
5
6
7
8
9
10
11
ChatViewController *chatPersonally = [[ChatViewController alloc]initWithChatter:roomID conversationType:eConversationTypeChatRoom];

_chatPersonally = chatPersonally;

chatPersonally.delelgate = self;

chatPersonally.view.frame = CGRectMake(0, 0, CGRectGetWidth(self.noDataChatView.frame), CGRectGetHeight(self.noDataChatView.frame));

[self.noDataChatView addSubview:chatPersonally.view];

[self addChildViewController:chatPersonally];
  • 用户可以同时加入多个聊天室
  • 退出聊天室聊天记录即清空
  • 服务器监听到当前用户不在线不会继续给此用户推送
  • 如果用户当前不在线,且聊天室有消息(则该用户进入聊天室后给推10条消息)
  • 进入聊天室后,杀掉程序,再次进入后记录依然存在(除非调用退出聊天室的方法,否则不算退出)
  • 会话列表页不显示聊天室信息

发送信息

相关方法在Demo有所体现,可自行查看。

Demo中ChatViewController包含的方法比较多,包括发送消息、解析消息、显示位置、语音、视频聊天等,可以直接将这个类拖至项目中使用,但要注意上下文使用环境,也可以自己布局UI,仅使用相关方法。

牵扯到聊天相关一般都比较繁杂,文档只起到辅助的作用,还是需要花时间去好好研究!


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

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

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

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

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

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

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

iOS开发如何集成并使用支付宝SDK

现在很多App都集成了支付宝SDK中的移动支付功能,但使用支付宝SDK的前提是购买的物品必须是和应用程序无关的商品,比如服装、电子产品等。如果是游戏币、会员等则必须使用内购,内购是用户将钱支付给苹果,之后苹果分成给商户。但即便是使用支付宝SDK购买真实的商品,在提交审核的时候依然可能被拒,这时候需要给苹果回复邮件阐述支付的流程。

如何集成并使用支付宝SDK呢?

需要先到 支付宝官网去申请签约,流程比较麻烦,不过有详细文档。签约成功后会获得商户ID(partner)和账户ID(seller)和私钥(私钥需根据公钥按照文档步骤生成)。

之后需前往 支付宝开放平台,注册需集成的应用,填写相关信息。

SDK下载页面: 点此前往

然后选择 SDK下载(注意:也有人使用CocoaPods集成SDK,但是CocoaPods上SDK不是最新的,所以支付时的回调会有问题,建议到官网下载)

下载完成后导入项目

kNotifyURL问后台获取,在支付成功或失败的时候会根绝这个回调同时通知后台。kPrivateKey这个私钥我存储在info.plist里面,也可以从后台获取。kAppScheme用来处理支付宝回调

Identifier可不填,URL Schemes可随意填写,但需和官网注册应用时填写的一样。

这里面的AlipayRequestConfig类官网Demo里并没有,是封装了一个支付方法类

在Appdelegate中别忘了加上下面代码,否则支付后无法跳转回来

在项目中的具体使用

为安全起见,在支付返回应用后要验证返回结果是否是支付宝服务器发来的信息,以帮助校验反馈回来的数据是否真实。

网上有个第三方库模仿支付宝支付的页面

CYPasswordView

在集成支付宝的时候也参考了网上的文章

2分钟快速集成支付宝快捷支付

以上!


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

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

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

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

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

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

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

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