Coding源码学习第一部分(AppDelegate.m)
前言:在此首先感谢开源,感谢大神们的无私分享。
Coding 的主页:https://coding.net/app#app-feature
Coding 自己家的仓库:https://coding.net/u/coding/p/Coding-iOS/git
Coding GitHub仓库:https://github.com/Coding/Coding-iOS
首先可以根据官方提示下载并运行代码。
第一部分首先逐行分析AppDelegate.m,对Coding 的启动过程以及一些中断处理做一个大致的了解。
顶部的宏定义:
1 #define UMSYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) // NSNumericSearch 按照字符串里的数字为依据,算出顺序 // NSOrderedAscending 升序 http://blog.sina.com.cn/s/blog_916e0cff0102uyz1.html 2 #define _IPHONE80_ 80000
第一个宏对当前系统版本做比较判断用了一个字符串比较函数:
NSString 比较函数
1 - (NSComparisonResult)compare:(NSString *)string; // 这个方法可以用来比较两个字符串内容的大小,对两个字符串逐个字符地进行比较ASCII值,返回NSComparisonResult作为比较结果 // http://blog.csdn.net/qq_30513483/article/details/51536443 2 - (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask; // 按mask的方式比较两个字符串 3 - (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)compareRange; // 在指定的compareRange 按mask 的方式比较字符串 4 - (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)compareRange locale:(nullable id)locale; // 在指定的compareRange 按mask 的方式和本地化语言环境比较字符串 5 - (NSComparisonResult)caseInsensitiveCompare:(NSString *)string; // 不区分大小写比较字符串 6 - (NSComparisonResult)localizedCompare:(NSString *)string; // 本地化比较字符串 有关本地化的两篇博客 http://my.oschina.net/u/1049180/blog/215695 http://zasoft.blog.163.com/blog/static/205215176201301703129935/ 7 - (NSComparisonResult)localizedCaseInsensitiveCompare:(NSString *)string; // 不区分大小以及本地化比较字符串 8 - (NSComparisonResult)localizedStandardCompare:(NSString *)string NS_AVAILABLE(10_6, 4_0); // 本地化标准字符串比较,返回升序降序相等 9 - (BOOL)isEqualToString:(NSString *)aString; // 比较两个字符串内容是否相同
NSStringCompareOptions 枚举的值(两个字符串比较的方式)
1 typedef NS_OPTIONS(NSUInteger, NSStringCompareOptions) { 2 3 NSCaseInsensitiveSearch = 1, // 不区分大小写比较 4 5 NSLiteralSearch = 2,//区分大小写比较 6 NSBackwardsSearch = 4, //从字符串末尾开始搜索 7 8 NSAnchoredSearch = 8, //搜索限制范围的字符串 9 10 NSNumericSearch = 64, //按照字符串里的数字为依据,算出顺序 11 12 NSDiacriticInsensitiveSearch NS_ENUM_AVAILABLE(10_5, 2_0) = 128,//忽略 "-" 符号的比较 13 NSWidthInsensitiveSearch NS_ENUM_AVAILABLE(10_5, 2_0) = 256, //忽略字符串的长度,比较出结果 14 NSForcedOrderingSearch NS_ENUM_AVAILABLE(10_5, 2_0) = 512, //忽略不区分大小写比较的选项,并强制返回 NSOrderedAscending 或者 NSOrderedDescending 15 NSRegularExpressionSearch NS_ENUM_AVAILABLE(10_7, 3_2) = 1024 //只能应用于 rangeOfString:..., stringByReplacingOccurrencesOfString:...和 replaceOccurrencesOfString:... 方法。使用通用兼容的比较方法,如果设置此项,可以去掉 NSCaseInsensitiveSearch 和 NSAnchoredSearch 16 };
NSComparisonResult 枚举值(比较返回的结果)
1 typedef NS_ENUM(NSInteger, NSComparisonResult) {NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending}; // 升序 相等 降序
字符串比较里面有一个本地化比较字符,延展了一下,搜集了这几篇博客 http://yulingtianxia.com/blog/2014/10/02/localizing-with-xcode-6/ http://blog.csdn.net/shenjie12345678/article/details/45670771
做iOS的本地化操作,本地化主要包括本地化应用程序名称、本地化字符串、本地化图片、本地化其他文件.当前暂时接触不到本地化操作,所以只是写了一小段代码测试了一下.
1 // 这是系统提供的几个相关的宏定义 2 #define NSLocalizedString(key, comment) \ 3 [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] 4 #define NSLocalizedStringFromTable(key, tbl, comment) \ 5 [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)] 6 #define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \ 7 [bundle localizedStringForKey:(key) value:@"" table:(tbl)] 8 #define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) \ 9 [bundle localizedStringForKey:(key) value:(val) table:(tel)] 10 // 测试了一下NSLocalizedString(key, comment) 11 self.lab = ({ 12 UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(50, 100, 100, 30)]; 13 label.textColor = [UIColor redColor]; 14 label.backgroundColor = [UIColor grayColor]; 15 label.font = [UIFont boldSystemFontOfSize:17]; 16 label.textAlignment = NSTextAlignmentCenter; 17 // label.text = [NSString stringWithFormat:@"本地化处理"]; 18 label.text = NSLocalizedString(@"key", nil); // 本地化字符串 19 [self.view addSubview:label]; 20 label; 21 });
1 // Localizable.strings(English) 2 "key" = "Hello world"; 3 CFBundleDisplayName = "Program"; 4 // Localizable.strings(Base) 5 "key" = "你好 世界"; 6 CFBundleDisplayName = "程序";
宏定义下面是两个在DEBUG 模式使用的第三方工具
1 #if DEBUG // 如果是DEBUG 状态下进行条件编译 2 #import <FLEX/FLEXManager.h> // FLEX (Flipboard Explorer) 是 iOS 开发的应用内调试和探测工具集。运行的时候,FLEX 提供一个浮动在应用之上的工具栏,用户可以查看和修改应用的每一个地方。https://github.com/Flipboard/FLEX // http://www.cocoachina.com/ios/20140728/9259.html // http://itony.me/774.html 3 #import "RRFPSBar.h" // 在状态栏上显示FPS low 最低FPS avg 平均FPS 默认不显示avg FPS // https://github.com/RolandasRazma/RRFPSBar // http://mobile.51cto.com/hot-418125.htm 4 #endif
RRFPSBar 的使用非常简单如下,FLEX 的使用还没有看到,后面会做相应的补充。
1 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 2 { 3 #if DEBUG 4 [[RRFPSBar sharedInstance] setShowsAverage:YES]; // low 最低FPS avg 平均FPS 默认不显示avg FPS 5 [[RRFPSBar sharedInstance] setHidden:NO]; 6 #endif 7 }
先略过#import 引入的各个类,看下面的@implementation AppDelegate 以下所有的代码都是按顺序依次细读的
1 - (void)registerPush{ 2 float sysVer = [[[UIDevice currentDevice] systemVersion] floatValue]; 3 if(sysVer < 8){ 4 [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)]; // iOS 8 以下的远程推送的注册 包括弹框提示 Icon 角标提示 声音提示 之前做过一个本地的闹钟提醒,声音提示用自己录的声音而且是每一个提醒都可以自定义声音,主目录不能写进去录音文件郁闷了很久 5 }else{ 6 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= _IPHONE80_ // 条件编译指令 7 UIMutableUserNotificationCategory *categorys = [[UIMutableUserNotificationCategory alloc] init]; 8 UIUserNotificationSettings *userSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert 9 categories:[NSSet setWithObject:categorys]]; 10 [[UIApplication sharedApplication] registerUserNotificationSettings:userSettings]; 11 [[UIApplication sharedApplication] registerForRemoteNotifications]; 12 #endif 13 } 14 } 15 // 注册远程通知,当然等等这些不及一个"不允许"按钮
这里大概是获取一个用户标识的意思
1 #pragma mark - UserAgent 2 - (void)registerUserAgent{ 3 struct utsname systemInfo; 4 uname(&systemInfo); 5 NSString *deviceString = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; // c 语言字符串转成oc 字符串 6 NSString *userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", 7 [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleExecutableKey] ? : [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleIdentifierKey], 8 (__bridge id)CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), kCFBundleVersionKey) ? : [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleVersionKey], 9 deviceString, 10 [[UIDevice currentDevice] systemVersion], 11 ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0f)]; // 打印结果 Coding_iOS/3.8.1.201603152130 (x86_64; iOS 9.3; Scale/2.00) //kCFBundleExecutableKey 获取项目名称 //kCFBundleVersionKey 获取项目版本号 // CFBundleDisplayName app名称 // CFBundleShortVersionString app版本 //CFBundleVersion app build版本 12 // DebugLog(@"infoDictionary = %@", [[NSBundle mainBundle] infoDictionary]); 13 NSDictionary *dictionary = @{@"UserAgent" : userAgent};//User-Agent 14 [[NSUserDefaults standardUserDefaults] registerDefaults:dictionary]; // http://www.cnblogs.com/letmefly/archive/2012/07/14/2591735.html 15 }
1 - (void)registerDefaults:(NSDictionary<NSString *, id> *)registrationDictionary; // 该方法第一次使用,保证第一次输入的key 的值不会改变
1 #pragma mark lifeCycle 2 // 告诉代理启动基本完成程序准备开始运行 3 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 4 { 5 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 6 // Override point for customization after application launch. 7 self.window.backgroundColor = [UIColor whiteColor]; 8 // 网络 9 [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES]; // 网络活动指示器状态栏上的小菊花 10 [[AFNetworkReachabilityManager sharedManager] startMonitoring]; // 开启网络监听 11 //[self performSelector:@selector(AFNetworkingReachabilityManagerAction:) withObject:nil afterDelay:0.35]; 12 13 // sd加载的数据类型 14 [[[SDWebImageManager sharedManager] imageDownloader] setValue:@"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" forHTTPHeaderField:@"Accept"]; // sure 15 16 // 设置导航条样式 17 [self customizeInterface]; 18 // 显示状态栏 19 [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade]; 20 21 // UIWebView 的 User-Agent 22 [self registerUserAgent]; 23 24 // 设置控制器 25 if ([Login isLogin]) { // 如果登录过了,记忆登录 26 [self setupTabViewController]; 27 }else{ 28 [UIApplication sharedApplication].applicationIconBadgeNumber = 0; // Icon 角标设置为 0 29 [self setupIntroductionViewController]; // 从启动引导页启动 30 } 31 32 [self.window makeKeyAndVisible]; // 走到这里会把RootViewController 配置一遍 33 34 [FunctionIntroManager showIntroPage]; // 方法执行后直接return 了,并没有做什么事情,另外一个简单的启动引导页面,只在中秋节的时候启动用的。在指定的版本中有一个在中秋节期间的时候特别制作的启动页面 35 36 EaseStartView *startView = [EaseStartView startView]; // 设置正常启动的view 和启动动画 这里面有app 的第一个网络请求,先获取到了启动图的信息,再判断如果是WiFi 的话根据启动图的信息下载启动图片 37 @weakify(self); 38 // startView 开始动画和动画完成后的block 实现 39 [startView startAnimationWithCompletionBlock:^(EaseStartView *easeStartView) { 40 @strongify(self); 41 /** 42 * 启动动画执行完毕后执行下面的方法 43 */ 44 [self completionStartAnimationWithOptions:launchOptions]; 45 }]; 46 47 #if DEBUG 48 [[RRFPSBar sharedInstance] setShowsAverage:YES]; // low 最低FPS avg 平均FPS 默认不显示avg FPS 49 [[RRFPSBar sharedInstance] setHidden:NO]; 50 #endif 51 52 return YES; 53 }
首先是AFNetWorking 有一个小技巧,如果在程序启动的时候想获取当前的网络类型 [AFNetworkReachabilityManager sharedManager].networkReachabilityStatus 要做一个延时操作,由于检测网络有一定的延迟,所以如果启动app立即去检测调用[AFNetworkReachabilityManager sharedManager].networkReachabilityStatus 有可能得到的是status == AFNetworkReachabilityStatusUnknown;但是此时明明是有网的,建议在收到监听网络状态回调以后再取[AFNetworkReachabilityManager sharedManager].networkReachabilityStatus。 http://www.open-open.com/lib/view/open1423036162561.html
1 - (void)completionStartAnimationWithOptions:(NSDictionary *)launchOptions{ 2 // 启动图的动画执行完毕主要做了许多第三方SDK 的处理 3 // 1 首先判断是否是用户登录状态,判断的方式是查看NSUserDefaults 的plist 文件里面取数据,如果当前有用户登录,再判断当前的launchOptions 里面是否有推送消息,如果有推送消息根据 UIApplicationState 做相应的页面跳转 4 // 2 初始化友盟统计模块 5 [MobClick startWithAppkey:kUmeng_AppKey reportPolicy:BATCH channelId:nil]; 6 // 3 注册谷歌统计,由于谷歌统计(Google Analytics)的SDK 一直下载不下来,FQ也下载不下来,就从ProFile 里面把它删除了,有趣的是用Safari 直接从官方能下载 https://accounts.google.com/ServiceLogin?service=analytics&passive=1209600&continue=https://analytics.google.com/analytics/web/?authuser%3D0%23management%2FSettings%2Fa76403314w115061617p120271020%2F%253Fm.page%253DTrackingCode%2F&followup=https://analytics.google.com/analytics/web/?authuser%3D0#identifier 7 8 http://www.cocoachina.com/industry/20140108/7674.html 9 [self registerGA]; // 谷歌统计注释掉了 10 // 4 友盟第三方登录分享注册 11 [UMSocialData setAppKey:kUmeng_AppKey]; 12 // 5 设置微信AppId 和 url 地址 13 [UMSocialWechatHandler setWXAppId:kSocial_WX_ID appSecret:kSocial_WX_Secret url:[NSObject baseURLStr]]; // 微信 14 // 6 设置分享到手机QQ 和QQ 空间的应用ID 15 [UMSocialQQHandler setQQWithAppId:kSocial_QQ_ID appKey:kSocial_QQ_Secret url:[NSObject baseURLStr]]; // QQ 16 // 7 印象笔记的SDK 的初始化 17 [ENSession setSharedSessionConsumerKey:kSocial_EN_Key consumerSecret:kSocial_EN_Secret optionalHost:nil]; // 印象笔记 18 // 8 新浪微博SDK 来处理SSO 授权 19 [UMSocialSinaSSOHandler openNewSinaSSOWithRedirectURL:kSocial_Sina_RedirectURL]; 20 // 9 设置一个新浪微博官方账号 默认勾选关注 21 [UMSocialConfig setFollowWeiboUids:@{UMShareToSina : kSocial_Sina_OfficailAccount}];//设置默认关注官方账号 22 // 10 友盟设置分享完成时的"发送完成"或者分享错误的提示 23 [UMSocialConfig setFinishToastIsHidden:YES position:UMSocialiToastPositionCenter]; 24 // 11 友盟设置 设置导航栏,包括导航栏的UINavigationBar,返回按钮,关闭按钮,发送按钮,刷新按钮和中间的UINavigationItem的样式 25 // 12 信鸽推送 26 /** 27 * 初始化信鸽 28 * @param appId - 通过前台申请的应用ID 29 * @param appKey - 通过前台申请的appKey 30 * @return none 31 */ 32 [XGPush startApp:kXGPush_Id appKey:kXGPush_Key]; 33 // 当前有用户登录的时候注册推送 34 [Login setXGAccountWithCurUser]; 35 // 注销之后需要再次注册前的准备 36 @weakify(self); 37 // block 实现 38 void (^successCallback)(void) = ^(void){ 39 //如果变成需要注册状态 40 /** 41 * 判断当前是否是已注销状态 42 * @param none 43 * @return none 44 */ 45 if(![XGPush isUnRegisterStatus] && [Login isLogin]){ 46 @strongify(self); 47 [self registerPush]; 48 } 49 }; 50 51 /** 52 * 如果注销过信鸽,需要再次注册push服务前的准备 53 * @param successCallback 初始化之后的回调,如果需要即刻恢复push服务,registerPush在此回调中调用 54 * @return none 55 */ 56 [XGPush initForReregister:successCallback]; 57 58 //[XGPush registerPush]; //注册Push服务,注册后才能收到推送 59 60 //推送反馈(app不在前台运行时,点击推送激活时。统计而已) 61 /** 62 * 在didFinishLaunchingWithOptions中调用,用于推送反馈.(app没有运行时,点击推送启动时) 63 * @param userInfo 64 * @return none 65 */ 66 [XGPush handleLaunching:launchOptions]; 67 }
下面是应用程序进入非活动状态代理,在此期间应用程序接收消息和事件,比如正在运行时手机来电话了
1 - (void)applicationWillResignActive:(UIApplication *)application 2 { 3 // application 做的处理,做了下面两件事情 4 [[ImageSizeManager shareManager] save]; // 保存图片 5 [[Tweet tweetForSend] saveSendData]; // 保存要发送的朋友圈的草稿 6 }
当应用程序进入活动状态,这个刚好和上面的方法相反
1 - (void)applicationDidBecomeActive:(UIApplication *)application 2 { 3 if ([Login isLogin]) { // 如果当前有用户登录的话 4 [[UnReadManager shareManager] updateUnRead]; // 刷新Icon 角标数字 去请求未读信息的数目 5 UIViewController *presentingVC = [BaseViewController presentingVC]; // 这个方法是获得当前的控制器 后面会单独讲解和UIWindow 相关的操作 6 SEL selector = NSSelectorFromString(@"refresh"); // 这个refresh 方法,如果当前的控制器有这个方法,刷新数据 如果是信息控制器的话就对它进行刷新(各种信息的数目进行刷新) 7 if ([presentingVC isKindOfClass:NSClassFromString(@"Message_RootViewController")] 8 && [presentingVC respondsToSelector:selector]) { 9 #pragma clang diagnostic push 10 #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 11 /** 12 * 如果激活时是Message_RootViewController 控制器的话,presentingVC refresh 13 * 如果当前是Message_RootViewController 控制器的话且有refresh 方法,就执行refresh 方法 14 */ 15 [presentingVC performSelector:selector]; 16 #pragma clang diagnostic pop 17 } 18 } 19 }
下面是远程推送的代理事件
1 #pragma mark - XGPush Message 2 // 注册远程推送的时候获取DeviceToken 3 - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken 4 { 5 /** 6 * 注册设备 7 * @param deviceToken - 通过app delegate的didRegisterForRemoteNotificationsWithDeviceToken回调的获取 8 * @return 获取的deviceToken字符串 9 */ 10 NSString * deviceTokenStr = [XGPush registerDevice:deviceToken]; 11 DebugLog(@"deviceTokenStr : %@", deviceTokenStr); 12 } 13 14 // application 收到远程推送信息 15 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 16 { 17 DebugLog(@"didReceiveRemoteNotification-userInfo:-----\n%@", userInfo); 18 /** 19 * 在didReceiveRemoteNotification中调用,用于推送反馈。(app在运行时) 20 * @param userInfo 苹果apns的推送信息 21 * @return none 22 */ 23 [XGPush handleReceiveNotification:userInfo]; 24 // 对远程推送的处理,跳转控制器或者网页或者刷新数据 25 [BaseViewController handleNotificationInfo:userInfo applicationState:[application applicationState]]; 26 }
DebugLog() 是一个打印信息的宏
1 #define DebugLog(s, ...) NSLog(@"%s(%d): %@", __FUNCTION__, __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__]) // 使用标识符__VA_ARGS_来表示多个参数,在宏的名称中则使用(...) // 将"##"放在","和参数之间,那么如果参数留空的话,那么"##"前面的","就会删掉,从而防止编译错误
下面是控制器的创建
1 #pragma mark - Methods Private 2 // 登录控制器 3 - (void)setupLoginViewController{ 4 LoginViewController *loginVC = [[LoginViewController alloc] init]; 5 [self.window setRootViewController:[[BaseNavigationController alloc] initWithRootViewController:loginVC]]; 6 } 7 8 // 设置引导页控制器 9 - (void)setupIntroductionViewController{ 10 IntroductionViewController *introductionVC = [[IntroductionViewController alloc] init]; 11 // [self.window setRootViewController:[[BaseNavigationController alloc] initWithRootViewController:introductionVC]]; 12 [self.window setRootViewController:introductionVC]; 13 } 14 15 // 设置RootTabViewController 16 - (void)setupTabViewController{ 17 RootTabViewController *rootVC = [[RootTabViewController alloc] init]; 18 rootVC.tabBar.translucent = YES; 19 [self.window setRootViewController:rootVC]; 20 }
下面是导航条是设置这里要详细看一下
1 //设置Nav的背景色和title色 2 - (void)customizeInterface { 3 UINavigationBar *navigationBarAppearance = [UINavigationBar appearance]; 4 [navigationBarAppearance setBackgroundImage:[UIImage imageWithColor:[UIColor colorWithHexString:[NSObject baseURLStrIsTest]? @"0x3bbd79" : @"0x28303b"]] forBarMetrics:UIBarMetricsDefault]; // 横竖屏通用的bar 5 [navigationBarAppearance setTintColor:[UIColor whiteColor]];//返回按钮的箭头颜色 6 NSDictionary *textAttributes = @{ 7 NSFontAttributeName: [UIFont boldSystemFontOfSize:kNavTitleFontSize], 8 NSForegroundColorAttributeName: [UIColor whiteColor], 9 }; 10 [navigationBarAppearance setTitleTextAttributes:textAttributes]; 11 [[UITextField appearance] setTintColor:[UIColor colorWithHexString:@"0x3bbc79"]]; //设置UITextField的光标颜色 12 [[UITextView appearance] setTintColor:[UIColor colorWithHexString:@"0x3bbc79"]]; //设置UITextView的光标颜色 13 [[UISearchBar appearance] setBackgroundImage:[UIImage imageWithColor:kColorTableSectionBg] forBarPosition:UIBarPositionAny barMetrics:UIBarMetricsDefault]; // 横竖屏通用的bar 14 }
UIAppearance.h
引用的链接:
http://www.jianshu.com/p/8262ec74bfc7
http://www.cnblogs.com/salam/archive/2013/01/30/appearance.html
http://blog.csdn.net/yongyinmg/article/details/39927717
http://blog.csdn.net/shenjx1225/article/details/8552449
http://blog.csdn.net/comic0514/article/details/48228013
(1)哪些控件和类可以设置主题,只要是遵守了UIAppearance 协议的类,都可以设置主题。包括如下:
1.UIActivitiIndicatorView
2.UIBarButtonItem
3.UIBarItem
4.UINavgationBar
5.UIPopoverControll
6.UIProgressView
7.UISearchBar
8.UISegmentControll
9.UISlider
10.UISwitch
11.UITabBar
12.UITabBarItem
13.UIToolBar
14.UIView
15.UIViewController
查看UIView 的头文件:
1 NS_CLASS_AVAILABLE_IOS(2_0) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusEnvironment>
UIView 遵守UIAppearance 协议,即所有继承自UIView 的控件都可以设置主题。
不仅控件可以支持主题, UIBarItem 等遵守了UIAppearance 协议的类,也可以设置主题。
1 NS_CLASS_AVAILABLE_IOS(2_0) @interface UIBarButtonItem : UIBarItem <NSCoding> 2 NS_CLASS_AVAILABLE_IOS(2_0) @interface UIBarItem : NSObject <NSCoding, UIAppearance>
(2)哪些属性可以设置主题,遵守UIAppearance 协议的类可以设置主题,那哪些类的属性可以设置主题呢,通过主题对象设置属性的前提是:属性后面是否带有UI_APPEARANCE_SELECTOR 的方法,因为并不是所有的属性都可以设置主题,设置主题属性是有前提的,查看头文件可以知道,可以设置UITabBarItem 类, 发现setTitleTextAttributes: 可以设置UITabBarItem 文字主题:
1 @property (nonatomic, readwrite, assign) UIOffset titlePositionAdjustment NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR; 2 - (void)setTitleTextAttributes:(nullable NSDictionary<NSString *,id> *)attributes forState:(UIControlState)state NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR; 3 - (nullable NSDictionary<NSString *,id> *)titleTextAttributesForState:(UIControlState)state NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
注意:
初学者肯定会任意调用方法,大部分方法时无效的,如果调用时会抛出unknown selector 异常,那么如何查看你调用的方法时有效的呢,我们可以在此类的头文件中查看包含“UI_APPEARANCE_SELECTOR”常量的方法。只要是头文件中有“UI_APPEARANCE_SELECTOR”标记的方法都是可以用UIAppearance协议对象去调的。注意这些自定义方法都要在相应的对象显示之前调用,可以放到App启动后立刻配置,以后只要这个对象显示之前,就会设置相应的属性。
例如UIToolBar
它支持下列方法
@property(nonatomic,retain) UIColor *tintColor UI_APPEARANCE_SELECTOR;
- (void)setBackgroundImage:(UIImage *)backgroundImage forToolbarPosition:(UIToolbarPosition)topOrBottom barMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
- (UIImage *)backgroundImageForToolbarPosition:(UIToolbarPosition)topOrBottom barMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
- (void)setShadowImage:(UIImage *)shadowImage forToolbarPosition:(UIToolbarPosition)topOrBottom NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
- (UIImage *)shadowImageForToolbarPosition:(UIToolbarPosition)topOrBottom NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
(3)运用主题appearance ,是否会生效,何时会生效
主题会生效:先设置控件主题,后添加控件。添加控件时,添加的那一刻会检查主题,会根据主题设置控件=》 主题会生效
主题不会生效:先添加控件,后设置主题。控件已经添加,后设置主题,对以前的添加的控件不起作用了。
解决主题失效问题:将控件所在控制器的View从窗体移除,因为主题已经确定了,再将控件所在的控制器的View 添加到窗口上=》此时主题就起作用了。(扩展性不好,因为这样不能解决所有控制器上的控件的主题)
1 UIView *superView = self.view.superview; 2 [self.view removeFromSuperview]; 3 [superView addSubview:self.view];
获取到应用的Window 数组,然后遍历, 将View 删除,然后添加上即可
注意:控件是否销毁,要看是否有强指针指向它
1 // 刷新appearance 2 NSArray *windows = [UIApplication sharedApplication].windows; 3 for (UIView *subView in windows) { 4 [subView removeFromSuperview]; 5 [window addSubView:subView]; 6 }
(4)UIAppearance 实现原理
在通过UIAppearance 调用"UI_APPEARANCE_SELECTOR" 标记的方法来配置外观时,UIAppearance 实际上没有进行任何实际调用,而是把这个调用保存起来(在Objc 中可以用NSInvocation 对象来保存一个调用)。当实际的对象显示之前(添加到窗口上,drawRect:之前),就会对这个对象调用之前保存的调用。当这个setter 调用后,你的界面风格自定义就完成了。
(5)UIAppearance.h 文件方法
/* To customize the appearance of all instances of a class, send the relevant appearance modification messages to the appearance proxy for the class. For example, to modify the bar tint color for all UINavigationBar instances: [[UINavigationBar appearance] setBarTintColor:myColor]; Note for iOS7: On iOS7 the tintColor property has moved to UIView, and now has special inherited behavior described in UIView.h. This inherited behavior can conflict with the appearance proxy, and therefore tintColor is now disallowed with the appearance proxy. */ + (instancetype)appearance; // 统一全部改 /* To customize the appearances for instances of a class contained within an instance of a container class, or instances in a hierarchy, use +appearanceWhenContainedInInstancesOfClasses: for the appropriate appearance proxy. For example: [[UINavigationBar appearanceWhenContainedInInstancesOfClasses:@[[UISplitViewController class]]] setBarTintColor:myColor]; [[UINavigationBar appearanceWhenContainedInInstancesOfClasses:@[[UITabBarController class], [UISplitViewController class]]] setBarTintColor:myTabbedNavBarColor]; In any given view hierarchy the outermost appearance proxy wins. Specificity (depth of the chain) is the tie-breaker. In other words, the containment statement is treated as a partial ordering. Given a concrete ordering (actual subview hierarchy), we select the partial ordering that is the first unique match when reading the actual hierarchy from the window down. */ + (instancetype)appearanceWhenContainedIn:(nullable Class <UIAppearanceContainer>)ContainerClass, ... NS_REQUIRES_NIL_TERMINATION NS_DEPRECATED_IOS(5_0, 9_0, "Use +appearanceWhenContainedInInstancesOfClasses: instead") __TVOS_PROHIBITED; // 当出现在某个类的出现的时候才会改变 + (instancetype)appearanceWhenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes NS_AVAILABLE_IOS(9_0); + (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait NS_AVAILABLE_IOS(8_0); + (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait whenContainedIn:(nullable Class <UIAppearanceContainer>)ContainerClass, ... NS_REQUIRES_NIL_TERMINATION NS_DEPRECATED_IOS(8_0, 9_0, "Use +appearanceForTraitCollection:whenContainedInInstancesOfClasses: instead") __TVOS_PROHIBITED; + (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait whenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes NS_AVAILABLE_IOS(9_0);
当看第二个方法 // 当出现在某个类的出现的时候才会改变
第二个方法是当出现在某个类的出现时候才会改变:例如:
[[UIBarButtonItem appearanceWhenContainedIn:[UINavigationBar class], [UIPopoverController class], nil] setTintColor:myPopoverNavBarColor];
UIPopoverController 第一次见这个类,顺便学习了一下,下面做一些总结。
UIPopoverController.h
1.是iPad开发中常见的一种控制器(在iPhone上不允许使用)
跟其他控制器不一样的是,它直接继承自NSObject,并非继承自UIViewController
它只占用部分屏幕空间来呈现信息,而且显示在屏幕的最前面
2.使用步骤
要想显示一个UIPopoverController,需要经过下列步骤
(1)设置内容控制器
由于UIPopoverController直接继承自NSObject,不具备可视化的能力。因此UIPopoverController上面的内容必须由另外一个继承自UIViewController的控制器来提供,这个控制器称为“内容控制器”
(2)设置内容的尺寸
显示出来占据多少屏幕空间
(3)显示,即从哪个地方冒出来
http://www.cnblogs.com/oumygade/p/4313782.html
http://www.cnblogs.com/wendingding/p/3918204.html
AppDelegate.m 最后是回调的处理:
1 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation NS_DEPRECATED_IOS(4_2, 9_0, "Please use application:openURL:options:") __TVOS_PROHIBITED; // 该方法最iOS 9 以后已经被下面的方法取代,我之前在做支付宝支付的时候就遇到了这个问题,在 iOS 9 中该回调方法并不执行,要用下面的方法来做更好的兼容 2 - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options NS_AVAILABLE_IOS(9_0); // no equiv. notification. return NO if the application can't open for some reason
AppDelegate.m 先总结到这里,还有很多细节都没有提起,下面会逐步的做出分析。