怎样增大 Linux 系统的 open file(s) 上限

#
From: http://www.chengweiyang.cn/2015/11/14/how-to-enlarge-linux-open-files-upper-cell/

最近在工作中遇到一个问题,尝试直接将服务运行在高配(40core, 192GB;相比虚拟机来说) 的物理机上,但是发现服务打开的文件句柄达到 80 万左右就不能再开更多了。

80 万已经是一个不小的值了,通常情况下,Linux 默认的值都很小,例如:Debian 8(jessie) 给普通用户设置的 open file(s) 限制为 65536, 可以通过下面的命令查看当前限制。

$ ulimit -n
$ ulimit -Sn
$ ulimit -Hn

ulimit 是一个 shell(这里使用的是 bash) 内置命令,可以通过 type ulimit 验证。

-n 即表示查看或者设置 open file(s) 的限制,在 ulimit 中,每个限制都有两种类型:

  • -S, soft limit, 软限制,用户可以上调软限制到硬限制
  • -H, hard limit, 硬限制,非 root 用户不能修改

如果没有指明,则同时修改软限制和硬限制。

修改 ulimit

修改分为临时修改和永久修改,临时修改只对当前 session 有效,登出和重启后都恢复系统设置。

临时修改使用 ulimit 命令,以修改 open file(s) 为例。

# ulimit -n 1024000
# ulimit -n
1024000

永久修改需要修改 /etc/security/limits.conf 或者在 /etc/security/limits.d/ 目录下添加一个文件。具体格式参考 /etc/security/limits.conf,里面有详细说明。
注 新增

1
2
3
" * " 代表所有用户
* soft nofile 102400
* hard nofile 102400

open file(s) 上限

回到遇到的问题中来:服务打开 80 万个左右的文件句柄就不能再打开了。所以, 尝试将 ulimit 设置为 1000 万,结果提示出错:

# ulimit -n 10000000
-bash: ulimit: open files: cannot modify limit: Operation not permitted

注意,使用的可以 root 用户,居然没有权限,然后尝试降低到:

  • 500 万,依然错误
  • 300 万,依然错误
  • 200 万,依然错误
  • 100 万,成功了

显然,这里有一个上限,大概在 100-200 万之间。

所以,解决问题的办法,在于怎样提高这个上限!

通过一番搜索,发现 open file(s) kernel 级别有 2 个配置,分别是:

fs.nr_open,进程级别
fs.file-max,系统级别

fs.nr_open 默认设置的上限是 1048576,所以用户的 open file(s) 不可能超过这个上限。

# sysctl -w fs.nr_open=10000000
# ulimit -n 10000000
# ulimit -n
10000000

修改后即可设置更大的 open file(s) 了。

同样,对于 kernel 参数的修改,sysctl 命令修改的是当前运行时,如果需要永久修改, 则将配置添加到 /etc/sysctl.conf 中,例如:

# echo "fs.nr_open = 10000000" >> /etc/sysctl.conf
# echo "fs.file-max = 11000000" >> /etc/sysctl.conf

注意:fs.nr_open 总是应该小于等于 fs.file-max

如果要查看当前打开的文件数,使用下面的命令:

# sysctl fs.file-nr
fs.file-nr = 1760       0       11000000

不过,增大这些值意味着能够打开更多的文件(在 Linux 中,everything is file,包括 socket),但是同时也意味着消耗更多的资源,所以基本上在物理机上才会遇到这种问题。

IOS知识点-小技巧-小笔记(2)

xcode中,全局去掉项目warning的开头在Builder Setttings->Inhibit All Warnings(抵制所有的警告).当把它设置为Yes时,编译项目就不会出现warning警告了.
因为部分引入的第三方的项目 去掉警告


清理icon 角标 对于ios11 没有去测试

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
/**
刷新本地和服务器的图标 (不在appIcon上显示推送数量,但是在系统通知栏保留推送通知的方法)
@param noti <#noti description#>
*/
- (void)refreshPushBageValue:(NSNotification *)noti{
NSNumber *value = [noti object];
//
if(kiOS11Later){
/*
iOS 11后,直接设置badgeNumber = -1就生效了
*/
[UIApplication sharedApplication].applicationIconBadgeNumber = value.integerValue;
[JPUSHService setBadge:value.integerValue];
}else{
// 原理是 发送了一个本地推送 无消息的, 如果本地推送有处理 需要处理下
UILocalNotification *clearEpisodeNotification = [[UILocalNotification alloc] init];
clearEpisodeNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:(0.3)];
clearEpisodeNotification.timeZone = [NSTimeZone defaultTimeZone];
/// 根据这个消息 不处理本地
clearEpisodeNotification.alertTitle = @"清理icon";
clearEpisodeNotification.applicationIconBadgeNumber = value.integerValue;
[[UIApplication sharedApplication] scheduleLocalNotification:clearEpisodeNotification];
}
}

终端代理(临时方案)

1
2
3
在终端中执行以下代码, 1 为http 代理, 2 为全部代理 . ;电脑开启代理软件
export http_proxy=http://127.0.0.1:1087 当前临时方案, 关闭后就不走代理了
export all_proxy=socks5://127.0.0.1:1086 这个是临时 都走代理 不光http ,会很快

通过宏定义判断是否引入的是framework,反之则使用双引号,实用!

1
2
3
4
5
6
7
8
9
10
#if __has_include(<xxx/xxx.h>)
#import <xxx/xxx.h>
#else
#import "xxx.h"
#endif
#if __has_include(<GPUImage/GPUImageFramework.h>)
#import <GPUImage/GPUImageFramework.h>
#else
#import "GPUImage.h"

swift 打印内存地址

Unmanaged.passRetained(self as AnyObject)//这个引用计数会+1
Unmanaged.passUnretained(self as AnyObject) // 这个引用计数+0

如下输出 Unmanaged(_value: )


部分服务端更改, 验证了cookie app 中没有cookie 清除 造成服务端返回400 和413, 然后加入cookie 清理

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
URLCache.shared.removeAllCachedResponses()
let cookies = HTTPCookieStorage.shared.cookies
if cookies != nil && cookies!.count>0{
for cookie in cookies!{
HTTPCookieStorage.shared.deleteCookie(cookie)
}
}
//iOS9.0以上使用的方法
if #available(iOS 9.0, *) {
let dataStore = WKWebsiteDataStore.default()
dataStore.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), completionHandler: { (records) in
for record in records{
//清除本站的cookie
if record.displayName.contains("sina.com"){//这个判断注释掉的话是清理所有的cookie
WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {
//清除成功
print("清除成功\(record)")
})
}
}
})
} else {
//ios8.0以上使用的方法
let libraryPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
let cookiesPath = libraryPath! + "/Cookies"
try!FileManager.default.removeItem(atPath: cookiesPath)
}

1 宏
1.1 has_include
用于判断是否包含某些头文件 例如:
`#if
has_include()`

1.2 NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END
两者搭配使用,在这两个宏之间的所有函数变量都是不可空。如果在这两个宏之间,想要可空的函数变量,需要单独设置 nullable关键字。

1.3 UNAVAILABLE_ATTRIBUTE
不可用。在方法或者属性 加上这个宏之后。将变成不可用。

1.4 UI_APPEARANCE_SELECTOR
加到属性后面,所有该属性的实例都统一设置。


IPv6 问题:
1 使用域名
2 手动转换
假设访问http://67.218.154.33
转换为ipv6 形式 : http://[::ffff:67.218.154.33]

注 找了一个解释:https://www.jianshu.com/p/1312e98cd35b

—- 获取类名—
oc

1
2
3
4
/// switf 代码会带程序名
NSString *selfClassName = NSStringFromClass([self class]);
/// 去除程序名
selfClassName = [selfClassName componentsSeparatedByString:@"."].lastObject;

swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 返回内部类名
print("class: \(object_getClassName(self))")
// 返回应用程序名+类名
print("class: \(NSStringFromClass(self.dynamicType))")
// 返回应用程序名+类名,并去掉应用程序名
print("class: \(NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!)")
// 返回应用程序名+类名+内存地址
print("class: \(self)")
// 返回应用程序名+类名+内存地址
print("class: \(self.description)")
// 返回类名
print("class: \(self.dynamicType)")

Swizzle touchesBegan:withEvent:事故

得实现override touches 方法原因见
地址


ios 设备目录获取

1
2
3
4
5
6
7
8
9
10
// 获取沙盒主目录路径
NSString *homeDir = NSHomeDirectory();
// 获取Documents目录路径
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
// 获取Library的目录路径
NSString *libDir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
// 获取Caches目录路径
NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
// 获取tmp目录路径
NSString *tmpDir = NSTemporaryDirectory();

程序目录

1
2
3
NSLog(@"%@",[[NSBundle mainBundle] bundlePath]);
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"apple" ofType:@"png"];
UIImage *appleImage = [[UIImage alloc] initWithContentsOfFile:imagePath];

libstdc++适配Xcode10与iOS12

原因是苹果在XCode10和iOS12中移除了libstdc++这个库,由libc++这个库取而代之,苹果的解释是libstdc++已经标记为废弃有5年了,建议大家使用经过了llvm优化过并且全面支持C++11的libc++库。

beta 版本的xcode 10 拷贝下xcode9 的文件(注 模拟器 还是崩溃)

1
2
3
cp /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libstdc++.* /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/
cp /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/libstdc++.* /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/

根本解决办法:

如果你自己的业务模块使用了libstdc++,那么就把模块代码重新调整为依赖libc++,然后重新检查是否存在问题,重新编译
如果你引用的三方库使用了libstdc++,那么向三方库寻求支持,进行升级


查看应用初始化时间
在Xcode中,可以通过设置环境变量来查看App的启动时间,DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS。(这个是更详细的)
如图设置:控制台会输出时间

一个App在执行main函数前包括app delegate的系列方法如applicationWillFinishLaunching时,会做许多系统级别的准备.而在iOS10之前,开发者很难清楚自己App为何启动加载慢.而通过在工程的scheme中添加环境变量DYLD_PRINT_STATISTICS,设置Value为1,App启动加载时就会有启动过程的日志输出. 现在(iOS 10之后)Apple对DYLD_PRINT_STATISTICS的日志输出结果进行了简化,使得更容易让开发者理解.


iOS开发之使用P3图片导致崩溃的解决方法

最近app刚上架,突然收到大面积投诉….一看bugly,9.0-9.3的机器无一幸免,由于项目里有些图标是我直接从阿里图库下载的,问了UI P3,16进制的图片是什么他也说不清,索性让他重新做图了,这个问题只要图片是UI做图基本就可避免

1.打包成ipa

2.把ipa的后缀改成zip,解压缩(这时候会看到一个Payload文件夹)

3.打开终端 输入 cd

4.把 Payload 拖动到终端里(这里的拖动只是为了获取这个文件在电脑上的地址), 回车

5.在终端输入 find . -name ‘Assets.car’ 回车(会输出找到的位置)

6.在终端输入 sudo xcrun –sdk iphoneos assetutil –info ./Assets.car > /tmp/Assets.json 回车
(car 地址可以根据上面找到的位置填入)

7.在终端输入 open /tmp/Assets.json 回车

8.这时候会打开一个text 搜索 DisplayGamut 看看后面是不是P3 如果搜索到的是p3 图片格式还是不对,如果是空或者搜索到显示的不是P3,那图片就对了,根据Name去查找项目里的这张图片吧,然后将其替换.

转自点击


UIView 设置单边圆角

1
2
3
4
5
6
7
8
9
10
11
/// 设置单边圆角
private func setMaskLayer(){
let corner = self.height/2
let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: [UIRectCorner.bottomLeft,UIRectCorner.topLeft], cornerRadii: CGSize.init(width: corner, height: corner));
let maskLayer = CAShapeLayer.init()
maskLayer.frame = self.bounds
maskLayer.path = maskPath.cgPath
self.layer.mask = maskLayer;
}

jsonp 转json 正则

str.match(“.?({.}).*”)返回数组第一个


记录一个逗号分隔用法

1
2
3
4
5
6
7
// 多值的设置,使用逗号分隔
// 注意:if let语句中不能使用&& || 条件
// if let中只要有任何一个条件为nil,就跳出循环
if let name = oName, age = oAge {
print("Hi~" + name + "年龄:" + String(age))
}

IOS脚本打包 IPA(.APP转.IPA)

将要转化的.app文件放到 convertToIpa.sh 同目录之中

运行 convertToIpa.sh 脚本

打开 Terminal,cd 到 convertToIpa.sh 的目录,执行

./convertToIpa.sh appName(.app 的名字)

如果提示 permission denied,则用 chmod 777 distribute.sh 命令赋予权限后,再执行一次。

等脚本之行结束后,会在当前文件夹下生成 appName 文件夹,里面的 appName.ipa 就是我们最终想要的包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
mkdir $1
mkdir $1/Payload
cp -r $1.app $1/Payload/$1.app
cp Icon.png $1/iTunesArtwork
cd $1
zip -r $1.ipa Payload iTunesArtwork
exit 0

xcode 编译线程数

1.获取当前内核数:
$ sysctl -n hw.ncpu
2.设置编译线程数:
$ defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks 8
3.获取编译线程数:
$ defaults read com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks
4.显示编译时长:

$ defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES

iOS12.1 使用 UINavigationController + UITabBarController( UITabBar 磨砂),设置hidesBottomBarWhenPushed后,在 pop 后,会引起TabBar布局异常

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
// .h
@interface CYLTabBar : UITabBar
@end
// .m
#import "CYLTabBar.h"
/**
* 用 block 重写某个 class 的指定方法
* @param targetClass 要重写的 class
* @param targetSelector 要重写的 class 里的实例方法,注意如果该方法不存在于 targetClass 里,则什么都不做
* @param implementationBlock 该 block 必须返回一个 block,返回的 block 将被当成 targetSelector 的新实现,所以要在内部自己处理对 super 的调用,以及对当前调用方法的 self 的 class 的保护判断(因为如果 targetClass 的 targetSelector 是继承自父类的,targetClass 内部并没有重写这个方法,则我们这个函数最终重写的其实是父类的 targetSelector,所以会产生预期之外的 class 的影响,例如 targetClass 传进来 UIButton.class,则最终可能会影响到 UIView.class),implementationBlock 的参数里第一个为你要修改的 class,也即等同于 targetClass,第二个参数为你要修改的 selector,也即等同于 targetSelector,第三个参数是 targetSelector 原本的实现,由于 IMP 可以直接当成 C 函数调用,所以可利用它来实现“调用 super”的效果,但由于 targetSelector 的参数个数、参数类型、返回值类型,都会影响 IMP 的调用写法,所以这个调用只能由业务自己写。
*/
CG_INLINE BOOL
OverrideImplementation(Class targetClass, SEL targetSelector, id (^implementationBlock)(Class originClass, SEL originCMD, IMP originIMP)) {
Method originMethod = class_getInstanceMethod(targetClass, targetSelector);
if (!originMethod) {
return NO;
}
IMP originIMP = method_getImplementation(originMethod);
method_setImplementation(originMethod, imp_implementationWithBlock(implementationBlock(targetClass, targetSelector, originIMP)));
return YES;
}
@implementation CYLTabBar
+ (void)load {
/* 这个问题是 iOS 12.1 Beta 2 的问题,只要 UITabBar 是磨砂的,并且 push viewController 时 hidesBottomBarWhenPushed = YES 则手势返回的时候就会触发。
出现这个现象的直接原因是 tabBar 内的按钮 UITabBarButton 被设置了错误的 frame,frame.size 变为 (0, 0) 导致的。如果12.1正式版Apple修复了这个bug可以移除调这段代码(来源于QMUIKit的处理方式)*/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (@available(iOS 12.1, *)) {
OverrideImplementation(NSClassFromString(@"UITabBarButton"), @selector(setFrame:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP originIMP) {
return ^(UIView *selfObject, CGRect firstArgv) {
if ([selfObject isKindOfClass:originClass]) {
// 如果发现即将要设置一个 size 为空的 frame,则屏蔽掉本次设置
if (!CGRectIsEmpty(selfObject.frame) && CGRectIsEmpty(firstArgv)) {
return;
}
}
// call super
void (*originSelectorIMP)(id, SEL, CGRect);
originSelectorIMP = (void (*)(id, SEL, CGRect))originIMP;
originSelectorIMP(selfObject, originCMD, firstArgv);
};
});
}
});
}
@end

来源 https://github.com/ChenYilong/iOS12AdaptationTips/issues/3


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pod 'AFNetworking' //不显式指定依赖库版本,表示每次都获取最新版本
pod 'AFNetworking', '~>0' //高于0的版本,写这个限制和什么都不写是一个效果,都表示使用最新版本
pod 'AFNetworking', '~> 0.1.2' //使用大于等于0.1.2但小于0.2的版本
pod 'AFNetworking', '~>0.1' //使用大于等于0.1但小于1.0的版本
pod 'AFNetworking', '2.0' //只使用2.0版本
pod 'AFNetworking', '= 2.0' //只使用2.0版本
pod 'AFNetworking', '> 2.0' //使用高于2.0的版本
pod 'AFNetworking', '>= 2.0' //使用大于或等于2.0的版本
pod 'AFNetworking', '< 2.0' //使用小于2.0的版本
pod 'AFNetworking', '<= 2.0' //使用小于或等于2.0的版本
pod 'AFNetworking', :git => 'http://gitlab.xxxx.com/AFNetworking.git', :branch => 'R20161010' //指定分支
pod 'AFNetworking', :path => '../AFNetworking' //指定本地库


1
2
3
4
// //排序
// NSSortDescriptor *isDefault = [NSSortDescriptor sortDescriptorWithKey:@"isDefault" ascending:NO];
// NSSortDescriptor *isUse = [NSSortDescriptor sortDescriptorWithKey:@"isOnlyUse" ascending:NO];
// [self.dataArray sortUsingDescriptors:@[isDefault,isUse]];

系统装 carthage 造成的终端编译ipa找不到编辑器

1
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/

——node link— 失败问题

Could not symlink share/doc/node/gdbinit
Target /usr/local/share/doc/node/gdbinit
already exists. You may want to remove it:
rm ‘/usr/local/share/doc/node/gdbinit’

解决
1 sudo chown -R $USER /usr/local
2 brew link –overwrite node


xcode 打包多taget 版本号自动同步
添加 Shell 脚本; 在Xcode Build Phases -> 添加 Run Script;
注: 会影响打包, 直接自动打包shell 脚步直接处理, 方法同样

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
# Type a script or drag a script file from your workspace to insert its path.
if [ $CONFIGURATION == Release ]; then
echo "Bumping build number..."
plist=${INFOPLIST_FILE}
buildnum=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${plist}")
if [[ "${buildnum}" == "" ]]; then
echo "No build number in $plist"
exit 2
fi
echo "Bumped build number to $buildnum"
buildnum=$(expr $buildnum + 1)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildnum" "${INFOPLIST_FILE}"
echo "Update build number to Current Project Version"
agvtool new-version -all $buildnum
echo "Keep Extension Target build version and number as same as app"
buildver=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${plist}")
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $buildver" "$SRCROOT/$ZBiOSNotificationService/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $buildver" "$SRCROOT/$ZBiOSNotificationService/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildnum" "$SRCROOT/$ZBiOSNotificationService/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildnum" "$SRCROOT/$ZBiOSNotificationService/Info.plist"
else
echo $CONFIGURATION "build - Not bumping build number."
fi

时间的一个处理

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
// 获取代表公历的NSCalendar对象
let gregorian = Calendar(
identifier: .gregorian)
// 获取当前日期
let dt = Date()
// 定义一个时间字段的旗标,指定将会获取指定年、月、日、时、分、秒的信息
// let unitFlags: NSCalendar.Unit = [.year, .month, .day, .hour, .minute, .second, .weekday]
// 获取不同时间字段的信息
let comp = gregorian.dateComponents(
[.year, .month, .day, .hour, .minute, .second, .weekday],
from: dt)
// 获取各时间字段的数值
// 再次创建一个NSDateComponents对象
var comp2 = DateComponents()
// 设置各时间字段的数值
comp2.year = comp.year
comp2.month = comp.month
comp2.day = comp.day
comp2.hour = comp.hour
comp2.minute = 34
// 通过NSDateComponents所包含的时间字段的数值来恢复NSDate对象
let date = gregorian.date(from: comp2)
if let date = date {
print("获取的日期为:\(date)")
}
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
// 获取代表公历的NSCalendar对象
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
// 获取当前日期
NSDate* dt = [NSDate date];
// 定义一个时间字段的旗标,指定将会获取指定年、月、日、时、分、秒的信息
unsigned unitFlags = NSCalendarUnitYear |
NSCalendarUnitMonth | NSCalendarUnitDay |
NSCalendarUnitHour | NSCalendarUnitMinute |
NSCalendarUnitSecond | NSCalendarUnitWeekday;
// 获取不同时间字段的信息
NSDateComponents* comp = [gregorian components: unitFlags
fromDate:dt];
// 获取各时间字段的数值
NSLog(@"现在是%ld年" , comp.year);
NSLog(@"现在是%ld月 " , comp.month);
NSLog(@"现在是%ld日" , comp.day);
NSLog(@"现在是%ld时" , comp.hour);
NSLog(@"现在是%ld分" , comp.minute);
NSLog(@"现在是%ld秒" , comp.second);
NSLog(@"现在是星期%ld" , comp.weekday);
// 再次创建一个NSDateComponents对象
NSDateComponents* comp2 = [[NSDateComponents alloc]
init];
// 设置各时间字段的数值
comp2.year = 2013;
comp2.month = 4;
comp2.day = 5;
comp2.hour = 18;
comp2.minute = 34;
// 通过NSDateComponents所包含的时间字段的数值来恢复NSDate对象
NSDate *date = [gregorian dateFromComponents:comp2];
NSLog(@"获取的日期为:%@" , date);


pod 组件,使用时, 头文件报 重复导入,使用检查头文件的方式导入

1
2
3
4
5
#if __has_include(<ShareSDK/ShareSDK.h>)
#import <ShareSDK/ShareSDK.h>
#else
#endif

格式化数据

1
2
3
4
5
6
7
8
NSLog(@"%02ld",2);
NSLog(@"%0.2f",0.2656);
NSLog(@"%0.2f",0.2646);
注意的是%0.2f 是会对数字进行一个四舍五入
14:57:28.506 App[4010:98217] 02
2016-06-20 14:57:28.507 App[4010:98217] 0.27
2016-06-20 14:57:28.507 App[4010:98217] 0.26

IOS知识点-小技巧-小笔记(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 保存到相册的老的方法 注意回调函数的写法 必须固定格式
let saveBool = UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(newURL!.path)
if saveBool == true{
// UISaveVideoAtPathToSavedPhotosAlbum(newURL!.path, self, nil, nil)
UISaveVideoAtPathToSavedPhotosAlbum(newURL!.path, self, #selector(self.image(image:didFinishSavingWithError:contextInfo:)), nil)
}
func image(image: UIImage, didFinishSavingWithError error: NSErrorPointer, contextInfo:UnsafeRawPointer) {
if error == nil {
printLog("保存成功")
} else {
printLog("保存失败\(error)")
}
}
1
2
3
4
5
6
//跳转到appstore
// let str = "http://itunes.apple.com/cn/app/id966492118?mt=8" // 可直接跳转到appstore
// let str = "itms://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=966492118" //跳转到itunes Store 也会定位到应用
let str = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=966492118"//跳转到评分页 appstore
let url = URL(string: str)
UIApplication.shared.openURL(url!)

忽略 “Undeclared selector…” 的 Warning

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (_delegate && [_delegate respondsToSelector:@selector(retrievingProgressMP4:)])
{
[_delegate performSelector:@selector(retrievingProgressMP4:) withObject:[NSNumber numberWithFloat:_exportSession.progress]];
// NSLog(@"Effect Progress: %f", exportSession.progress);
}
// 方法没有显示声明 ,会报一个警告, 可以这样局部去除, 局部去除比较合适
//忽略 "Undeclared selector..." 的 Warning
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
// 需要禁用警告的代码
#pragma clang diagnostic pop

将GIF图片分解成多张PNG图片,使用UIImageView播放。

需要导入#import

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"<#gifName#>" withExtension:@"gif"]; //加载GIF图片
CGImageSourceRef gifSource = CGImageSourceCreateWithURL((CFURLRef) fileUrl, NULL); //将GIF图片转换成对应的图片源
size_t frameCout = CGImageSourceGetCount(gifSource); //获取其中图片源个数,即由多少帧图片组成
NSMutableArray *frames = [[NSMutableArray alloc] init]; //定义数组存储拆分出来的图片
for (size_t i = 0; i < frameCout; i++) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(gifSource, i, NULL); //从GIF图片中取出源图片
UIImage *imageName = [UIImage imageWithCGImage:imageRef]; //将图片源转换成UIimageView能使用的图片源
[frames addObject:imageName]; //将图片加入数组中
CGImageRelease(imageRef);
}
UIImageView *gifImageView = [[UIImageView alloc] initWithFrame:CGRectMake(<#x#>, <#y#>, <#w#>, <#h#>)];
gifImageView.animationImages = frames; //将图片数组加入UIImageView动画数组中
gifImageView.animationDuration = 0.15; //每次动画时长
[gifImageView startAnimating]; //开启动画,此处没有调用播放次数接口,UIImageView默认播放次数为无限次,故这里不做处理
[self.view addSubview:gifImageView];

// 视频中获取图片 视频截取图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 正常mp4 好获取,不说 hls 渐进式加载, 理论我觉得seek 到位置 然后在获取 或者获取当前
CMTime itemTime = self.playerItem.currentTime;
CVPixelBufferRef pixelBuffer = [_playerItemVideoOutput copyPixelBufferForItemTime:itemTime itemTimeForDisplay:nil];
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
CIContext *temporaryContext = [CIContext contextWithOptions:nil];
CGImageRef videoImage = [temporaryContext
createCGImage:ciImage
fromRect:CGRectMake(0, 0,
CVPixelBufferGetWidth(pixelBuffer),
CVPixelBufferGetHeight(pixelBuffer))];
UIImage *uiImage = [UIImage imageWithCGImage:videoImage];
CGImageRelease(videoImage);
NSLog(@"uiImage:%@", uiImage);
self.myImgView.image = uiImage;
分析得到:copyPixelBufferForItemTime这个方法需要的参数是当前avplayerItem的cmtime, 也就是说前提是正在播放视频流,不知道有没有理解错。

视频播放时候如何在静音模式下有声音

1
2
3
4
5
6
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
//swift
let audioSession = AVAudioSession.sharedInstance()
try? audioSession.setCategory(AVAudioSessionCategoryPlayback);

// 简单保存视频到相册 —

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let filepath = GetDownFilePath(fileName).path
printLog("测试保存地址 \(filepath)")
if ( UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(filepath) == true){
UISaveVideoAtPathToSavedPhotosAlbum(filepath, self, #selector(self.video(videoPath:didFinishSavingWithError:contextInfo:)), nil);
}
func video(videoPath: String, didFinishSavingWithError error: NSError?, contextInfo info: AnyObject) {
}
////UIImageWriteToSavedPhotosAlbum 保存函数的通知处理函数
func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
if error == nil{
MBManage.shareMode.showBriefHUD("保存成功")
}else{
MBManage.shareMode.showBriefHUD("保存失败")
}
}

view 层级调整 ,互换 层最上层等

1
2
3
4
5
6
7
8
9
//将view1挪到最上边
self.view.bringSubviewToFront(view1)
//将view1挪到最下边
self.view.sendSubviewToBack(view1)
//互换
self.view.exchangeSubviewAtIndex(2, withSubviewAtIndex: 3)

swift 3 继承变量问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 父类
/// 基类 是否支持旋转 默认false 不旋转
open var isRotate:Bool = false
/// 子类
override var isRotate: Bool {
get{
// return super.isRotate // 可设置
return true// 直接改成需要的true
}
set{
super.isRotate = newValue
}
}

是否显示状态栏

1
2
3
4
5
//在info.plist中添加
//View controller-based status bar appearance
//并且把值设定为NO,就可以在程序中自由控制状态栏的隐藏和显示了。
UIApplication.shared.setStatusBarHidden(false, with: UIStatusBarAnimation.none)

navigationController 自带边缘右滑 退出, 代理 可以设置启用和不启用 UIGestureRecognizerDelegate

1
2
3
4
5
6
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
//代理
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}

iOS获取数组的最大值

1
2
3
4
5
6
7
NSMutableArray* array = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
CGFloat num = arc4random() % 100 + 1;
[array addObject:[NSNumber numberWithFloat:num]];
}
CGFloat maxValue = [[array valueForKeyPath:@"@max.floatValue"] floatValue];
CGFloat minValue = [[array valueForKeyPath:@"@min.floatValue"] floatValue];

重点在这句话上
@”@max.floatValue”(获取最大值),
@”@min.floatValue”(获取最小值),
@”@avg.floatValue” (获取平均值),
@”@count.floatValue”(获取数组大小)
等等。。。。


oc通过强制类型转换四舍五入。swift 同理可行

1
2
3
4
float f = 1.5;
int a;
a = (int)(f+0.5);
NSLog("a = %d",a);

TabBarViewController 和UINavigationController的bar 上的黑线

1
2
3
let tabFrame = CGRect(x: 0, y: 0, width: stageWidth, height: self.tabBar.frame.height)
self.tabBar.backgroundImage = Getdevice.shared().image(withFrame: tabFrame, alphe: 1.0)
self.tabBar.shadowImage = UIImage()

UINavigationController 中的navigationBar 同理


iOS 字符串 中包含 % 百分号的方法

百分号的转换,NSString中需要格式化的字符串中百分号使用%%表示,而char*中百分号也是使用%%表示。

例如:NSLog(@”%%%@%%”,@”hello”),控制台会打印出%hello%。


cell 选择按钮替换

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
/// 用户发布的视频显示 cell
class UserVideoManagerTableViewCell: UITableViewCell {
var showView:ShowVideoTableCellView!
override func layoutSubviews() {
super.layoutSubviews()
if #available(iOS 8 , *){
// ios7 没效果 第一次也不出现,还是得子定义
for control in self.subviews{
if control.isMember(of: NSClassFromString("UITableViewCellEditControl")!){
for v in control.subviews{
if v.isKind(of: UIImageView.classForCoder()){
//var img = v as! UIImageView
// v = UIImageView(image: UIImage(named: "activity_close"))
if self.isSelected{
(v as! UIImageView).image = UIImage(named: "v2_4_btn_set_selected")
}else{
//(v as! UIImageView).image = UIImage(named: "activity_new")//必须原先的图 因为效果不能一直有
}
}
}
}
}
}else{
for view in self.subviews{
if view.isMember(of: NSClassFromString("UITableViewCellScrollView")!){
printLog("views \(view.subviews)")
let views2 = view.subviews
for control in views2{
if control.isMember(of: NSClassFromString("UITableViewCellEditControl")!){
for v in control.subviews{
if v.isKind(of: UIImageView.classForCoder()){
let img = v as! UIImageView
if self.isSelected{
img.image = UIImage(named: "v2_4_btn_set_selected")
}else{
//img.image = UIImage(named: "activity_new")//必须原先的图 因为效果不能一直有
}
}
}
}
}
}
}
}
}
}

下面的方法本地还在
github 删除错误提交

git reset –hard HEAD~1
git push –force
就会删除远程的提交

1
2
3
git reset --hard <commit_id>
git push origin HEAD --force

其他:

1
2
3
4
5
6
7
8
9
10
11
根据–soft –mixed –hard,会对working tree和index和HEAD进行重置:
git reset –mixed:此为默认方式,不带任何参数的git reset,即时这种方式,它回退到某个版本,只保留源码,回退commitindex信息
git reset –soft:回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可
git reset –hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容
HEAD 最近一个提交
HEAD^ 上一次
<commit_id> 每次commitSHA1值. 可以用git log 看到,也可以在页面上commit标签页里找到.
commit合并:

使用Cocoapods时忽略Xcode警告
我使用相当多的第三方库,其中有很多警告,在最新的Xcode更新后。 (例如Facebook SDK pod)
现在所有这些警告都显示在我的Xcode上我想看到自己的警告或错误的地方。
有没有办法忽略这些错误?修复它们不会有帮助,因为在每个“pod安装”之后,更改被丢弃。
添加到您的Podfile:

1
2
3
4
5
6
7
platform :ios
# ignore all warnings from all pods
inhibit_all_warnings!
# ignore warnings from a specific pod
pod 'FBSDKCoreKit', :inhibit_warnings => true

然后执行:pod install


链接
down vote
As it has already been answered, ObjC doesn’t support method overloading (two methods with the same name) and In swift 2 under Xcode 7 there are two options to solve this kind of problems. One option is to rename the method using the attribute: @objc(newNameMethod:)

func methodOne(par1, par2) {…}

@objc(methodTwo:) 标记为objc
func methodOne(par1) {…}
another option to solve this problem in Xcode 7+ is by applying @nonobjc attribute to any method, subscript or initialiser

func methodOne() {…}

@nonobjc // 标记为非objc
func methodOne() {…}


//NSRange转化为range
extension String {
func range(from nsRange: NSRange) -> Range? {
guard
let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
let from = String.Index(from16, within: self),
let to = String.Index(to16, within: self)
else { return nil }
return from ..< to
}
}

//range转换为NSRange
extension String {
func nsRange(from range: Range) -> NSRange {
let from = range.lowerBound.samePosition(in: utf16)
let to = range.upperBound.samePosition(in: utf16)
return NSRange(location: utf16.distance(from: utf16.startIndex, to: from),
length: utf16.distance(from: from, to: to))
}
}

swift 4

NSRange(range, in: string) //return 这个 range 转NSRange

/// string 中 查找子串
extension String {
func nsranges(of string: String) -> [NSRange] {
return ranges(of: string).map { (range) -> NSRange in
self.nsrange(fromRange: range)
}
}
}

// range 转 NSRange
extension String {
func nsrange(fromRange range : Range) -> NSRange {
return NSRange(range, in: self)
}
}

链接:
链1 链2


1
2
3
4
5
6
7
8
9
10
// kvc 实现提示 TextView 的提示文本(私有api)
self.placeHolderLabel = UILabel()
self.placeHolderLabel.font = GlobalFont_32
self.placeHolderLabel.textColor = UIColor.lightGray
self.placeHolderLabel.text = "请输入内容"
self.placeHolderLabel.sizeToFit()
self.inputeTextView.addSubview(self.placeHolderLabel)
self.inputeTextView.setValue(self.placeHolderLabel, forKey: "_placeholderLabel")
或者用titleTextField.attributedPlaceholder 参数

—YYlabel-YY–

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
// 一个yylabel 搞点点击处理
func setData(mode:MJ_UserMessage){
self.mode = mode
let titleString = "\(mode.source_title) 赞了你的视频"
let attrText = NSMutableAttributedString(string: titleString)
attrText.yy_font = GlobalFont_32
attrText.yy_color = GlobalMainToneTextColor_lime
let fromUserRange = NSMakeRange(0, mode.source_title.characters.count)
let fromUserHighlight = YYTextHighlight.init(backgroundColor: UIColor.clear)// 高亮的背景色 先没有
fromUserHighlight.userInfo = ["MJ_UserMessage":mode];// 把评论者信息存储到userinfo中
attrText.yy_setTextHighlight(fromUserHighlight, range: fromUserRange)
attrText.yy_setColor(UIColor(red:0.90, green:0.30, blue:0.25, alpha:1.00), range: fromUserRange)
self.messageNameLabel.attributedText = attrText
self.messageNameLabel.sizeToFit() //YYLabel
self.messageNameLabel.highlightTapAction = { [weak self](containerView:UIView ,text:NSAttributedString ,range:NSRange ,rect:CGRect) in
if let weakSelf = self{
let highlight:YYTextHighlight = containerView.value(forKeyPath: "_highlight") as! YYTextHighlight
let mode = highlight.userInfo?["MJ_UserMessage"] as? MJ_UserMessage
/// 代理吧数据传出去 (被评论人无user ImageUrl 字段)
if mode == nil || mode?.source_type != "user"{return}
DispatchQueue.main.async {
let controller = MyMainPageViewController(NavTitle: mode!.source_ID, userId: mode!.source_title)
weakSelf.findController().navigationController?.pushViewController(controller, animated: true)
}
}
}
}

ios 防止锁屏

1
UIApplication.shared.isIdleTimerDisabled = true // 长亮 反之正常

一个oc警告忽略
忽略 “Undeclared selector…” 的 Warning

1
2
3
4
if ([someObject respondsToSelector:@selector(someSelector)])
{
[someObject performSelector:@selector(someSelector)];
}

以上这句代码,除非你在 someObject 的头文件中显式地声明了 someSelector,否则在 Xcode 中会提示警告:
Undeclared selector ‘someSelector’
但很多情况下我们并不想去声明它,此时我们可以禁用编译器的此类警告:

1
#pragma GCC diagnostic ignored "-Wundeclared-selector"

这样将会在整个文件内禁用此类警告,也可只在部分代码处禁用,保证编译器依然会对文件内其他代码进行警告检测,避免出现预料之外的 bug:

1
2
3
4
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
// 需要禁用警告的代码
#pragma clang diagnostic pop

记录事件, GPUImage 运行时 崩溃 8.3 系统上 ,只有debug 连着电脑运行才崩溃 , 只要正常安装正常


Provisioning Profile相关
在~/Library/MobileDevice/Provisioning Profiles目录可以看到本机所有的Provisioning Profile,但是这里显示都是.mobileprovision这样的文件,并不是很好辨认。
一般更新设备删除~/Library/MobileDevice/Provisioning
在xcode 重新下载


ios 苹果的测试 The TestFlight app 中测试 可以测试正式环境的推送


xcode 代码块 导出到其他电脑使用
cd /Users/用户名/Library/Developer/Xcode/UserData/

将CodeSnippets拷贝到新电脑的对应的目录下


swift 4 字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
So, change
text.substring(to: 3)
text.substring(from: 3)
to
String(text.prefix(3))
String(text[text.index(text.startIndex, offsetBy: 3)...])
let newStr = str.substring(to: index) // Swift 3
let newStr = String(str[..<index]) // Swift 4
let newStr = str.substring(from: index) // Swift 3
let newStr = String(str[index...]) // Swift 4
let range = firstIndex..<secondIndex // If you have a range
let newStr = = str.substring(with: range) // Swift 3
let newStr = String(str[range]) // Swift 4

详情


根据字符串进行类的实例化 NSClassFromString 使用

1
2
3
4
5
6
7
//oc
Class vc_class = NSClassFromString(class_name);
//swift
let classtest = NSClassFromString("项目名.类名") as? 类名.Type
classtest?.init()
printLog("测试 \(classtest?.init())")

— 代码行数统计——
项目目录终端执行
其中 -name “*.m” 就表示扩展名为.m的文件。

1
find . "(" -name "*.m" -or -name "*.mm" -or -name "*.swift" -or -name "*.cpp" -or -name "*.h" -or -name "*.rss" ")" -print | xargs wc -l

swift KVO 需要的条件

swift 4 得 @objc dynamic
前缀 基于运行时,动态变量,
一般在swift 中用自动布局SDAutoLayout 自动cell 高度 需要使用这个


一个swift 闭包内存处理

1
2
3
4
5
6
7
8
9
lazy var newBtn: UIButton = {
// 可以直接self 因为lazy 自动处理了 (其他的一些 didSet 等 同理)
let obj = UIButton();
obj.addAction({ [weak self](btn) in
// 但是这里还是得标记
self?.delegate?.xxx
})
return obj;
}()


player 播放器网络差处理 摘取自CLPlayerDemo

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
#pragma mark - 监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"status"]) {
if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
self.state = CLPlayerStatePlaying;
self.player.muted = self.mute;
}
else if (self.player.currentItem.status == AVPlayerItemStatusFailed) {
self.state = CLPlayerStateFailed;
}
} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
// 计算缓冲进度
NSTimeInterval timeInterval = [self availableDuration];
CMTime duration = self.playerItem.duration;
CGFloat totalDuration = CMTimeGetSeconds(duration);
[self.maskView.progress setProgress:timeInterval / totalDuration animated:NO];
} else if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {
// 当缓冲是空的时候
if (self.playerItem.isPlaybackBufferEmpty) {
[self bufferingSomeSecond];
}
} else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
// 当缓冲好的时候
if (self.playerItem.isPlaybackLikelyToKeepUp && self.state == CLPlayerStateBuffering){
self.state = CLPlayerStatePlaying;
}
}
}
#pragma mark - 缓冲较差时候
//卡顿时会走这里
- (void)bufferingSomeSecond{
self.state = CLPlayerStateBuffering;
// 需要先暂停一小会之后再播放,否则网络状况不好的时候时间在走,声音播放不出来
[self pausePlay];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self playVideo];
// 如果执行了play还是没有播放则说明还没有缓存好,则再次缓存一段时间
if (!self.playerItem.isPlaybackLikelyToKeepUp) {
[self bufferingSomeSecond];
}
});
}

一个xcode注释文档警告忽略
Bulid Settings -> Documentation Comments -> NO


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
常用的一些占位符:
%@:字符串占位符
%d:整型
%ld:长整型
%f:浮点型
%c:char类型
%%:%的占位符
BOOL studyBool = YES;
NSLog(@"打印BOOL型数据%@",studyBool?@"YES":@"NO");//打印BOOL型数据YES
NSLog(@"打印BOOL型数据%d",studyBool);//打印BOOL型数据1
BOOL alsoBool = NO;
NSLog(@"打印BOOL型数据%@",alsoBool?@"YES":@"NO");//打印BOOL型数据NO
NSLog(@"打印BOOL型数据%d",alsoBool);//打印BOOL型数据0
详细介绍:**********************************************************
%@: Objective-C对象,印有字符串返回descriptionWithLocale:如果于的话,或描述相反.CFTypeRef工作对象,返回的结果的CFCopyDescription功能.(这个翻译有问题建议按照自己的理解方式理解)。
%%: 为'%'字符;
%d,%D,%i: 为32位整型数(int);
%u,%U: 为32位无符号整型数(unsigned int);
%hi: 为有符号的16位整型数(short);
%hu: 为无符号的16位整型数(unsigned shord);
%qi: 为有符号的64位整型数(long long);
%qu: 为无符号的64位整型数(unsigned long long);
%x: 为32位的无符号整型数(unsigned int),打印使用数字0-9的十六进制,小写a-f;
%X: 为32位的无符号整型数(unsigned int),打印使用数字0-9的十六进制,大写A-F;
%qx: 为无符号64位整数(unsigned long long),打印使用数字0-9的十六进制,小写a-f;
%qX: 为无符号64位整数(unsigned long long),打印使用数字0-9的十六进制,大写A-F;
%o,%O: 为32位的无符号整数(unsigned int),打印八进制数;
%f: 为64位的浮点数(double);
%e: 为64位的浮点数(double),打印使用小写字母e,科学计数法介绍了指数的增大而减小;
%E: 为64位的浮点数(double),打印科学符号使用一个大写E介绍指数的增大而减小;
%g: 为64位的浮点数(double),用%e的方式打印指数,如果指数小于4或者大于等于精度,那么%f的风格就会有不同体现;
%G: 为64位的浮点数(double),用%E的方式打印指数,如果指数小于4或者大于等于精度,那么%f的风格就会有不同体现;
%c: 为8位的无符号字符%c(unsigned char),通过打印NSLog()将其作为一个ASCII字符,或者,不是一个ASCII字符,八进制格式\ddd或统一标准的字符编码的十六进制格式\udddd,在这里d是一个数字;
%C: 为16位Unicode字符%C(unichar),通过打印NSLog()将其作为一个ASCII字符,或者,不是一个ASCII字符,八进制格式\ddd或统一标准的字符编码的十六进制格式\udddd,在这里d是一个数字;
%s: 对于无符号字符数组空终止,%s系统中解释其输入编码,而不是别的,如utf-8;
%S: 空终止一系列的16位Unicode字符;
%p: 空指针(无效*),打印十六进制的数字0-9和小写a-f,前缀为0x;
%L: 在明确规定的长度下,进行修正,下面的一批数据a,A,e,E,f,F,g,G应用于双精度长整型的参数;
%a: 为64位的浮点数(double),按照科学计数法打印采用0x和一个十六进制数字前使用小写小数点p来介绍指数的增大而减小;
%A: 为64位的浮点数(double),按照科学计数法打印采用0X和一个十六进制数字前使用大写字母小数点P界扫指数的增大而减小;
%F: 为64位的浮点数(double),按照十进制表示法进行打印;
%z: 修改说明在%z长度以下d,i,o,u,x,X适用于某一指定类型的转换或者适用于一定尺寸的整数类型的参数;
%t: 修改说明在%t长度以下d,i,o,u,x,X适用于某一指定类型或一定尺寸的整数类型的转换的参数;
%j: 修改说明在%j长度以下d,i,o,u,x,X适用于某一指定类型或一定尺寸的整数类型的转换的参数

swift中的正则表达式

swift中的t正则表达式
正则表达式是对字符串操作的一种逻辑公式,用事先定义好的一些特定字符、及这些特定字符的组合,组成一个”规则字符串”,这个”规则字符串”用来表达对字符串的一种过滤逻辑。

正则表达式的用处:
判断给定的字符串是否符合某一种规则(专门用于操作字符串)
电话号码,电子邮箱,URL…
可以直接百度别人写好的正则
别人真的写好了,而且测试过了,我们可以直接用
要写出没有漏洞正则判断,需要大量的测试,通常最终结果非常负责
过滤筛选字符串,网络爬虫
替换文字,QQ聊天,图文混排

语法规则
1> 集合

1
2
3
4
5
6
[xyz] 字符集合(x/y或z)
[a-z] 字符范围
[a-zA-Z]
[^xyz] 负值字符集合 (任何字符, 除了xyz)
[^a-z] 负值字符范围
[a-d][m-p] 并集(a到d 或 m到p)

2> 常用元字符

1
2
3
4
5
6
7
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字 [a-zA-Z_0-9]
\s 匹配任意的空白符(空格、TAB\t、回车\r \n
\d 匹配数字 [0-9]
^ 匹配字符串的开始
$ 匹配字符串的结束
\b 匹配单词的开始或结束

2> 常用反义符

1
2
3
4
5
6
\W 匹配任意不是字母,数字,下划线,汉字的字符[^\w]
\S 匹配任意不是空白符的字符 [^\s]
\D 匹配任意非数字的字符[^0-9]
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符

4> 常用限定符

1
2
3
4
5
6
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n
{n,} 重复n次或更多次
{n,m} 重复n到m次,

5> 贪婪和懒惰

1
2
3
4
5
*? 重复任意次,但尽可能少重复
*+ 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

使用过程
1、创建规则
2、创建正则表达式对象
3、开始匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private func check(str: String) {
// 使用正则表达式一定要加try语句
do {
// - 1、创建规则
let pattern = "[1-9][0-9]{4,14}"
// - 2、创建正则表达式对象
let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions.CaseInsensitive)
// - 3、开始匹配
let res = regex.matchesInString(str, options: NSMatchingOptions(rawValue: 0), range: NSMakeRange(0, str.characters.count))
// 输出结果
for checkingRes in res {
print((str as NSString).substringWithRange(checkingRes.range))
}
}
catch {
print(error)
}
}

其他几个常用方法

1
2
3
4
5
6
7
8
9
10
11
// 匹配字符串中所有的符合规则的字符串, 返回匹配到的NSTextCheckingResult数组
public func matchesInString(string: String, options: NSMatchingOptions, range: NSRange) -> [NSTextCheckingResult]
// 按照规则匹配字符串, 返回匹配到的个数
public func numberOfMatchesInString(string: String, options: NSMatchingOptions, range: NSRange) -> Int
// 按照规则匹配字符串, 返回第一个匹配到的字符串的NSTextCheckingResult
public func firstMatchInString(string: String, options: NSMatchingOptions, range: NSRange) -> NSTextCheckingResult?
// 按照规则匹配字符串, 返回第一个匹配到的字符串的范围
public func rangeOfFirstMatchInString(string: String, options: NSMatchingOptions, range: NSRange) -> NSRange

使用子类来匹配日期、地址、和URL
看官网文档解释,可以知道这个NSDataDetector主要用来匹配日期、地址、和URL。在使用时指定要匹配的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class NSDataDetector : NSRegularExpression {
// all instance variables are private
/* NSDataDetector is a specialized subclass of NSRegularExpression. Instead of finding matches to regular expression patterns, it matches items identified by Data Detectors, such as dates, addresses, and URLs. The checkingTypes argument should contain one or more of the types NSTextCheckingTypeDate, NSTextCheckingTypeAddress, NSTextCheckingTypeLink, NSTextCheckingTypePhoneNumber, and NSTextCheckingTypeTransitInformation. The NSTextCheckingResult instances returned will be of the appropriate types from that list.
*/
public init(types checkingTypes: NSTextCheckingTypes) throws
public var checkingTypes: NSTextCheckingTypes { get }
}
// 这个是类型选择
public static var Date: NSTextCheckingType { get } // date/time detection
public static var Address: NSTextCheckingType { get } // address detection
public static var Link: NSTextCheckingType { get } // link detection

NSDataDetector 获取URL示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
匹配字符串中的URLS
- parameter str: 要匹配的字符串
*/
private func getUrl(str:String) {
// 创建一个正则表达式对象
do {
let dataDetector = try NSDataDetector(types: NSTextCheckingTypes(NSTextCheckingType.Link.rawValue))
// 匹配字符串,返回结果集
let res = dataDetector.matchesInString(str, options: NSMatchingOptions(rawValue: 0), range: NSMakeRange(0, str.characters.count))
// 取出结果
for checkingRes in res {
print((str as NSString).substringWithRange(checkingRes.range))
}
}
catch {
print(error)
}
}

“.*?” 可以满足一些基本的匹配要求
如果想同时匹配多个规则 ,可以通过 “|” 将多个规则连接起来
将字符串中文字替换为表情

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
/**
显示字符中的表情
- parameter str: 匹配字符串
*/
private func getEmoji(str:String) {
let strM = NSMutableAttributedString(string: str)
do {
let pattern = "\\[.*?\\]"
let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions.CaseInsensitive)
let res = regex.matchesInString(str, options: NSMatchingOptions(rawValue: 0), range: NSMakeRange(0, str.characters.count))
var count = res.count
// 反向取出文字表情
while count > 0 {
let checkingRes = res[--count]
let tempStr = (str as NSString).substringWithRange(checkingRes.range)
// 转换字符串到表情
if let emoticon = EmoticonPackage.emoticonWithStr(tempStr) {
print(emoticon.chs)
let attrStr = EmoticonTextAttachment.imageText(emoticon, font: 18)
strM.replaceCharactersInRange(checkingRes.range, withAttributedString: attrStr)
}
}
print(strM)
// 替换字符串,显示到label
emoticonLabel.attributedText = strM
}
catch {
print(error)
}
}

TextKit 给URL高亮显示
主要用到三个类
NSTextStorage
NSLayoutManager
NSTextContainer
自定义UILabel来实现url高亮

1、定义要用到的属性

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
/*
只要textStorage中的内容发生变化, 就可以通知layoutManager重新布局
layoutManager重新布局需要知道绘制到什么地方, 所以layoutManager就会文textContainer绘制的区域
*/
// 准们用于存储内容的
// textStorage 中有 layoutManager
private lazy var textStorage = NSTextStorage()
// 专门用于管理布局
// layoutManager 中有 textContainer
private lazy var layoutManager = NSLayoutManager()
// 专门用于指定绘制的区域
private lazy var textContainer = NSTextContainer()
override init(frame: CGRect) {
super.init(frame: frame)
setupSystem()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupSystem()
}
private func setupSystem()
{
// 1.将layoutManager添加到textStorage
textStorage.addLayoutManager(layoutManager)
// 2.将textContainer添加到layoutManager
layoutManager.addTextContainer(textContainer)
}
override func layoutSubviews() {
super.layoutSubviews()
// 3.指定区域
textContainer.size = bounds.size
}

2、重写label的text属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
override var text: String?
{
didSet{
// 1.修改textStorage存储的内容
textStorage.setAttributedString(NSAttributedString(string: text!))
// 2.设置textStorage的属性
textStorage.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(20), range: NSMakeRange(0, text!.characters.count))
// 3.处理URL
self.URLRegex()
// 2.通知layoutManager重新布局
setNeedsDisplay()
}
}

3、匹配字符串

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
func URLRegex()
{
// 1.创建一个正则表达式对象
do{
let dataDetector = try NSDataDetector(types: NSTextCheckingTypes(NSTextCheckingType.Link.rawValue))
let res = dataDetector.matchesInString(textStorage.string, options: NSMatchingOptions(rawValue: 0), range: NSMakeRange(0, textStorage.string.characters.count))
// 4取出结果
for checkingRes in res
{
let str = (textStorage.string as NSString).substringWithRange(checkingRes.range)
let tempStr = NSMutableAttributedString(string: str)
// tempStr.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: NSMakeRange(0, str.characters.count))
tempStr.addAttributes([NSFontAttributeName: UIFont.systemFontOfSize(20), NSForegroundColorAttributeName: UIColor.redColor()], range: NSMakeRange(0, str.characters.count))
textStorage.replaceCharactersInRange(checkingRes.range, withAttributedString: tempStr)
}
}catch
{
print(error)
}
}

4、重绘文字

1
2
3
4
5
6
7
8
9
10
// 如果是UILabel调用setNeedsDisplay方法, 系统会促发drawTextInRect
override func drawTextInRect(rect: CGRect) {
// 重绘
// 字形 : 理解为一个小的UIView
/*
第一个参数: 指定绘制的范围
第二个参数: 指定从什么位置开始绘制
*/
layoutManager.drawGlyphsForGlyphRange(NSMakeRange(0, text!.characters.count), atPoint: CGPointZero)
}

获取label中URL的点击

如果要获取URL的点击,那么必须获取点击的范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// 1、获取手指点击的位置
let touch = (touches as NSSet).anyObject()!
let point = touch.locationInView(touch.view)
print(point)
// 2、获取URL区域
// 注意: 没有办法直接设置UITextRange的范围
let range = NSMakeRange(10, 20)
// 只要设置selectedRange, 那么就相当于设置了selectedTextRange
selectedRange = range
// 给定指定的range, 返回range对应的字符串的rect
// 返回数组的原因是因为文字可能换行
let array = selectionRectsForRange(selectedTextRange!)
for selectionRect in array {
if CGRectContainsPoint(selectionRect.rect, point) {
print("点击了URL")
}
}
}

转自:http://www.cnblogs.com/songliquan/p/4860196.html

ios 跑马灯

swift3 可将time 换成GCD 优化 使用过程

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
92
93
94
95
96
97
98
99
100
101
/// 跑马灯View 需要手动调用清理函数
class MarqueeView: UIView {
var iconImageView:UIImageView!
var titleLabel:UILabel!
var showLabel:UILabel!
/// 显示的跑马灯数组
var showTextArray:[String] = ["测试跑马灯1","测试跑马灯2","测试跑马灯3","测试跑马灯4"]
/// 显示到的位置
var showTextCount = 0
var showtime:Timer?
override init(frame: CGRect) {
super.init(frame: frame)
let icon_W = frame.height*0.4444
self.iconImageView = UIImageView(frame: CGRect(x: interval_W_20, y: 0, width: icon_W, height: icon_W))
self.iconImageView.frame.origin.y = frame.height * 0.2777
self.addSubview(self.iconImageView)
self.titleLabel = UILabel()
self.titleLabel.font = GlobalFont_32
self.titleLabel.textColor = GlobalMainToneColor_2_4
self.addSubview(self.titleLabel)
self.showLabel = UILabel()
self.showLabel.font = GlobalFont_32
self.showLabel.textColor = GlobalTxtColor_1
self.addSubview(self.showLabel)
self.layer.masksToBounds = true
}
func setData(icon:UIImage,title:String,showTextArr:[String],backColor:UIColor = UIColor.white) {
self.backgroundColor = backColor
self.iconImageView.image = icon
self.titleLabel.text = title.truncate(start: 0, end: 8)
self.titleLabel.sizeToFit()
self.titleLabel.frame.origin.x = self.iconImageView.frame.origin.x+self.iconImageView.frame.width+interval_W_20
self.titleLabel.center.y = self.iconImageView.center.y
self.showLabel.frame.origin.x = self.titleLabel.frame.origin.x+self.titleLabel.frame.width + interval_W_20
if self.showTextArray != showTextArr{
self.showTextArray = showTextArr
self.showTextCount = 0
}
if showtime != nil{
showtime!.invalidate()
showtime = nil
}
self.timeChange()
self.showtime = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(self.timeChange), userInfo: nil, repeats: true)
}
deinit {
printLog("deinit MarqueeView")
}
func clear(){
self.stopTime()
}
/// 必须手动调用
private func stopTime(){
if showtime != nil{
showtime!.invalidate()
showtime = nil
}
}
/// 时间间隔
let timeInterval:Double = 5
/// 时间到处理函数
func timeChange(){
if self.showTextCount < self.showTextArray.count{
self.showLabel.text = self.showTextArray[self.showTextCount]
self.showTextCount += 1
}else{
self.showTextCount = 0
self.showLabel.text = self.showTextArray[self.showTextCount]
}
self.showLabel.sizeToFit()
var frame = self.showLabel.frame
frame.origin.y = self.frame.height
self.showLabel.frame = frame
UIView.beginAnimations("showText", context: nil)
UIView.setAnimationDuration(timeInterval)
UIView.setAnimationCurve(UIViewAnimationCurve.linear)
UIView.setAnimationDelegate(self)
UIView.setAnimationRepeatAutoreverses(false)
UIView.setAnimationRepeatCount(0)
frame = self.showLabel.frame
frame.origin.y = -frame.size.height
self.showLabel.frame = frame
UIView.commitAnimations()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

DZNEmptyDataSet 使用

From: http://www.jianshu.com/p/f70dc3e0f436

                  ⭐️⭐️⭐️ 
以下内容来源于官方源码、 README 文档、测试 Demo 以及个人使用总结 !

[TOC]

DZNEmptyDataSet 是基于 UITableViewUICollectionView 的范畴/扩展(category)类,它可以在空白页面上显示提示信息。

这是 iOS 内建的标准,用于处理空表和集合视图。默认情况下,如果你的表视图是空的,屏幕上什么也不会显示,它给用户的体验不是很好。

使用这个库,你只需要实现一些协议,iOS 就会很好地处理集合视图,然后合理美观地显示出用户信息。

支付宝-查询我的挂号记录

支付宝-查询我的挂号记录

其他效果图参考

使用该框架的项目

将你的项目添加到列表中 并且提供一张(320px)的效果图。

空数据设计模式(The Empty Data Set Pattern)

也被称为 Empty state 或者 Blank Slate

大多数应用程序会显示内容列表、数据集(在 iOS 程序猿眼里,这里通常指的是 UITableViewUICollectionView。),但有些时候这些页面可能是空白的,特别是对于那些刚创建空账户的新用户来说。 空白界面会对用户造成不知道如何进行下一步操作的困惑,因为用户不知道屏幕空白的原因是错误/Bug、网络异常,还是用户应该自己新建内容以恢复APP的正常状态。

请阅读这篇非常有趣的文章 Designing For The Empty States

iOS 9人机界面指南 1.4.2 中也提及过类似的指引:

如果应用中所有的功能当前都不可用,那么应该显示一些内容来解释当前的情形,并建议用户如何进行后续操作。这部分内容给予了用户以反馈,使用户相信你的应用现在没问题。同时这也可以稳定用户情绪,让他们决定是否要采取纠正措施,继续使用应用,还是切换到另一个应用。

Empty Data Sets 有助于:

  • 避免使用空白屏幕,并向用户传达屏幕空白的原因。
  • 用户指引(特别是作为引导页面)。
  • 避免其他中断机制,如显示错误警报。
  • 一致性和改善用户体验。
  • 传递品牌价值。

特性

  • 兼容 UITableViewUICollectionView 。 也兼容 UISearchDisplayControllerUIScrollView
  • 通过显示图片、标题、详细文本、按钮,提供布局外观的多种可能性。
  • 使用 NSAttributedString 类提供更容易定制的外观。
  • 使用 Auto Layout 以自动将内容集中到表格视图,并支持自动旋转。 也接受自定义垂直和水平对齐。
  • 自定义背景颜色。
  • 允许在整个表格矩形上轻敲手势(有助于放弃第一个响应者或类似操作)。
  • 提供更高级的定制,允许自定义视图。
  • 兼容 Storyboard
  • 兼容iOS 6,tvOS 9或更高版本。
  • 兼容iPhone,iPad和Apple TV。
  • 支持 App Store 。

这个库已经被设计为不需要通过扩展(extend) UITableViewUICollectionView 类的方式来实现了。 使用 UITableViewControllerUICollectionViewController 类仍然可以奏效。 只要通过遵循DZNEmptyDataSetSourceDZNEmptyDataSetDelegate 协议,您将能够完全自定义应用程序的空状态的内容和外观。

安装

支持 [CocoaPods][15] 导入

[15]: http://cocoapods.org/?q=DZNEmptyDataSet

pod ‘DZNEmptyDataSet’

使用

完整文档参考:CocoaPods 自动生成文档

导入

#import "UIScrollView+EmptyDataSet.h"

作为框架导入:

#import <DZNEmptyDataSet/UIScrollView+EmptyDataSet.h>

遵循协议

// 遵守 DZNEmptyDataSetSource 、DZNEmptyDataSetDelegate 协议
@interface MainViewController : UITableViewController <DZNEmptyDataSetSource, DZNEmptyDataSetDelegate>

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.tableView.emptyDataSetSource = self;
    self.tableView.emptyDataSetDelegate = self;

    // 删除单元格分隔线的一个小技巧
    self.tableView.tableFooterView = [UIView new];
}

实现数据源协议

DZNEmptyDataSetSource ——实现该协议,可以设置你想要在空白页面显示的内容,并且充分利用 NSAttributedString 功能来自定义文本外观。

  • 空白页显示图片

    • (UIImage )imageForEmptyDataSet:(UIScrollView )scrollView {
      return [UIImage imageNamed:@”lion”];
      }

效果图:


  • 空白页显示标题

    • (NSAttributedString )titleForEmptyDataSet:(UIScrollView )scrollView {
      NSString title = @”狮子王”;
      NSDictionary
      attributes = @{
      NSFontAttributeName:[UIFont boldSystemFontOfSize:18.0f],
      NSForegroundColorAttributeName:[UIColor darkGrayColor]
      };
      
      return [[NSAttributedString alloc] initWithString:title attributes:attributes];
      }

效果图:


  • 空白页显示详细描述

    • (NSAttributedString )descriptionForEmptyDataSet:(UIScrollView )scrollView {
      NSString *text = @”你好,我的名字叫辛巴,大草原是我的家!”;

      NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
      paragraph.lineBreakMode = NSLineBreakByWordWrapping;
      paragraph.alignment = NSTextAlignmentCenter;

      NSDictionary *attributes = @{

      NSFontAttributeName:[UIFont systemFontOfSize:14.0f],
      NSForegroundColorAttributeName:[UIColor lightGrayColor],
      NSParagraphStyleAttributeName:paragraph
      };
      

      return [[NSAttributedString alloc] initWithString:text attributes:attributes];
      }

效果图:


  • 空白页显示按钮:示例1

    • (NSAttributedString )buttonTitleForEmptyDataSet:(UIScrollView )scrollView forState:(UIControlState)state {
      // 设置按钮标题
      NSString buttonTitle = @”喜欢我就点点点点我”;
      NSDictionary
      attributes = @{
      NSFontAttributeName:[UIFont boldSystemFontOfSize:17.0f]
      };
      
      return [[NSAttributedString alloc] initWithString:buttonTitle attributes:attributes];
      }

效果图:

  • 空白页显示按钮:示例2

按钮点击高亮效果

- (NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state {
    NSString *text = @"Learn more";
    UIFont   *font = [UIFont systemFontOfSize:15.0];
    // 设置默认状态、点击高亮状态下的按钮字体颜色
    UIColor  *textColor = [UIColor colorWithHex:(state == UIControlStateNormal) ? @"007ee5" : @"48a1ea"];

    NSMutableDictionary *attributes = [NSMutableDictionary new];
    [attributes setObject:font      forKey:NSFontAttributeName];
    [attributes setObject:textColor forKey:NSForegroundColorAttributeName];

    return [[NSAttributedString alloc] initWithString:text attributes:attributes];
}

效果图:

  • 空白页显示按钮:示例3

按钮标题中 点击重试 四个字加粗:

#pragma mark - DZNEmptyDataSetSource

- (UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView {
    return [UIImage imageNamed:@"placeholder_No_Network"];
}

- (NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state {
    NSString *text = @"网络不给力,请点击重试哦~";

    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
    // 设置所有字体大小为 #15
    [attStr addAttribute:NSFontAttributeName
                   value:[UIFont systemFontOfSize:15.0]
                   range:NSMakeRange(0, text.length)];
    // 设置所有字体颜色为浅灰色
    [attStr addAttribute:NSForegroundColorAttributeName
                   value:[UIColor lightGrayColor]
                   range:NSMakeRange(0, text.length)];
    // 设置指定4个字体为蓝色
    [attStr addAttribute:NSForegroundColorAttributeName
                   value:HexColor(@"#007EE5")
                   range:NSMakeRange(7, 4)];
    return attStr;
}

- (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView {
    return -70.0f;
}

#pragma mark - DZNEmptyDataSetDelegate

- (void)emptyDataSet:(UIScrollView *)scrollView didTapButton:(UIButton *)button {
    // button clicked...
}

- (void)emptyDataSetWillAppear:(UIScrollView *)scrollView {
    self.tableView.contentOffset = CGPointZero;
}

效果图

  • 空白页显示按钮:示例4

    // 空白页显示返回按钮图片

    • (UIImage )buttonImageForEmptyDataSet:(UIScrollView )scrollView forState:(UIControlState)state {
      return [UIImage imageNamed:@”placeholder_return”];
      }

    • (void)emptyDataSet:(UIScrollView )scrollView didTapButton:(UIButton )button {
      [self.navigationController popViewControllerAnimated:YES];
      }


  • 设置按钮的背景颜色

    • (nullable UIImage )buttonBackgroundImageForEmptyDataSet:(UIScrollView )scrollView forState:(UIControlState)state;

  • 设置按钮图片

    • (UIImage )buttonImageForEmptyDataSet:(UIScrollView )scrollView forState:(UIControlState)state {
      return [UIImage imageNamed:@”Image”];
      }

效果图:


  • 设置图片的 tintColor

    • (UIColor )imageTintColorForEmptyDataSet:(UIScrollView )scrollView {
      return [UIColor yellowColor];
      }

效果图:就是设置图片颜色,脑补中。。。


  • 设置空白页面的背景色

    • (UIColor )backgroundColorForEmptyDataSet:(UIScrollView )scrollView {
      UIColor *appleGreenColor = [UIColor colorWithRed:199/255.0 green:237/255.0 blue:204/255.0 alpha:1.0];
      return appleGreenColor;
      }

效果图:


  • 如果你需要设置更复杂的布局,也可以返回自定义视图

    • (UIView )customViewForEmptyDataSet:(UIScrollView )scrollView {
      UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
      [activityView startAnimating];
      return activityView;
      }

效果图:


  • 设置图片动画

    #pragma mark - DZNEmptyDataSetSource
    #pragma mark 设置空白页图片

    • (nullable UIImage )imageForEmptyDataSet:(UIScrollView )scrollView {
      return [UIImage imageNamed:@”lion”];
      }

    #pragma mark 设置图片动画: 旋转

    • (CAAnimation )imageAnimationForEmptyDataSet:(UIScrollView )scrollView {
      CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath: @”transform”];

      animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
      animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI_2, 0.0, 0.0, 1.0)];

      animation.duration = 0.25;
      animation.cumulative = YES;
      animation.repeatCount = MAXFLOAT;

      return animation;
      }

    #pragma mark - DZNEmptyDataSetDelegate
    // 向代理请求图像视图动画权限。 默认值为NO。
    // 确保从 imageAnimationForEmptyDataSet 返回有效的CAAnimation对象:

    • (BOOL)emptyDataSetShouldAnimateImageView:(UIScrollView *)scrollView {
      return YES;
      }

效果图:

  • 图像视图动画:缩放

    #pragma mark - DZNEmptyDataSetSource
    #pragma mark 设置空白页图片

    • (nullable UIImage )imageForEmptyDataSet:(UIScrollView )scrollView {
      return [UIImage imageNamed:@”computer”];
      }

    #pragma mark 设置图片动画

    • (CAAnimation )imageAnimationForEmptyDataSet:(UIScrollView )scrollView
      {
      CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@”bounds”];
      animation.duration = 1.25;
      animation.cumulative = NO;
      animation.repeatCount = MAXFLOAT;
      animation.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 45, 45)];

      return animation;
      }

    #pragma mark - DZNEmptyDataSetDelegate

    • (BOOL)emptyDataSetShouldAnimateImageView:(UIScrollView *)scrollView {
      return YES;
      }

效果图:


  • 我们发现在官方的 Applications Demo 应用中的空白视图中的动画是这样的:

空白视图默认情况下显示一张【静态图片】,当用户点击【静态图片】以后,该图片会被替换成【加载转圈】。

通过阅读源码,可以发现它是这样工作的:

  1. 首先在遵循协议的.m文件中声明了一个 BOOL 类型的变量,用来记录空白页面当前的加载状态:

    @property (nonatomic, getter=isLoading) BOOL loading;

  1. 然后为该属性设置 setter 方法,重新加载空数据集视图:

    • (void)setLoading:(BOOL)loading
      {
      if (self.isLoading == loading) {
      return;
      }

      _loading = loading;
      // 每次 loading 状态被修改,就刷新空白页面。
      [self.tableView reloadEmptyDataSet];
      }

  1. 接下来要实现几个关联协议

    #pragma mark - DZNEmptyDataSetSource
    #pragma mark 设置空白页图片

    • (UIImage )imageForEmptyDataSet:(UIScrollView )scrollView {
      if (self.isLoading) {
      // 圆形加载图片
      return [UIImage imageNamed:@"loading_imgBlue_78x78"];
      
      }else {
      // 默认静态图片
      return [UIImage imageNamed:@"staticImage"];
      
      }
      }

    #pragma mark 图片旋转动画

    • (CAAnimation )imageAnimationForEmptyDataSet:(UIScrollView )scrollView
      {
      CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@”transform”];
      animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
      animation.toValue = [NSValue valueWithCATransform3D: CATransform3DMakeRotation(M_PI_2, 0.0, 0.0, 1.0) ];
      animation.duration = 0.25;
      animation.cumulative = YES;
      animation.repeatCount = MAXFLOAT;

      return animation;
      }

    #pragma mark - DZNEmptyDataSetDelegate
    #pragma mark 是否开启动画

    • (BOOL)emptyDataSetShouldAnimateImageView:(UIScrollView *)scrollView {
      return self.isLoading;
      }

    #pragma mark 空白页面被点击时刷新页面

    • (void)emptyDataSet:(UIScrollView )scrollView didTapView:(UIView )view {
      // 空白页面被点击时开启动画,reloadEmptyDataSet
      self.loading = YES;
      // 执行加载任务…
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      // 任务加载完成后关闭动画,reloadEmptyDataSet
      self.loading = NO;
      
      });
      }

  • 此外,你还可以调整内容视图的垂直对齐(垂直偏移量)方式:

    // 向上偏移量为表头视图高度/2

    • (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView {
      return -self.tableView.tableHeaderView.frame.size.height/2.0f;
      }
      // 或者,返回固定值
    • (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView {
      return -64;
      }

效果图:


  • 最后,你也可以设置所有组件彼此之间的上下间距(默认间距为11 pt):

    • (CGFloat)spaceHeightForEmptyDataSet:(UIScrollView *)scrollView {
      return 25.0f;
      }

效果图:


实现代理协议

DZNEmptyDataSetDelegate ——实现该协议,可以设置你期望从空白页面返回的的行为,并接收用户交互事件。

  • 设置是否 渲染和显示空白页面(默认为YES):

    • (BOOL)emptyDataSetShouldDisplay:(UIScrollView *)scrollView {
      return YES;
      }
  • 设置是否 以淡入方式显示空白页面 。 (默认值为YES)

    • (BOOL)emptyDataSetShouldFadeIn:(UIScrollView *)scrollView {
      return YES;
      }
  • 强制显示空数据集:当项目数量大于0时,请求代理是否仍应显示空数据集。(默认值为NO

    • (BOOL)emptyDataSetShouldBeForcedToDisplay:(UIScrollView *)scrollView;
  • 获取交互权限:是否接收用户点击事件(默认为YES):

    • (BOOL)emptyDataSetShouldAllowTouch:(UIScrollView *)scrollView;
  • 获取滚动权限(默认值为NO):

    • (BOOL)emptyDataSetShouldAllowScroll:(UIScrollView *)scrollView;
  • 获取图像动画权限:是否开启图片动画(默认值为NO):

    • (BOOL)emptyDataSetShouldAnimateImageView:(UIScrollView *)scrollView {
      return YES;
      }
  • 空白数据集 视图被点击 时触发该方法:

    • (void)emptyDataSet:(UIScrollView )scrollView didTapView:(UIView )view {
      // 处理视图点击事件…
      }
  • 空白数据集 按钮被点击时 触发该方法:

    • (void)emptyDataSet:(UIScrollView )scrollView didTapButton:(UIButton )button {
      // 处理按钮点击事件…
      }
  • 空白页将要出现

    • (void)emptyDataSetWillAppear:(UIScrollView *)scrollView {
      // 如果你的空白占位图与需求向左,发生偏移,可如下设置:
      self.tableView.contentOffset = CGPointZero;
      }
  • 空白页已经出现

    • (void)emptyDataSetDidAppear:(UIScrollView *)scrollView;
  • 空白页将要消失

    • (void)emptyDataSetWillDisappear:(UIScrollView *)scrollView;
  • 空白页已经消失

    • (void)emptyDataSetDidDisappear:(UIScrollView *)scrollView;

刷新布局

如果你需要刷新空白页面布局,只需调用:

[self.tableView reloadData];

或者

[self.collectionView reloadData];

这取决于你用的是哪一个。

强制布局更新

你还可以调用 [self.tableView reloadEmptyDataSet] 以使当前空白页面布局无效,并触发布局更新,绕过 -reloadData。 如果你的数据源上有很多逻辑处理,当你不需要或者想避免调用 -reloadData 时这可能很有用。 当使用 UIScrollView 时, [self.scrollView reloadEmptyDataSet] 是刷新内容的唯一方法。

参考文章

YYModel 使用

From: http://www.jianshu.com/p/25e678fa43d3

开篇说明:
虽然网上有很多讲解YYModel使用方法的文章,包括YYModel作者也在github上对其做了使用说明。
但在我实际使用过程中,依然发现文档的不完善,比如对于复杂的模型(如多层嵌套)讲解的仍不透彻,同时本文也会介绍一神器配合YYModel使用,让你感受分分钟搞定模型创建的酸爽。
当然为了减少读者的学习成本,本会对YYModel作者的文档进行丰富和扩展。
可在github上下载Demo,以便更直观了解各种使用场景详细代码。
文章只要包含:

    1. 详解YYModel的多种使用场景
    1. 拓展插件,让你一分钟搞定所有的模型的创建和调用。

一、YYModel的使用场景

1.简单的 Model 与 JSON 相互转换

// JSON:
{
    "uid":123456,
    "name":"Harry",
    "created":"1965-07-31T00:00:00+0000"
}

// Model:
@interface User : NSObject
@property UInt64 uid;
@property NSString *name;
@property NSDate *created;
@end

@implementation User

@end

// 将 JSON (NSData,NSString,NSDictionary) 转换为 Model:
User *user = [User yy_modelWithJSON:json];

// 将 Model 转换为 JSON 对象:
NSDictionary *json = [user yy_modelToJSONObject];

JSON/Dictionary 中的对象类型与 Model 属性不一致时,YYModel 将会进行如下自动转换。自动转换不支持的值将会被忽略,以避免各种潜在的崩溃问题。

格式自动转换.png

2.Model 属性名和 JSON 中的 Key 不相同

// JSON:
{
    "n":"Harry Pottery",
    "p": 256,
    "ext" : {
        "desc" : "A book written by J.K.Rowing."
    },
    "ID" : 100010
}

// Model:
@interface Book : NSObject
@property NSString *name;
@property NSInteger page;
@property NSString *desc;
@property NSString *bookID;
@end
@implementation Book
//返回一个 Dict,将 Model 属性名对映射到 JSON 的 Key。
+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"name" : @"n",
             @"page" : @"p",
             @"desc" : @"ext.desc",
             @"bookID" : @[@"id",@"ID",@"book_id"]};
}
@end

你可以把一个或一组 json key (key path) 映射到一个或多个属性。如果一个属性没有映射关系,那默认会使用相同属性名作为映射。
在 json->model 的过程中:如果一个属性对应了多个 json key,那么转换过程会按顺序查找,并使用第一个不为空的值。
在 model->json 的过程中:如果一个属性对应了多个 json key (key path),那么转换过程仅会处理第一个 json key (key path);如果多个属性对应了同一个 json key,则转换过过程会使用其中任意一个不为空的值。

3.Model 包含其他 Model

// JSON
{
    "author":{
        "name":"J.K.Rowling",
        "birthday":"1965-07-31T00:00:00+0000"
    },
    "name":"Harry Potter",
    "pages":256
}

// Model: 什么都不用做,转换会自动完成
@interface Author : NSObject
@property NSString *name;
@property NSDate *birthday;
@end
@implementation Author
@end

@interface Book : NSObject
@property NSString *name;
@property NSUInteger pages;
@property Author *author; //Book 包含 Author 属性
@end
@implementation Book
@end

4.容器类属性

@class Shadow, Border, Attachment;

@interface Attributes
@property NSString *name;
@property NSArray *shadows; //Array<Shadow>
@property NSSet *borders; //Set<Border>
@property NSMutableDictionary *attachments; //Dict<NSString,Attachment>
@end

@implementation Attributes
// 返回容器类中的所需要存放的数据类型 (以 Class 或 Class Name 的形式)。
+ (NSDictionary *)modelContainerPropertyGenericClass {
    return @{@"shadows" : [Shadow class],
             @"borders" : Border.class,
             @"attachments" : @"Attachment" };
}
@end

在实际使用过过程中,[Shadow class]Border.class@"Attachment"没有明显的区别。
这里仅仅是创建作者有说明,实际使用时,需要对其遍历,取出容器中得字典,然后继续字典转模型。(YYModel的核心是通过runtime获取结构体中得Ivars的值,将此值定义为key,然后给keyvalue值,所以我们需要自己遍历容器(NSArrayNSSetNSDictionary),获取每一个值,然后KVC)。

  • 具体的代码实现如下:

    NSDictionary json =[self getJsonWithJsonName:@”ContainerModel”];
    ContainerModel
    containModel = [ContainerModel yy_modelWithDictionary:json];
    NSDictionary dataDict = [containModel valueForKey:@”data”];
    //定义数组,接受key为list的数组
    self.listArray = [dataDict valueForKey:@”list”];
    //遍历数组
    [self.listArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL
    _Nonnull stop) {

    NSDictionary *listDict = obj;
    //获取数组中得字典
    List *listModel = [List yy_modelWithDictionary:listDict];
    //获取count 和 id
    NSString *count = [listModel valueForKey:@"count"];
    NSString *id = [listModel valueForKey:@"id"];
    

5.黑名单与白名单

@interface User
@property NSString *name;
@property NSUInteger age;
@end

@implementation Attributes
// 如果实现了该方法,则处理过程中会忽略该列表内的所有属性
+ (NSArray *)modelPropertyBlacklist {
    return @[@"test1", @"test2"];
}
// 如果实现了该方法,则处理过程中不会处理该列表外的属性。
+ (NSArray *)modelPropertyWhitelist {
    return @[@"name"];
}
@end

6.数据校验与自定义转换

实际这个分类的目的比较简单和明确。
就是对判断是否为时间戳,然后对时间戳进行处理,调用
_createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue];
获取时间。

// JSON:
{
    "name":"Harry",
    "timestamp" : 1445534567     //时间戳
}

// Model:
@interface User
@property NSString *name;
@property NSDate *createdAt;
@end

@implementation User
// 当 JSON 转为 Model 完成后,该方法会被调用。
// 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。
// 你也可以在这里做一些自动转换不能完成的工作。
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic {
    NSNumber *timestamp = dic[@"timestamp"];
    if (![timestamp isKindOfClass:[NSNumber class]]) return NO;
    _createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue];
    return YES;
}

// 当 Model 转为 JSON 完成后,该方法会被调用。
// 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。
// 你也可以在这里做一些自动转换不能完成的工作。
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic {
    if (!_createdAt) return NO;
    dic[@"timestamp"] = @(n.timeIntervalSince1970);
    return YES;
}
@end
  • 需要注意的时,如果用插件,对时间戳类型或默认创建为NSUInteger类型,需要将其更改为NSDate类型。

7.Coding/Copying/hash/equal/description

以下方法都是YYModel的简单封装,实际使用过程和系统方法区别不大。对其感兴趣的可以点进方法内部查看。

@interface YYShadow :NSObject <NSCoding, NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) CGSize size;
@end

@implementation YYShadow
// 直接添加以下代码即可自动完成
- (void)encodeWithCoder:(NSCoder *)aCoder { 
 [self yy_modelEncodeWithCoder:aCoder]; 
}
- (id)initWithCoder:(NSCoder *)aDecoder {
  self = [super init];
  return [self yy_modelInitWithCoder:aDecoder]; 
}
- (id)copyWithZone:(NSZone *)zone { 
 return [self yy_modelCopy]; 
}
- (NSUInteger)hash { 
 return [self yy_modelHash]; 
}
- (BOOL)isEqual:(id)object { 
 return [self yy_modelIsEqual:object]; 
}
- (NSString *)description { 
 return [self yy_modelDescription]; 
}
@end
  • 二、ESJsonFormat与YYModel的结合使用

彩蛋
给大家介绍一款插件,配合ESJsonFormat

配图:

ESJsonFormat插件使用.gif

使用方法:
快捷键:shift + control + J
插件安装方法比较简单,在此不赘述,不知道可自行google。

好处

    1. 可以直接将json数据复制,ESJsonFormat会根据数据类型自动生成属性。(建议还是要自行检查,比如时间戳,系统会默认帮你生成为NSUInteger,而我们想要的为NSDate类型)
    1. 对于多模型嵌套,不必创建多个文件,ESJsonFormat会自动在一个文件下创建多重类型,极其便捷。

至此YYModel的使用已讲解完毕,关于YYModel的底层核心是运用runtime获取类结构体中Ivars,进行KVC操作,然后根据不同情况进行分别处理
此处只是传递给大家一个概念,不展开讲解,网上有很多源码分析文章,可自学google学习。
文末,做个综述。
建议大家有时间一定要多看底层,分析源码。不要只会用,知其然不知其所以然。
如有错误欢迎指出。

写在最后

我得写作原则:
在技术学习道路上,阅读量和代码量绝不能线性提升你的技术水平。
同样写文章也是如此,作者所写的文章完全是基于自己对技术的理解,在写作时也力求形象不抽象。绝不copy充数,所以也欢迎大家关注和参与讨论。
技术学习绝不能孤胆英雄独闯天涯,而应在一群人的交流碰撞,享受智慧火花的狂欢。
希望我的文章能成为你的盛宴,也渴望你的建议能成为我的大餐。
如有错误请留言指正,对文章感兴趣可以关注作者不定期更新。

github fork 更新作者代码

From: https://my.oschina.net/snail0/blog/776099

我们在github上可以fork别人的代码来阅读,更改,提交修改。这里网上命令或者可视化操作很多博客都有提及,不在赘述。

但是如果源代码更新了,我们怎么同步更新了,网络上好多用的命令行,我试了很久,没同步成功。

原来github上已经支持同步,只需要在需要的时候操作下,就能同步作者的最新代码。

在我们fork后的界面点击

把我们的项目放到左边,原作者的放到右边,即可看到作者的更新

创建一个pull request

合并后就,在你的分支上就能看到原作者的更新了

/// 整理逻辑 自己代码在左边 更新 整理 合并, 最后只更新主分支

iOS SQLite 使用FMDBMigrationManager 进行数据库迁移

From: http://www.jianshu.com/p/6cfc38a6d2c0

FMDBMigrationManager 是与FMDB结合使用的一个第三方,可以记录数据库版本号并对数据库进行数据库升级等操作。
首先要集成FMDB和FMDBMigrationManager,建议使用cocoapods,这里不再多说。
根据官方文档的解释,有两种方法实现升级,我们一个一个的解释。
先说第一种,添加文件的方式进行记录版本和升级操作,新建一个空白的项目,并创建一个数据库,也就是我们将要进行升级操作的数据库。
将数据库与我们的FMDBMigrationManager关联起来

FMDBMigrationManager * manager=[FMDBMigrationManager managerWithDatabaseAtPath:DBPath migrationsBundle:[NSBundle mainBundle]];
//DBPath是要升级的数据库的地址
// [NSBundle mainBundle]是保存数据库升级文件的位置 根据自己放文件的位置定 升级文件是什么下面会说

下面创建版本号表,这个表会保存在我们的数据库中,进行数据库版本号的记录,并将我们的数据库升级到最高的版本。

BOOL resultState=NO;
NSError * error=nil;
if (!manager.hasMigrationsTable) {
 resultState=[manager createMigrationsTable:&error]; 
}

执行完该语句,再去我们的数据库中查看,会发现多了一个表 schema_migrations

这个表就是用来存储版本号的,目前表中的版本号(version)为0。

下面是最重要的升级语句

BOOL resultState=NO; 
resultState=[manager migrateDatabaseToVersion:UINT64_MAX progress:nil error:&error];
//UINT64_MAX 表示升级到最高版本

整体的升级函数就是

FMDBMigrationManager * manager=[FMDBMigrationManager managerWithDatabaseAtPath:DBPath migrationsBundle:[NSBundle mainBundle]]; 
BOOL resultState=NO; 
NSError * error=nil;
 if (!manager.hasMigrationsTable) {
 resultState=[manager createMigrationsTable:&error]; 
}
 resultState=[manager migrateDatabaseToVersion:UINT64_MAX progress:nil error:&error];

此时还没有添加升级文件,所以执行完这段代码数据库并没有什么变化,下面添加升级文件。
所谓升级文件,就是一些sql文件,在里面写入一些对数据库操作的语句

文件名的格式是固定的 (数字)_(描述性语言).sql,前面的数字就是所谓的版本号,官方建议使用时间戳,也可以使用1,2,3,4,5……升级,保持单调递增即可。
文件内写入要对数据库做的操作

然后将文件拖入工程

FMDBMigrationManager 将会根据创建时给入的NSBundle自行寻找sql文件,对比版本号进行操作。添加sql文件之后,重启项目,再运行一次升级代码,查看数据库

发现新增了一个User表,再加入一个新增数据库字段的文件(工程中常用的升级操作就是增加数据库字段)

2_AddEmail.sql 的内容

重启项目运行升级代码,查看数据库

发现email已经添加进来了

存储的版本号也已经记录到2了,以后还想升级的话,再向项目中添加 3_*.sql 文件就好了。
下面使用第二种方法进行升级,使用自定义类的形式。第一种方法,每次升级都要建立一个文件,而且大多数时候,文件里面只写一句话,感情上会难以接受,因此我们介绍一种较为简洁的方式。首先定义一个新的类:Migration

遵循FMDBMigrating协议,与第一种拖入文件的方式相同,name是升级描述,version是版本号,最后一个方法里面,进行操作。
由于name和version都是只读的,因此我们要自定义一个init方法,传入描述 版本号和升级语句,升级语句最好用数组的方式传入,因为可能有多个升级语句。

//
//  Migration.h
//  Sqlite
//
//  Created by LC on 16/10/21.
//  Copyright © 2016年 LC. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "FMDBMigrationManager.h"
@interface Migration : NSObject<FMDBMigrating>

- (instancetype)initWithName:(NSString *)name andVersion:(uint64_t)version andExecuteUpdateArray:(NSArray *)updateArray;//自定义方法

@property (nonatomic, readonly) NSString *name;
@property (nonatomic, readonly) uint64_t version;
- (BOOL)migrateDatabase:(FMDatabase *)database error:(out NSError *__autoreleasing *)error;


@end

//
//  Migration.m
//  Sqlite
//
//  Created by LC on 16/10/21.
//  Copyright © 2016年 LC. All rights reserved.
//

#import "Migration.h"

@interface Migration()

@property(nonatomic,copy)NSString * myName;
@property(nonatomic,assign)uint64_t myVersion;
@property(nonatomic,strong)NSArray * updateArray;
@end


@implementation Migration

- (instancetype)initWithName:(NSString *)name andVersion:(uint64_t)version andExecuteUpdateArray:(NSArray *)updateArray
{
    if (self=[super init]) {
        _myName=name;
        _myVersion=version;
        _updateArray=updateArray;
    }
    return self;
}

- (NSString *)name
{
    return _myName;
}

- (uint64_t)version
{
    return _myVersion;
}

- (BOOL)migrateDatabase:(FMDatabase *)database error:(out NSError *__autoreleasing *)error
{
    for(NSString * updateStr in _updateArray)
    {
        [database executeUpdate:updateStr];
    }
    return YES;
}


@end

使用方法也很简单,将自定义类对象添加进manager即可。

NSString *DBPath = [documents stringByAppendingPathComponent:KFMDBName];
NSLog(@"%@",DBPath);
//DBPath是要升级的数据库的地址
// [NSBundle mainBundle]是保存数据库升级文件的位置 根据自己放文件的位置定 升级文件是什么下面会说
FMDBMigrationManager * manager = [FMDBMigrationManager managerWithDatabaseAtPath:DBPath migrationsBundle:[NSBundle mainBundle]];

Migration * migration_1=[[Migration alloc]initWithName:@"新增USer表" andVersion:1 andExecuteUpdateArray:@[@"create table User(name text,age integer,sex text,phoneNum text)"]];
[manager addMigration:migration_1];

Migration * migration_2=[[Migration alloc]initWithName:@"USer表新增字段email" andVersion:2 andExecuteUpdateArray:@[@"alter table User add email text"]];
[manager addMigration:migration_2];

BOOL resultState=NO;
NSError * error=nil;
if (!manager.hasMigrationsTable) {
    resultState=[manager createMigrationsTable:&error];
}

//UINT64_MAX 表示升级到最高版本
resultState=[manager migrateDatabaseToVersion:UINT64_MAX progress:nil error:&error];

NSLog(@"Has `schema_migrations` table?: %@", manager.hasMigrationsTable ? @"YES" : @"NO");
NSLog(@"Origin Version: %llu", manager.originVersion);
NSLog(@"Current version: %llu", manager.currentVersion);
NSLog(@"All migrations: %@", manager.migrations);
NSLog(@"Applied versions: %@", manager.appliedVersions);
NSLog(@"Pending versions: %@", manager.pendingVersions);

以后还想升级,在加入一个新的自定义对象,注意!!!版本号要保持递增

NSString *DBPath = [documents stringByAppendingPathComponent:KFMDBName];
NSLog(@"%@",DBPath);
//DBPath是要升级的数据库的地址
// [NSBundle mainBundle]是保存数据库升级文件的位置 根据自己放文件的位置定 升级文件是什么下面会说
FMDBMigrationManager * manager = [FMDBMigrationManager managerWithDatabaseAtPath:DBPath migrationsBundle:[NSBundle mainBundle]];

Migration * migration_1=[[Migration alloc]initWithName:@"新增USer表" andVersion:1 andExecuteUpdateArray:@[@"create table User(name text,age integer,sex text,phoneNum text)"]];
[manager addMigration:migration_1];

Migration * migration_2=[[Migration alloc]initWithName:@"USer表新增字段email" andVersion:2 andExecuteUpdateArray:@[@"alter table User add email text"]];
[manager addMigration:migration_2];

Migration * migration_3=[[Migration alloc]initWithName:@"USer表新增字段address" andVersion:3 andExecuteUpdateArray:@[@"alter table User add address text"]];
[manager addMigration:migration_3];

BOOL resultState=NO;
NSError * error=nil;
if (!manager.hasMigrationsTable) {
    resultState=[manager createMigrationsTable:&error];
}

//UINT64_MAX 表示升级到最高版本
resultState=[manager migrateDatabaseToVersion:UINT64_MAX progress:nil error:&error];


SQLite清空表数据并将自增ID设为0

SQL标准中有TRUNCATE TABLE语句,用来清空表的所有内容。但SQLite不支持这个语句。在SQLite中直接使用“DELETE FROM TableName”就可以了。对于大多数DBMS来说,用DELETE不如用TRUNCATE 速度快,因为TRUNCATE 不用访问整个表,不用记录数据的变动。

SQLite虽然不支持TRUNCATE,但它对DELETE做了优化:

“When the WHERE is omitted(略去) from a DELETE statement and the table being deleted has no triggers(触发器), SQLite uses an optimization(优化) to erase the entire table content without having to visit each row of the table individually. This “truncate” optimization makes the delete run much faster.”

通常在清空表的时候,还需要把自增列归零。在SQLite中定义自增列的方法如下:

CREATE TABLE TableName ( id INTEGER PRIMARY KEY AUTOINCREMENT, … );

当SQLite数据库中包含自增列时,会自动建立一个名为 sqlite_sequence 的表。这个表包含两个列:name和seq。name记录自增列所在的表,seq记录当前序号(下一条记录的编号就是当前序号加1)。如果想把某个自增列的序号归零,只需要修改 sqlite_sequence表就可以了。

UPDATE sqlite_sequence SET seq = 0 WHERE name = ‘TableName’;
也可以直接把该记录删掉:

DELETE FROM sqlite_sequence WHERE name = ‘TableName’;
要想将所有表的自增列都归零,直接清空sqlite_sequence表就可以了:

DELETE FROM sqlite_sequence;

自定义UISearchBar外观

From: http://www.jianshu.com/p/66b5b777f5dc

最近,在项目过程中遇到要自定义SearchBar的外观,虽然自己觉得用系统默认的外观就行了,不过UI设计师要求不用系统的默认样式,要跟app主题保持一致。

图1:设计效果图

图1:设计效果图

从上图可以看出,我们要做的UISearchBar要有圆角,边框颜色,取消按钮颜色,背景透明等等。

开始以为可能要自己写一个自定义的UISearchBar控件了,后面研究了一番,发现可以设定系统UISearchBar属性来更改,便把经验记录下来跟大家分享一下。

首先,我们看下系统默认的SearchBar的样式,离我们的目标样式确实相差很大, 后面我会一步一步详细说明做成我们的目标样式。

图2:UISearchBar默认样式

图2:UISearchBar默认样式

1. 设置背景色

我以白色的背景色为例,下面看看代码:

//1. 设置背景颜色
    //设置背景图是为了去掉上下黑线
    self.customSearchBar.backgroundImage = [[UIImage alloc] init];
    // 设置SearchBar的颜色主题为白色
    self.customSearchBar.barTintColor = [UIColor whiteColor];

图3:设置SearchBar背景色为白色

图3:设置SearchBar背景色为白色

2. 设置边框颜色和圆角

//2. 设置圆角和边框颜色
    UITextField *searchField = [self.customSearchBar valueForKey:@"searchField"];
    if (searchField) {
        [searchField setBackgroundColor:[UIColor whiteColor]];
        searchField.layer.cornerRadius = 14.0f;
        searchField.layer.borderColor = [UIColor colorWithRed:247/255.0 green:75/255.0 blue:31/255.0 alpha:1].CGColor;
        searchField.layer.borderWidth = 1;
        searchField.layer.masksToBounds = YES;
    }

这段代码有个特别的地方就是通过KVC获得到UISearchBar的私有变量
searchField(类型为UITextField),设置SearchBar的边框颜色和圆角实际上也就变成了设置searchField的边框颜色和圆角,你可以试试直接设置SearchBar.layer.borderColor和cornerRadius,会发现这样做是有问题的。

图4:设置边框颜色和圆角

图4:设置边框颜色和圆角

嗯,离预期效果越来越近了!

3. 设置按钮(取消按钮)的文字和文字颜色

//3. 设置按钮文字和颜色
    [self.customSearchBar fm_setCancelButtonTitle:@"取消"];
    self.customSearchBar.tintColor = [UIColor colorWithRed:86/255.0 green:179/255.0 blue:11/255.0 alpha:1];
    //修正光标颜色
    [searchField setTintColor:[UIColor blackColor]];

//其中fm_setCancelButtonTitle是我写的UISearchBar一个分类的方法
- (void)fm_setCancelButtonTitle:(NSString *)title {
    if (IS_IOS9) {
        [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]] setTitle:title];
    }else {
        [[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil] setTitle:title];
    }
}

图5:设置按钮文字和颜色

图5:设置按钮文字和颜色

需要特别注意的是设置searchBar的tintColor会使输入框的光标颜色改变,可以通过设置searchField的tintColor来修正。

4. 设置输入框的文字颜色和字体

//4. 设置输入框文字颜色和字体
    [self.customSearchBar fm_setTextColor:[UIColor blackColor]];
    [self.customSearchBar fm_setTextFont:[UIFont systemFontOfSize:14]];

//下面两个方法是UISearchBar分类代码
- (void)fm_setTextColor:(UIColor *)textColor {
    if (IS_IOS9) {
        [UITextField appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]].textColor = textColor;
    }else {
        [[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setTextColor:textColor];
    }
}

- (void)fm_setCancelButtonTitle:(NSString *)title {
    if (IS_IOS9) {
        [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]] setTitle:title];
    }else {
        [[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil] setTitle:title];
    }
}

图6:最终对比效果图

图6:最终对比效果图

5. 如何设置搜索图标

下面评论中有简友问我怎么更改默认的搜索图标,我查了下UISearchBar的API,发现有方法可以更改的。

//5. 设置搜索Icon
    [self.customSearchBar setImage:[UIImage imageNamed:@"Search_Icon"]
                  forSearchBarIcon:UISearchBarIconSearch
                             state:UIControlStateNormal];

为了跟系统默认Icon的有个明显的对比,我特殊找了张绿色的搜索Icon,效果见下图:

设置搜索Icon.png

设置搜索Icon.png

Tips: 还可以设置其他的Icon(如清除按钮图标),也是用上面的方法,具体要设置什么,可以去看看UISearchBarIcon这个枚举。

2016-10-20新增

6.实现类似微信的搜索框

图8:提问.png

图8:提问.png

这里有位简友问到怎么实现类似微信的搜索框,下面我将详细说说我的思路。

首先,要告诉大家的是 UISearchBar 是一个由多个控件组合成的比较复杂的控件。用Reveal查看 UISearchBar 组成如下 :

图9:UISearchBar组成图.png

图9:UISearchBar组成图.png

从上图可以看出UISearch的组成结构,下面我总结了一张思维导图,更加的清晰直观:

图10:UISearchBar思维导图.png

图10:UISearchBar思维导图.png

UISearchBar 的主要部分是 UISearchBarTextFieldUISearchBarTextField 又包含好几个subView,其中那个 UIButton 指的是清除按钮(输入文字时会出现)。

好了,上面说了一堆,其实我主要是想表达:既然 UISearchBar 是由这么多个子控件组成,我再往里面加一个按钮又何妨?

最终解决思路就是这样了:在 UISearchBarTextField 添加一个 UIButton,我暂且将它叫做 voiceButton,然后 voiceButton 设置好自动布局以及点击事件处理(点击按钮后,显示录音界面…),最后还要监控文本的变化,有输入了文本时,voiceButton 隐藏,没有文本时, 显示 voiceButton

所有代码如下:

//6. 实现类似微信的搜索框
    UIButton *voiceButton = [UIButton buttonWithType:UIButtonTypeCustom];
    [voiceButton setImage:[UIImage imageNamed:@"Voice_button_icon"] forState:UIControlStateNormal];
    [voiceButton addTarget:self action:@selector(tapVoiceButton:) forControlEvents:UIControlEventTouchUpInside];
    [searchField addSubview:voiceButton];
    self.voiceButton = voiceButton;

    //Autolayout
    voiceButton.translatesAutoresizingMaskIntoConstraints = NO;
    NSDictionary *views = NSDictionaryOfVariableBindings(voiceButton);
    //设置水平方向约束
    [searchField addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[voiceButton(21)]-|" options:NSLayoutFormatAlignAllRight | NSLayoutFormatAlignAllLeft metrics:nil views:views]];
    //设置高度约束
    [searchField addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[voiceButton(21)]" options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:nil views:views]];
    //设置垂直方向居中约束
    [searchField addConstraint:[NSLayoutConstraint constraintWithItem:voiceButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:searchField attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];

//按钮触摸事件
- (IBAction)tapVoiceButton:(id)sender {
    NSLog(@"Tap voiceButton");
}

//监控文本变化
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    self.voiceButton.hidden = searchText.length > 0;
}

好了,打完收工,最终效果图如下:

record.gif

完整代码在这里