接上篇。上篇有一个细节忘了写,在Coding_iOS-Info.plist 里面添加了一个key 是 Status bar is initially hidden  Value 是 YES,在application 启动的时候隐藏状态栏,然后在

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
......
// 显示状态栏
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
......
}

本篇细读FunctionIntroManager 类,我认为该类的职责是Coding 为中秋节做的一个彩蛋。该类的唯一的一个类方法 + (void)showIntroPage; 只有在当前某个版本下的中秋节的的那天才不会直接return;

如果该类方法跳过return 向下执行,首先启动图会由EaseStartView 类换为一张中秋主题的Coding 图片, EaseStartView 类下篇会细读。FunctionIntroManage 类主要完成的功能是,提供一个类似首次启动引导页的功能,有几张图片组成调用 EAIntroView 这个第三方库,左右滑动可以展示几张Coding 风格的图片, 在最后一张图片左滑的时候进入[UIApplication sharedApplication].keyWindow.rootViewController 。除此之外Coding 还有调用 JazzHands 第三方库做的动画感十足的启动引导页,后面会做细读。首先看几张中秋风格的图片:

   

代码部分:

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
......
[self.window makeKeyAndVisible]; // 走到这里会把RootViewController 配置一遍 [FunctionIntroManager showIntroPage]; // 方法执行后直接return 了,并没有做什么事情,另外一个简单的启动引导页面,只在中秋节的时候启动用的。在指定的版本中有一个在中秋节期间的时候特别制作的启动页面
......
}

这里分析FunctionIntroManager 类之前,先对UIWindow 做一个延展阅读。

UIWindow 类

(一)UIWindow 介绍

  NS_CLASS_AVAILABLE_IOS(2_0) @interface UIWindow : UIView 

  UIWindow是继承自UIView,之前感觉UIWindow 一直处于顶级, 以为UIView 继承自UIWindow,孰不知正好相反,通常在一个程序中只会有一个UIWindow,但可以手动创建多个UIWindow,同时加到程序里面。iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器和view,最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了,一个iOS程序之所以能显示到屏幕上,完全是因为它有UIWindow。也就说,没有UIWindow,就看不见任何UI界面。UIWindow是创建的第一个视图控件,创建的第一个对象是UIapplication。先创建UIwindow,再创建控制器,创建控制器的view,然后将控制器的view添加到UIWindow上。

  在IOS应用中,我们使用UIWindow和UIView来呈现界面。UIWindow并不包含任何默认的内容,但它是被当做UIView的容器,用于放置应用中所有的UIView。从继承关系来看,UIWindow继承自UIView,所以UIWIndow除了具有UIView的所有功能外,还增加了一些特有的属性和方法。

  通常在一个程序中只会有一个UIWindow,但有些时候我们调用系统的控件(例如UIAlertView)时,IOS系统为了保证UIAlertView在所有的界面之上,它会临时创建一个新的UIWindow,通过将其UIWindow的UIWindowLevel设置的更高,让UIWindow盖在所有的应用界面之上。(平时输入文字弹出的键盘,就处在一个新的UIWindow中)

(二)UIWindow 作用

  1、作为容器,包含app 所要显示的所有视图,并展示app 的可视内容。

  2、传递触摸消息到程序中view和其他对象,即把事件分发给视图以及其他对象。

  3、与UIViewController协同工作,方便完成设备方向旋转的支持, 处理屏幕旋转。

(三)UIWindow 创建

  在有storyboard的项目中,UIWindow是如何创建的?

  为什么创建一个storyboard,没有看到创建uiwindow的过程?

  它其实是把创建UIWindow的过程给屏蔽起来了。可以把代理的UIWindow的属性的值打印出来NSLog(@“window=%p”,self.window);打印出来确实是有值的,说明确实创建了UIWindow.不仅创建了UIWindow,默认还创建了UIWindow对应的控制器,也可以打印进行查看。NSLog(@“%@“,self.window.rootviewcontroller);

  有storyboard的项目中的创建过程:

  当用户点击应用程序图标的时候,先执行Main函数,执行UIApplicationMain(),根据其第三个和第四个参数创建Application,创建代理,并且把代理设置给application(看项目配置文件info.plist里面的storyboard的name,根据这个name找到对应的storyboard),开启一个事件循环,当程序加载完毕,他会调用代理的didFinishLaunchingWithOptions:方法。在调用didFinishLaunchingWithOptions:方法之前,会加载storyboard,在加载的时候创建一个window,接下来会创建箭头所指向的控制器,把该控制器设置为UIWindow的根控制器,接下来再将window显示出来,即看到了运行后显示的界面。(提示:关于这部分可以查看story的初始化的文档)

  使用nib文件创建:

  如果不使用storyboard,也可以用nib文件来代替。将一个window对象拖拽到Interface Builder文件中,并将这个文件指定为app的main interface。那么在app启动的时候,iOS也会自动创建window对象。
为了确保window的大小与屏幕大小吻合,需要在Interface Builder中对window对象勾选Full Screen at Launch这个属性。需要注意的是,window的尺寸永远应该是屏幕的尺寸,不应该考虑状态栏等元素,因为这些是view controller应该处理的问题。

  在没有storyboard中的创建过程:

  先执行Main函数,执行UIApplicationMain(),根据其第三个和第四个参数创建Application,创建代理,并且把代理设置给application,开启一个事件循环,当程序加载完毕,他会调用代理的didFinishLaunchingWithOptions:方法。在该方法中,会创建一个Window,然后创建一个控制器,并把该控制器设置为UIWindow的根控制器,接下来再将window显示出来,即看到了运行后显示的界面。

  手写代码:

  当然也可以通过手写代码的方式创建window。比如官方示例代码:

 - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

     UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
myViewController = [[MyViewController alloc] init];
window.rootViewController = myViewController;
[window makeKeyAndVisible]; return YES;
}

(四)UIWindowLevel & KeyWindow

 UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar __TVOS_PROHIBITED;

  UIWindow有三个层级,分别是Normal,StatusBar,Alert。打印输出他们三个这三个层级的值我们发现从左到右依次是0,1000,2000,也就是说Normal级别是最低的,StatusBar处于中等水平,Alert级别最高。而通常我们的程序的界面都是处于Normal这个级别上的,系统顶部的状态栏应该是处于StatusBar级别,UIActionSheet和UIAlertView这些通常都是用来中断正常流程,提醒用户等操作,因此位于Alert级别。根据window显示级别优先的原则,级别高的会显示在上面,级别低的在下面,我们程序正常显示的view位于最底层。iOS系统中定义了三个window层级,其中每一个层级又可以分好多子层级(从UIWindow的头文件中可以看到成员变量CGFloat _windowSublevel;),不过系统并没有把则个属性开出来。

   当Level层级相同的时候,只有第一个设置为KeyWindow的显示出来,后面同级的再设置KeyWindow也不会显示。
   UIWindow在显示的时候是不管KeyWindow是谁,都是Level优先的,即Level最高的始终显示在最前面。

  什么是keyWindow,官方文档中是这样解释的"The key window is the one that is designated to receive keyboard and other non-touch related events. Only one window at a time may be the key window." 翻译过来就是说,keyWindow是指定的用来接收键盘以及非触摸类的消息,而且程序中每一个时刻只能有一个window是keyWindow。(非keyWindow也是可以接受键盘消息)

 UIKIT_EXTERN NSString *const UIWindowDidBecomeVisibleNotification; // nil
UIKIT_EXTERN NSString *const UIWindowDidBecomeHiddenNotification; // nil
UIKIT_EXTERN NSString *const UIWindowDidBecomeKeyNotification; // nil
UIKIT_EXTERN NSString *const UIWindowDidResignKeyNotification; // nil

  这四个通知对象中的object都代表当前已显示(隐藏),已变成keyWindow(非keyWindow)的window对象,其中的userInfo则是空的。于是我们可以注册这个四个消息,再打印信息来观察keyWindow的变化以及window的显示,隐藏的变动。

  如果我们创建的UIWindow需要处理键盘事件,那就要合理的将其设置为keyWindow。keyWindow是被系统设计用来接受键盘和其他非触摸事件的UIWindow。我们可以通过makeKeyWindow 和 resignKeyWindow 方法来将自己创建的UIWindow实例设置成keyWindow。

  Action Sheet和Alert View

  知道了window的存在之后,感觉也能知道很多事情。

  比如,iOS中的UIActionSheet和UIAlertView其实是显示在另一个window上的。

  监听UIWindowDidResignKeyNotification,可以发现,当action sheet弹出时,UIWindowDidResignKeyNotification通知被发送了。此时检查app所在的window,发现它已经不再是key window了。

(五)把View 添加到UIWindow 

  1、addSubview

  直接将view通过addSubview方式添加到window中,程序负责维护view的生命周期以及刷新,但是并不会为去理会view对应的ViewController,因此采用这种方法将view添加到window以后,我们还要保持view对应的ViewController的有效性,不能过早释放。

  2、rootViewController

  rootViewController时UIWindow的一个遍历方法,通过设置该属性为要添加view对应的ViewController,UIWindow将会自动将其view添加到当前window中,同时负责ViewController和view的生命周期的维护,防止其过早释放

  两个方法的区别:

  以后的开发中,建议使用(2).因为方法(1)存在一些问题,比如说控制器上面可能由按钮,需要监听按钮的点击事件,如果是1,那么按钮的事件应该由控制器来进行管理。但控制器是一个局部变量,控制器此时已经不存在了,但是控制器的view还在,此时有可能会报错。注意:方法执行完,这个控制器就已经不存在了。

问题描述1:当view发生一些事件的时候,通知控制器,但是控制器已经销毁了,所以可能出现未知的错误。

  问题描述2:添加一个开关按钮,让屏幕360度旋转(两者的效果不一样)。当发生屏幕旋转事件的时候,UIapplication对象会将旋转事件传递给uiwindow,uiwindow又会将旋转事件传递给它的根控制器,由根控制器决定是否需要旋转

  UIapplication->uiwindow->根控制器(第一种方式没有根控制器,所以不能跟着旋转)。

  提示:不通过控制器的view也可以做开发,但是在实际开发中,不要这么做,不要直接把view添加到UIWindow上面去。因为,难以管理。

(六)UIWindow 获取

  1.主窗口和次窗口

  [self.window makekeyandvisible]; // 让窗口成为主窗口,并且显示出来。有这个方法,才能把信息显示到屏幕上。

  因为Window有makekeyandvisible这个方法,可以让这个Window凭空的显示出来,而其他的view没有这个方法,所以它只能依赖于Window,Window显示出来后,view才依附在Window上显示出来。

  [self.window make keywindow]; // 让uiwindow成为主窗口,但不显示。

  2.获取UIwindow

  (1)[UIApplication sharedApplication].windows  在本应用中打开的UIWindow列表,这样就可以接触应用中的任何一个UIView对象(平时输入文字弹出的键盘,就处在一个新的UIWindow中)

  (2)[UIApplication sharedApplication].keyWindow(获取应用程序的主窗口)用来接收键盘以及非触摸类的消息事件的UIWindow,而且程序中每个时刻只能有一个UIWindow是keyWindow。

  提示:如果某个UIWindow内部的文本框不能输入文字,可能是因为这个UIWindow不是keyWindow

  (3)view.window获得某个UIView所在的UIWindow

参考链接:

iOS开发UI篇—UIWindow简单介绍

以前从来没注意过的UIWindow 卖萌凉

UIWindow的一点儿思考

UIWindow & UIWindowLevel详解

UIWindow & UIWindowLevel笔记

UIWindow使用介绍

接下来接着学习FunctionIntroManager 类:

+ (void)showIntroPage{}, 方法内部,首先判断需不需要展示这个特殊的引导页:

 + (BOOL)needToShowIntro{
// return YES;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *preVersion = [defaults stringForKey:kIntroPageKey];
BOOL needToShow = ![preVersion isEqualToString:kVersion_Coding]; // 指定版本,特定的版本才有这个特定的图片引导启动
if (![NSDate isDuringMidAutumn]) {//中秋节期间才显示
needToShow = NO;
}
return needToShow;
}

preVersion 是取本地plist 保存的系统版本信息和当前的系统版本比较,获取当前系统版本的方法:

 //版本号
#define kVersion_Coding [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]
#define kVersionBuild_Coding [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]

[NSDate isDuringMidAutumn]; 判断当前是不是中秋节,对NSDate 添加了两个category ,NSDate+Common.m 和 NSDate+convenience.m:

 + (BOOL)isDuringMidAutumn{
// return YES;
BOOL isDuringMidAutumn;
NSDate *curDate = [NSDate date];
if (curDate.year != ||
curDate.month != ||
curDate.day < ||
curDate.day > ) {//中秋节期间才显示
isDuringMidAutumn = NO;
}else{
isDuringMidAutumn = YES;
}
return isDuringMidAutumn;
}

curDate.year、curDate.month、curDate.day:

 -(int)year {
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [gregorian components:NSYearCalendarUnit fromDate:self];
return (int)[components year];
} -(int)month {
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [gregorian components:NSMonthCalendarUnit fromDate:self];
return (int)[components month];
} -(int)day {
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [gregorian components:NSDayCalendarUnit fromDate:self];
return (int)[components day];
}

这三个实例方法是获取当前的年月日,引出了NSCalendar 类 和 NSDateComponents 类,这里做一个延展阅读:

NSCalendar + NSDateComponents + NSDateFomatter + NSDate

  历法能使人类确定每一日在无限的时间中的确切位置并记录历史。日历,历法,一般历法都是遵循固定的规则的,具有周期性。日历都是已知的或可预测的。任何一种具体的历法,首先必须明确规定起始点,即开始计算的年代,这叫“纪元”;以及规定一年的开端,这叫“岁首”。此外,还要规定每年所含的日数,如何划分月份,每月有多少天等等。

  NSCalendar 对世界上现存的常用的历法进行了封装,既提供了不同历法的时间信息,又支持日历的计算。NSDateFomatter 表示的时间默认以公历为参考,可以通过设置calendar 属性变量获得特定历法下的时间表示。NSDate 是独立与任何历法的,它只是时间相对于某个时间点的时间差,NSDate 是进行日历计算的基础。NSDateComponents将时间表示成适合人类阅读和使用的方式,通过NSDateComponents可以快速而简单地获取某个时间点对应的“年”,“月”,“日”,“时”,“分”,“秒”,“周”等信息。当然一旦涉及了年月日时分秒就要和某个历法绑定,因此NSDateComponents必须和NSCalendar一起使用,默认为公历。NSDateComponents除了像上面说的表示一个时间点外,还可以表示时间段,例如:两周,三个月,20年,7天,10分钟,50秒等等。时间段用于日历的计算,例如:获取当前历法下,三个月前的某个时间点。可以说,要获取某个时间点在某个历法下的表示,需要NSDateComponents;要计算当前时间点在某个历法下对应的一个时间段前或后的时间点,需要NSDateComponents。NSDateComponents返回的day, week, weekday, month, year这一类数据都是从1开始的。因为日历是给人看的,不是给计算机看的,从0开始就是个错误。

当前所有的历法类型:

FOUNDATION_EXPORT NSString * const NSCalendarIdentifierGregorian  NS_AVAILABLE(10_6, 4_0); // the common calendar in Europe, the Western Hemisphere, and elsewhere // 公历
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierBuddhist NS_AVAILABLE(10_6, 4_0); // 佛教日历
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierChinese NS_AVAILABLE(10_6, 4_0); // 中国农历
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierCoptic NS_AVAILABLE(10_6, 4_0); // 埃及日历
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierEthiopicAmeteMihret NS_AVAILABLE(10_6, 4_0);
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierEthiopicAmeteAlem NS_AVAILABLE(10_6, 4_0);
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierHebrew NS_AVAILABLE(10_6, 4_0); // 希伯来日历
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierISO8601 NS_AVAILABLE(10_6, 4_0); // ISO8601
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierIndian NS_AVAILABLE(10_6, 4_0); // 印度日历
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierIslamic NS_AVAILABLE(10_6, 4_0); // 伊斯兰日历
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierIslamicCivil NS_AVAILABLE(10_6, 4_0); // 伊斯兰教日历
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierJapanese NS_AVAILABLE(10_6, 4_0); // 日本日历
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierPersian NS_AVAILABLE(10_6, 4_0);
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierRepublicOfChina NS_AVAILABLE(10_6, 4_0);
// A simple tabular Islamic calendar using the astronomical/Thursday epoch of CE 622 July 15
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierIslamicTabular NS_AVAILABLE(10_10, 8_0);
// The Islamic Umm al-Qura calendar used in Saudi Arabia. This is based on astronomical calculation, instead of tabular behavior.
FOUNDATION_EXPORT NSString * const NSCalendarIdentifierIslamicUmmAlQura NS_AVAILABLE(10_10, 8_0);

NSDateComponents 实例化的方式

第一种:

     // 定义一个遵循某个历法的日历对象
NSCalendar *greCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
// 通过已定义的日历对象,获取某个时间点的NSDateComponents 表示,并设置需要设置哪些信息
NSDateComponents *dateComponents = [greCalendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitWeekday | NSCalendarUnitWeekdayOrdinal | NSCalendarUnitEra | NSCalendarUnitQuarter | NSCalendarUnitWeekOfMonth | NSCalendarUnitWeekOfYear | NSCalendarUnitYearForWeekOfYear | NSCalendarUnitNanosecond | NSCalendarUnitCalendar | NSCalendarUnitTimeZone fromDate:[NSDate date]];
NSLog(@"year(年份): %li", (long)dateComponents.year);
NSLog(@"quarter(季度): %li", (long)dateComponents.quarter);
NSLog(@"month(月份)%li", (long)dateComponents.month);
NSLog(@"day(日期)%li", (long)dateComponents.day);
NSLog(@"hour(小时)%li", (long)dateComponents.hour);
NSLog(@"minute(分钟)%li", (long)dateComponents.minute);
NSLog(@"second(秒)%li", (long)dateComponents.second);
// 周日 1 周一 2 周二 3 ...
NSLog(@"weekday(星期)%li", (long)dateComponents.weekday);
NSLog(@"weekOfYear(该年第几周)%li", (long)dateComponents.weekOfYear);
NSLog(@"weekOfMonth(该月第几周)%li", (long)dateComponents.weekOfMonth);

注意:若获取dateComponents对象时,设置components的时候未添加

NSCalendarUnitYear ,dateComponents.year将返回错误的数值,其他的也一样,所以使用NSDateComponents表示时间时,要确保需要使用的数据都在componets中添加了。

第二种:

     // 先定义一个遵循某个历法的日历对象
NSCalendar *greCalendarTwo = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
// 定义一个NSDateComponents 对象,设置一个时间点
NSDateComponents *dateComponentsForDate = [[NSDateComponents alloc] init];
[dateComponentsForDate setDay:];
[dateComponentsForDate setMonth:];
[dateComponentsForDate setYear:];
// 根据设置的dateComponentsForDate 获取历法中与之对应的时间点
// 这里的时分秒会使用NSDateComponents 中规定的默认值,一般是0 或 1
NSDate *dateFromDateComponentsForDate = [greCalendarTwo dateFromComponents:dateComponentsForDate];
// 定义一个NSDateComponents 对象, 设置一个时间段
NSDateComponents *dateComponentsAsTimeQantum = [[NSDateComponents alloc] init];
[dateComponentsForDate setDay:];
// 在当前历法下,获取6 天后的时间点
NSDate *dateFromDateComponentsAsTimeQantum = [greCalendarTwo dateByAddingComponents:dateComponentsAsTimeQantum toDate:[NSDate date] options:NSCalendarWrapComponents];

第三种:

     // 先定义一个尊徐某个历法的日历对象
NSCalendar *greCalendarThree = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
// 根据两个时间点,定义NSDatecomponents 对象,从而获取这两个时间点的时间差
NSDateComponents *dateComponentsThree = [greCalendarThree components:NSCalendarUnitYear fromDate:[NSDate dateWithTimeIntervalSince1970:] toDate:[NSDate date] options:NSCalendarWrapComponents];
NSLog(@"number of years: %li", (long)dateComponentsThree.year);

NSCalendar中比较重要的方法和概念

(1) firstWeekday是大家比较容易浑淆的东西。

大家在使用dateComponents.weekday获取某天对应的星期时,会发现,星期日对应的值为1,星期一对应的值为2,星期二对应的值为3,依次递推,星期六对应的值为7,这与我们平时理解的方式不一样。然后,我们就开始找是不是可以设置这种对应关系。终于,我们在NSCalendar中发现了firstWeekday这个变量,从字面意思上看貌似就是我们寻找的那个东西。可是,设置过firstWeekday后,我们又发现完全没有作用,真是郁闷啊!其实,大家不必郁闷,因为郁闷也没用,iOS中规定的就是周日为1,周一为2,周二为3,周三为4,周四为5,周五为6,周六为7,无法通过某个设置改变这个事实的,只能在使用的时候注意一下这个规则了。那firstWeekday是干什么用的呢?大家设置一下firstWeekday,再获取一下dateComponents.weekOfYear或dateComponents.weekOfMonth,看看返回的数据是否发生了变化。firstWeekday的作用确实是修改当前历法中周的起始位置,但是不能修改周日对应的数值,只能修改一年或一个月中周的数量,以及周的次序。

(2)

- (NSRange)rangeOfUnit:(NSCalendarUnit)smaller inUnit:(NSCalendarUnit)larger forDate:(NSDate *)date;

Unit: 单元 NSRange : typedef struct _NSRange{NSUInteger location; NSUInteger length;} NSRange;

  我们大致可以理解为:某个时间点所在的“小单元”,在“大单元”中的数量(返回值range的location属性变量的值一般是错误的)。例如:

 // 当前时间对应的月份中有几天
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:[NSDate date]].length;
// 当前时间对应的月份中有几周(前面的firstWeekday 会影响这个结果)
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitWeekday inUnit:NSCalendarUnitMonth forDate:[NSDate date]].length;

(3)

- (NSUInteger)ordinalityOfUnit:(NSCalendarUnit)smaller inUnit:(NSCalendarUnit)larger forDate:(NSDate *)date;

我们大致可以理解为:某个时间点所在的“小单元”,在“大单元”中的位置(从1开始)。例如:

     // 当前时间对应的周是当前年中的第几周
[[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitWeekOfYear inUnit:NSCalendarUnitYear forDate:[NSDate date]];
[[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitWeekday inUnit:NSCalendarUnitYear forDate:[NSDate date]];
// 当前时间对应的周是当前月中的第几周
[[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitWeekOfMonth inUnit:NSCalendarUnitYear forDate:[NSDate date]];
[[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitWeekday inUnit:NSCalendarUnitMonth forDate:[NSDate date]];

在这里:NSCalendarUnitWeekOfYear ,NSCalendarUnitWeekOfMonth 与NSCalendarUnitWeekday 的使用结果相同, 为了避免混淆,建议此处使用

NSCalendarUnitWeekday , 而定义NSDateComponents 时使用 NSCalendarUnitWeekOfMonth NSCalendarUnitWeekOfMonth。

(4)

- (BOOL)rangeOfUnit:(NSCalendarUnit)unit startDate:(NSDate * __nullable * __nullable)datep interval:(nullable NSTimeInterval *)tip forDate:(NSDate *)date NS_AVAILABLE(10_5, 2_0);

大致可以理解为:"某个时间点"所在的"单元"的起始时间,以及起始时间距离"某个时间点" 的时差(单位秒)。例如:

     NSDate *startDateOfYear;
NSDate *startDateOfMonth;
NSDate *startDateOfWeek;
NSDate *startDateOfDay;
NSTimeInterval TIOfYear;
NSTimeInterval TIOfMonth;
NSTimeInterval TIOfWeek;
NSTimeInterval TIOfDay;
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitYear startDate:&startDateOfYear interval:&TIOfYear forDate:[NSDate date]];
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitMonth startDate:&startDateOfMonth interval:&TIOfMonth forDate:[NSDate date]];
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitWeekday startDate:&startDateOfWeek interval:&TIOfWeek forDate:[NSDate date]];
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay startDate:&startDateOfDay interval:&TIOfDay forDate:[NSDate date]];
NSLog(@"firstDateOfYear: %@, FirstDateOfMonth: %@, FirstDateOfWeek: %@, FirstDateOfDay: %@", startDateOfYear, startDateOfMonth, startDateOfWeek, startDateOfDay);
NSLog(@"TIOfYear: %f, TIOfMonth: %f, TIOfWeek: %f, TIOfDay: %f", TIOfYear, TIOfMonth, TIOfWeek, TIOfDay);
 // + currentCalendar
// 取得当前用户的逻辑日历
// currentCalendar 取得的值会一直保持在cache 中,第一个取得以后如果用户修改该系统日历设定,这个值也不会改变
NSCalendar *calendar = [NSCalendar currentCalendar];
NSLog(@"calendar = %@", calendar);
// + (id)autoupdatingCurrentCalendar
// 取得当前用户的逻辑日历
// 用autoupdatingCurrentCalendar ,那么每次取得的值都会是当前系统的日历的值
NSCalendar *autoupdatingCurrent = [NSCalendar autoupdatingCurrentCalendar];
NSLog(@"autoupdatingCurrent = %@", autoupdatingCurrent);
// Initializing a Calendar
// - initWithCalendarIdentifier:
// 如果想要用公历的时候,就要将NSDateFormatter 的日历设置成公历。 否则随着用户的系统设定的改变,取得的日期的格式也会不一样
NSCalendar *initCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setCalendar:initCalendar];
// - setFirstWeekday;
// 设置第一个工作日
// 设定每周的第一天从星期几开始,比如
// 如果设定从星期日开始, 则value 传入 1
// 如果设定从星期一开始, 则value 传入 2
// 以此类推
[initCalendar setFirstWeekday:];
// - setLocale;
// 设置区域
[initCalendar setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"zh"]];
// 设定作为(每年及每月)第一周必须包含的最少天数,比如: 如需设定第一周最少包括7 天, 则value 传入 7
// - setMinimumDaysInFirstWeek
[initCalendar setMinimumDaysInFirstWeek:];
// - setTimeZone
// 设置时区
[initCalendar setTimeZone:[NSTimeZone defaultTimeZone]];
// -- Getting Information About a calendar
// - calendarIdentifier
// 返回日历的标识符
NSString *calendarIdentifier = [initCalendar calendarIdentifier];
NSLog(@"calendarIdentifier = %@", calendarIdentifier);
// - firstWeekday
// 返回日历指定的每周的第一条从星期几开始。缺省为星期天,即firstWeek = 1
NSUInteger firstWeekday = [initCalendar firstWeekday];
NSLog(@"firstWeekDay = %lu", (unsigned long)firstWeekday);
// - locale
// 返回日历指定的地区信息
NSLocale *locale = [initCalendar locale];
NSLog(@"locale = %@", locale.localeIdentifier);
// - maximumRangeOfUnit: // 返回单元的最大范围
// - minimumRangeOfUnit: // 返回单元的最小范围 // 比如 Day Calendar Unit 就是一个月最多31 天这个意思
NSRange range = [initCalendar maximumRangeOfUnit:NSCalendarUnitDay];
NSLog(@"range = %lu", (unsigned long)range.length);
// - minimumDaysInFirstWeek
// 返回日历指定的第一周必须包含的最少天数
NSUInteger minimumDays = [initCalendar minimumDaysInFirstWeek];
NSLog(@"minimumDays = %lu", (unsigned long)minimumDays);
// - ordinalityOfUnit: inUmit:forDate:
// 在一个给定的时间,小日历单元如(一天)在大日历单元(一周)中的序数
// 比如forDate 参数是星期一,而且firstWeekday 参数被设置为2 (也就是星期一为一周的第一天),那么返回为1
// 通过这个函数可以判断 例如: 给定的日期是在一周的第几天,或一月的第几周。一年的第几个月。一年的第几天等
// 注:firstWeekday 的设定会影响这个函数的返回值
NSUInteger ordinality = [initCalendar ordinalityOfUnit:NSCalendarUnitWeekday inUnit:NSCalendarUnitWeekday forDate:[NSDate date]];
NSLog(@"ordinality = %lu", (unsigned long)ordinality);
//- rangeOfUnit:inUnit:forDate:
//一个小日历单元下。大日历单元的范围 例如 小日历单元是天。大日历单元是周。那么范围就是7天。就是1-7
NSRange rangeOfUnit = [initCalendar rangeOfUnit:NSCalendarUnitWeekday inUnit:kCFCalendarUnitWeek forDate:[NSDate date]];
NSLog(@"rangeOfUnit = %lu",(unsigned long)rangeOfUnit.length);
//- timeZone:
//返回日历指定的时区信息。
NSTimeZone *timeZone = [initCalendar timeZone];
NSLog(@"timeZone = %@",timeZone.abbreviation); //-- //-- Calendrical Calculations //- components:fromDate:
//返回时间组件
unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
NSDateComponents *comps = [initCalendar components:unitFlags fromDate:[NSDate date]];
NSLog(@"NSDateComponents - %ld",(long)comps.year); //- components:fromDate:toDate:options:
//返回时间组件 比较2个日期
NSDate *startDate = [NSDate dateWithTimeIntervalSince1970:];
NSDate *endDate = [NSDate date];
unsigned int unitFlags2 = NSCalendarUnitMonth | NSCalendarUnitDay;
NSDateComponents *comps2 = [initCalendar components:unitFlags2 fromDate:startDate toDate:endDate options:];
NSInteger months = [comps2 month];
NSInteger days = [comps2 day];
NSLog(@"months = %ld days = %ld",(long)months,(long)days); //- dateByAddingComponents:toDate:options:
//追加日期 并返回一个新日期
//
NSDate *currentDate = [NSDate date];
NSDateComponents *comps3 = [[NSDateComponents alloc] init];
[comps3 setMonth:];
[comps3 setDay:];
NSDate *newDate = [initCalendar dateByAddingComponents:comps3 toDate:currentDate options:];
NSLog(@"newDate = %@",newDate); //- dateFromComponents:
//创建日期
{
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setYear:];
[comps setMonth:];
[comps setDay:];
[comps setHour:];
[comps setMinute:];
[comps setSecond:];
NSDate *date = [calendar dateFromComponents:comps];
NSLog(@"date = %@",date);
}

参考链接:

http://www.cnblogs.com/wujian1360/archive/2011/09/05/2168007.html

http://www.cnblogs.com/CCSSPP/archive/2013/07/11/3183410.html

http://my.oschina.net/yongbin45/blog/156181

接下来继续看FunctionIntroManager 类:

假设当前是指定版本且今天是中秋节,方法不return ,继续往下执行。

下面介绍一下 EAIntroView 这个第三方库, github地址:https://github.com/ealeksandrov/EAIntroView

EAIntroView 是一个用来实现软件启动时介绍的控件,支持多个视图进行滑动显示。

可灵活自定义的App介绍界面,使用简单。特色包括:

1. 滑动到最后一页,继续滑动将隐藏介绍页,进入App页面;当然也可以在任意一页点击“Skip”按钮直接跳进App页面;

2. 介绍页面之间的滑动切换采用淡入淡出的效果(cross-dissolve transition);

3. 可以任意设置每页的元素,包括背景图片、标题、标题图片、描述以及这些元素的位置;

4. 支持storyboard/IB。

基本使用方式是: 创建一组EAIntropage(可自定义,具体使用见下文),使用这组EAIntropage 创建一个EAIntroView的视图IntroView,将这个IntroView showInView到想要展示的视图上(见下文)

  • pageWithCustomView://自定义视图

  • pageWithCustomViewFromNibNamed://自定义nib

代理协议:

  • introDidFinish: //完成引导

  • intro:pageAppeared:withIndex: //引导页切换

IntroView支持的方法:

  • setPages://设置界面

  • showInView:animateDuration://设置展示动画

  • hideWithFadeOutDuration://显示和消失时间

  • setCurrentPageIndex: //设置当前显示的界面以及动画

第一步: 创建界面

每一个界面需要通过[EAIntroPage page]来创建,你可以自定义属性,所有的属性都是可选的.或者你可以通过你自定义的view(可以是nib),使用这种方式大多数选项就被忽略了.例如:

 // 基本的创建方式
EAIntroPage *page1 = [EAIntroPage page];
page1.title = @"Hello world";
page1.desc = sampleDescription1;
// 自定义的,这些属性都是可选的
EAIntroPage *page2 = [EAIntroPage page];
page2.title = @"This is page 2";
page2.titleFont = [UIFont fontWithName:@"Georgia-BoldItalic" size:];
page2.titlePositionY = ;
page2.desc = sampleDescription2;
page2.descFont = [UIFont fontWithName:@"Georgia-Italic" size:];
page2.descPositionY = ;
page2.titleIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"title2"]];
page2.titleIconPositionY = ;
//nib的自定义视图
EAIntroPage *page3 = [EAIntroPage pageWithCustomViewFromNibNamed:@"IntroPage"];
page3.bgImage = [UIImage imageNamed:@"bg2"];

第二步:创建介绍视图

所有的页面创建完成后,创建介绍视图,只是在介绍视图中按顺序展示.也可以通过传递给IntroView一组视图初始化, IntroView将重建视图的内容.

 EAIntroView *intro = [[EAIntroView alloc] initWithFrame:self.view.bounds andPages:@[page1,page2,page3,page4]];

 //设置代理
[intro setDelegate:self];

第三步: 展示引导图

  [intro showInView:self.view animateDuration:0.0]; 

在 FunctionIntroManager 是:

     NSMutableArray *pages = [NSMutableArray new];
for (int index = ; index < kIntroPageNum; index ++) {
EAIntroPage *page = [self p_pageWithIndex:index]; // 创建EAIntroPage
[pages addObject:page];
}
if (pages.count <= ) {
return;
}
EAIntroView *introView = [[EAIntroView alloc] initWithFrame:kScreen_Bounds andPages:pages];
introView.backgroundColor = [UIColor whiteColor];
introView.swipeToExit = YES;
introView.scrollView.bounces = YES; // 这里使用了 introView.swipeToExit = YES, 在滑动到pageController 最后一页的时候再向左滑即退出,进入app 内部 // introView.skipButton = [self p_skipButton];
// introView.skipButtonY = 20.f + CGRectGetHeight(introView.skipButton.frame);
// introView.skipButtonAlignment = EAViewAlignmentCenter; if (pages.count <= ) {
introView.pageControl.hidden = YES;
}else{
introView.pageControl = [self p_pageControl]; // 创建SMPageController
introView.pageControlY = .f + CGRectGetHeight(introView.pageControl.frame);
}
[introView showFullscreen];
// 保存启动页的版本信息 intro_page_version
[self markHasBeenShowed];

#define kIntroPageNum 2

for 循环创建2 个EAIntroPage 添加到 pages 数组里面,然后创建EAIntroView  把pages 初始化时传递给他。

然后下面根据EAIntroPage 个数判断是否显示introView.pageControl .当需要pageControl 时,调用SMPageControl 这个第三方库创建pageControl;

最后[self markHasBeenShowed]; 把版本信息写进本地。

这里主要看一下EAIntroView  的展示函数:

 - (void)showFullscreen {
[self showFullscreenWithAnimateDuration:0.3f andInitialPageIndex:]; // 展示在整个屏幕上 默认的动画时间是 0.3 秒,透明度由0 变到1
} - (void)showFullscreenWithAnimateDuration:(CGFloat)duration {
[self showFullscreenWithAnimateDuration:duration andInitialPageIndex:]; // 设置的动画的时间, 并且默认的是从第 0 页开始, 也就是从第一页展示引导页
} - (void)showFullscreenWithAnimateDuration:(CGFloat)duration andInitialPageIndex:(NSUInteger)initialPageIndex {
UIView *selectedView; NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator]; // 按照索引号从大到小访问数组的元素,而不是从小到大访问数组的元素,即逆序遍历数组。下面会做一个延伸阅读。
for (UIWindow *window in frontToBackWindows) { // 从windows 数组里面获取application 的当前的Window
BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
BOOL windowIsVisible = !window.hidden && window.alpha > ;
BOOL windowLevelNormal = window.windowLevel == UIWindowLevelNormal; if (windowOnMainScreen && windowIsVisible && windowLevelNormal) {
selectedView = window;
break;
}
} [self showInView:selectedView animateDuration:duration withInitialPageIndex:initialPageIndex]; // 在 selectedView 上展示
} - (void)showInView:(UIView *)view {
[self showInView:view animateDuration:0.3f withInitialPageIndex:]; // 在指定的View 上展示
} - (void)showInView:(UIView *)view animateDuration:(CGFloat)duration {
[self showInView:view animateDuration:duration withInitialPageIndex:];
} - (void)showInView:(UIView *)view animateDuration:(CGFloat)duration withInitialPageIndex:(NSUInteger)initialPageIndex {
if(![self pageForIndex:initialPageIndex]) { // 如果指定的首先展示的页的页数大于总页数,直接return
NSLog(@"Wrong initialPageIndex received: %ld",(long)initialPageIndex);
return;
} self.currentPageIndex = initialPageIndex;
self.alpha = ; if(self.superview != view) { // 如果不在指定的View 上则添加
[view addSubview:self];
} else {
[view bringSubviewToFront:self]; // 如果self 在指定的View 上,则把self 提到View 最前面
} [UIView animateWithDuration:duration animations:^{ // 在duration 内透明度变成 1
self.alpha = ;
} completion:^(BOOL finished) {
EAIntroPage *currentPage = _pages[self.currentPageIndex];
if(currentPage.onPageDidAppear) currentPage.onPageDidAppear(); // EAIntroPage 页面显示完毕的block ,当EAIntroPage 显示完毕执行onPageDidAppear block if ([(id)self.delegate respondsToSelector:@selector(intro:pageAppeared:withIndex:)]) { // 这行这个delegate 都是在首次显示完毕的时候执行 代理和block 同时存在,可以自由选择
[self.delegate intro:self pageAppeared:_pages[self.currentPageIndex] withIndex:self.currentPageIndex];
}
}];
}

另外在创建EAIntroPage 的时候用到了三个判断当前手机型号的宏,以此选择不同尺寸的启动图片。

 #define kDevice_Is_iPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO)
#define kDevice_Is_iPhone6 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(750, 1334), [[UIScreen mainScreen] currentMode].size) : NO)
#define kDevice_Is_iPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242, 2208), [[UIScreen mainScreen] currentMode].size) : NO)

UIPageController 部分是在EAIntroView 内部根据偏移量做self.pageControl.currentPage 和 当前的EAIntroPage 的改变。

以下代码是做每次切换EAIntroPage 时的淡入淡出:

 - (void)makePanelVisibleAtIndex:(NSUInteger)panelIndex{
[UIView animateWithDuration:0.3 animations:^{
for (int idx = ; idx < _pages.count; idx++) {
if (idx == panelIndex) {
[[self viewForPageIndex:idx] setAlpha:[self alphaForPageIndex:idx]];
} else {
if(!self.hideOffscreenPages) {
[[self viewForPageIndex:idx] setAlpha:];
}
}
}
}];
}

最后一页向左滑动退出引导的处理:

 - (void)finishIntroductionAndRemoveSelf {
if ([(id)self.delegate respondsToSelector:@selector(introDidFinish:)]) { // 启动图引导完毕的代理
[self.delegate introDidFinish:self];
} // Remove observer for rotation
[[NSNotificationCenter defaultCenter] removeObserver:self]; // 移除通知
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; // 结束设备转向通知 //prevent last page flicker on disappearing
self.alpha = ; //Calling removeFromSuperview from scrollViewDidEndDecelerating: method leads to crash on iOS versions < 7.0.
//removeFromSuperview should be called after a delay
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self removeFromSuperview]; // 调主线程,从父视图移除
});
}

SMPageControl github:https://github.com/Spaceman-Labs/SMPageControl

这个第三方控件使得UIPagecontrol 自定义使用起来更加自由方便。

 #pragma mark private M
+ (UIPageControl *)p_pageControl{
UIImage *pageIndicatorImage = [UIImage imageNamed:@"intro_dot_unselected"];
UIImage *currentPageIndicatorImage = [UIImage imageNamed:@"intro_dot_selected"]; if (!kDevice_Is_iPhone6 && !kDevice_Is_iPhone6Plus) {
CGFloat desginWidth = 375.0;//iPhone6 的设计尺寸
CGFloat scaleFactor = kScreen_Width/desginWidth;
pageIndicatorImage = [pageIndicatorImage scaleByFactor:scaleFactor]; // 缩放图片 NYXImagesKit NYXImagesKit 包含一组很有用的 UIImage 图像处理方法,包括 filtering, blurring, enhancing, masking, reflecting, resizing, rotating, saving. 同时也提供了一个 UIImageView 的之类,支持异步的从 URL 下载图像并显示 https://github.com/Nyx0uf/NYXImagesKit
currentPageIndicatorImage = [currentPageIndicatorImage scaleByFactor:scaleFactor];
} SMPageControl *pageControl = [SMPageControl new]; // SMPageControl 自定义UIPageControl的外观,包括形状、大小、间距等,也可以用图片代替UIPageControl上的小圆点。 https://github.com/Spaceman-Labs/SMPageControl
pageControl.pageIndicatorImage = pageIndicatorImage;
pageControl.currentPageIndicatorImage = currentPageIndicatorImage;
[pageControl sizeToFit];
return (UIPageControl *)pageControl;
}

其中在对SMPageControl pageIndicatorImage 处理的时候调用了一个功能强大图片处理库NYXImagesKit , 下篇会做详细讲解。

 

Coding源码学习第二部分(FunctionIntroManager.m)的更多相关文章

  1. Coding源码学习第一部分(AppDelegate.m)

    前言:在此首先感谢开源,感谢大神们的无私分享. Coding 的主页:https://coding.net/app#app-feature Coding 自己家的仓库:https://coding.n ...

  2. Coding源码学习第三部分(EaseStartView.m)

    首先接上篇的要做一个NSEnumerator 类的延展阅读. 枚举(NSEnumerator) (1)依附于集合类(NSArray,NSSet,NSDictionary),没有用来创建实例的接口. ( ...

  3. Coding源码学习第四部分(Masonry介绍与使用(二))

    接上篇,本篇继续对Masonry 进行学习,接上篇示例: (6)Masonry 布局实现iOS 计算器 - (void)exp4 { WS(weakSelf); // 申明区域,displayView ...

  4. Coding源码学习第四部分(Masonry介绍与使用(三))

    接上篇继续进行Masonry 的学习. (12)tableViewCell 布局 #import "TableViewController.h" #import "Tes ...

  5. Coding源码学习第四部分(Masonry介绍与使用(一))

    Masonry GitHub:https://github.com/SnapKit/Masonry Masonry是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了 ...

  6. Hadoop源码学习笔记(1) ——第二季开始——找到Main函数及读一读Configure类

    Hadoop源码学习笔记(1) ——找到Main函数及读一读Configure类 前面在第一季中,我们简单地研究了下Hadoop是什么,怎么用.在这开源的大牛作品的诱惑下,接下来我们要研究一下它是如何 ...

  7. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

随机推荐

  1. C#的四种Timer介绍

    一.Timer的几个类别 1.System.Threading.Timer 2.System.Timers.Timer 3.System.Windows.Forms.Timer 4.System.Wi ...

  2. asp.net mvc bundle中数组超出索引

    在使用bundle 来加载css的时候报错了, @Styles.Render("~/bundles/appStyles") 第一反应 以为是的css 太多了,可是当我这个style ...

  3. Eclipse 快捷键 (应用中自己总结)

    调试快捷键: 1: resume(F8) 调试中用来直接跳到下一个断点 2:  用来结束JVM 3:step into (F5)跳入函数 4: step over (F6)单步执行 5:step re ...

  4. iOS - GeoCoder 地理编码

    前言 NS_CLASS_AVAILABLE(10_8, 5_0) @interface CLGeocoder : NSObject 地理编码 地名 -> 经纬度 等具体位置数据信息.根据给定的位 ...

  5. I2C总线(异步)

    起始位与停止位的定义: 起始信号:当SCL为高期间,SDA由高到低的跳变:启动信号是一种电平跳变时序信号,而不是一个电平信号. 停止信号:当SCL为高期间,SDA由低到高的跳变:停止信号也是一种电平跳 ...

  6. 利用Object.prototype.toString方法,实现比typeof更准确的type校验

    Object.prototype.toString方法返回对象的类型字符串,因此可以用来判断一个值的类型. 调用方法: Object.prototype.toString.call(value) 不同 ...

  7. Java多线程同步 synchronized 关键字的使用

    代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A, ...

  8. PHPStorm技巧篇 -- 观感优化

    (1)设置默认显示行号 (2)设置自动换行 (3)去除代码下划线(拼写检测) 优化说明:自动换行和显示行号字面意思很好理解,下划线说明一下,phpstorm默认对代码进行拼写校验,即对于不符合英文单词 ...

  9. JQuery_DOM 节点操作之包裹节点

    jQuery 提供了一系列方法用于包裹节点,那包裹节点是什么意思呢?其实就是使用字符串代码将指定元素的代码包含着的意思. <script type="text/javascript&q ...

  10. HDFS 核心原理

    HDFS 核心原理 2016-01-11 杜亦舒 HDFS(Hadoop Distribute File System)是一个分布式文件系统文件系统是操作系统提供的磁盘空间管理服务,只需要我们指定把文 ...