如何发布自己的开源框架到CocoaPods

现在iOS开发过程中Cocoapods已经用的很普遍了,可以很方便的将第三方框架加到自己的项目中。现在也有个第三方的Cocoapods的插件,下载链接:https://github.com/ChinaFishNews/cocoapods-xcode-plugin.git 安装好后是这样的

还有个管理插件的插件Alcatraz,下载地址:https://github.com/ChinaFishNews/Alcatraz.git 安装好后是这样的

通过Alcatraz可以随意安装很多插件,但是一些插件和当前的Xcode版本有时并不兼容,会导致卡慢、崩溃或者不起作用。这时候可以随意卸载

目前安装了十多个插件,包括XAlign、Activate Power Mode、VVDocumenter、MacVim等十多个插件,感觉的确更便利于开发,更重要的是,不经意间一些很高大上的效果或动作在产品、测试面前装逼基本会得到满分。

如果我们想把自己写的组件或者库,甚至一个类开源出去,让别人也可以通过Cocoapods安装我们写的框架该怎么做呢?接下来和大家演示一下如何上传到Cocoapods上。

这里以我的一个Demo为例,cd到项目根目录,创建Podspec文件,UIImageDemo为项目名

1
pod spec create UIImageDemo

打开podspec文件基本是这样的

不需要填写的可以前面加个#注掉。

需要填写的大概有这几项
s.name ,是库的名称

s.version是库源代码版本号,项目提交到github上时需要给项目加个标签,这个标签要和这个 version一致,注意,版本号前面不需要加v

用到的命令是

1
2
3
4
5
git add .
git commit -m"test"
git tag
git push --tags
git push origin master

s.summary是对库的简要概述

s.homepage是库的主页,我在这里填写的是github的链接(https://github.com/ChinaFishNews/UIImageDemo.git)

s.license是许可文件,这里填”MIT”

s.author是库的作者

s.platform是库所支持的软件平台

s.source是声明原代码的地址,我这里也是填写的github链接,tag和上述的version要一致

s.source_files,声明了库中源代码的位置,前面的Classes可以去掉,我这里填的是”UIImageDemo/UIImageDemo/UIImageFactory.{.h,.m}”,{.h,m}匹配所有以.h和.m为扩展名的文件,我是把UIImageFactory这个类提交到Cocoapods上,项目目录如下所示

s.exclude_files 这里没有用到,注掉就好

s.framework是依赖的核心库,我这只用了UIKit,如果有用到其他的库,可以用s.frameworks = @”aaa.framework”,@”bbb.framework”

s.dependency是填写依赖的库,没有的话注释掉就好。

填写完后,保存退出,需要验证这个文件是否可用,有任何的warning或者errror都不可以,验证命令:

1
pod spec lint UIImageDemo.podspec

UIImageDemo换成你们自己的名字。我在这里尝试了多次都是失败,如下图所示

这个错是因为cocoapods不是最新版本,需更新到最新版本,如下图所示

步骤如下,先输入下面命令

1
gem sources -l

结果若为

1
2
3
4
gem sources -l
*** CURRENT SOURCES ***

http://ruby.taobao.org/

说明之前我替换过,若没有更改过gem source应该结果为

1
2
3
4
gem sources -l
*** CURRENT SOURCES ***

https://rubygems.org/

或者

1
2
3
4
$ gem sources -l
*** CURRENT SOURCES ***

http://rubygems.org/

现在淘宝ruby的源现在改成https的了,更换操作如下

移除操作(移除当前你查询到的gem source)

1
gem sources --remove http://ruby.taobao.org/

执行结果为

1
http://ruby.taobao.org/ removed from sources

添加操作

1
gem sources -a https://ruby.taobao.org/

执行结果为

1
https://ruby.taobao.org/ added to sources

再查询

1
gem sources -l

结果已经发生变化

1
2
3
*** CURRENT SOURCES ***

https://ruby.taobao.org/

然后更新

1
sudo gem install cocoapods

更新完毕,再次验证,没有错误,但是有个警告

1
pod spec lint UIImageDemo.podspec

这个警告是说没有找到许可文件,我随便从其他第三方库中找到了这个License文件导入到项目目录中

再次验证,UIImageDemo.podspec passed validation 终于验证通过

如果把它作为私有库来使用,可以使用pod 'ProjectName',:git=>"http://xxx.git"(把xxx替换为库的git地址)。这时候可以把podspec文件放到自己本机中,目录为~/.cocoapods/repos/master/Specs。

这时候并没有UIImagDemo文件夹,需要自己创建,也没有json文件,这是之后传到Cocoapods trunk之后才生成的文件。然后搜索这个库就已经有了

但是这个时候别人并不能通过Cocoapod搜索到这个库。2014年5月20日,CocoaPods不再接受向CocoaPods/Specs的pull request,官方的说法是为了安全考虑,防止每个人的pod被其他人修改,于是CocoaPods团队开发了trunk服务,这样每个人都是其发布的pod的owner,没有权限的人无法修改,这样更安全。在cocoapods使用了trunk服务后,这需要0.33以上的版本,可以通过pod –version查看当前版本,上文已讲如何更新cocoapods到最新版本。使用步骤如下

注册Trunk
1
pod trunk register your_email 'your_name' --description='macbook pro'

改成自己对应的邮箱和名字即可,会收到一封邮件,点击邮件链接验证,然后
可使用pod trunk me查看信息

如果项目是多人维护,可以添加其他维护者`pod trunk add-owner her_name her_email,不过我并没有尝试。

最后在工程根目录下执行pod trunk push命令。pod trunk push 命令会首先验证本地的podspec文件(是否有错误),之后会上传spec文件到trunk,最后会将你上传的podspec文件转换为需要的json文件。

如图,即上传成功了,整个过程就结束了。以后有新版本只需要修改工程根目录下的podspec文件就行了,然后重新执行pod trunk push命令。这时候别人就可以通过cocoapods使用你的库了。

新建一个项目,创建podfile文件,

1
2
3
platform :ios, "8.0"

pod 'UIImageDemo'

这时候使用pod install --verbose --no-repo-update并不会成功,因为这个命令 不会更新本地仓库,需要使用pod install才可以,下载到本地之后以后再次使用就可以使用`pod install --verbose --no-repo-update命令了。

如果想删除已经上传到Cocoapods上的库,只需前往/Users/xinwen/.cocoapods/repos/master/Specs 目录下找到对应名字的文件夹删除即可。我只是删除本地对应文件,但是pod search就搜索不到了,看来这应该是本地搜索。但是pod install 也安装不了,有些奇怪。在别人的电脑上,这个库对应的文件还在,还是可以继续使用,这时候如果不想别人继续使用,只能去github上把对应的源删了。关于删除这一点还有些许疑惑,后续有时间再留意下!

整个流程如下

  • 上传至github时,给项目打个tag

  • 到项目根目录创建podspec文件 命令:pod spec create name

  • 验证podsepc文件 命令:pod spec lint name.podspec

  • 注册pod trunk 命令:pod trunk register your_email 'your_name' --description='macbook pro

  • 发布到pod trunk 命令: pod trunk push

  • 更新pod库 命令:pod setup 如果pod trunk push成功后无法pod search到自己的库,需要执行该命令。

以上,流程参考了网上的一篇上传HUPhotoBrowser的文章,过程中遇到不少问题,但是也的确很受帮助,感谢!

还是得感慨一下,这写博客的时间比我自己操作的时间还要长,心塞!


如有任何疑问或问题请联系我: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周报,欢迎关注或订阅

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

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

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

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周报,欢迎关注或订阅

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

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

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

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程序后台无限运行

让我们的应用一直在后台运行不被系统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周报,欢迎关注或订阅

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

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

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

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

现在很多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周报,欢迎关注或订阅

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

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

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

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