iOS走近商城APP(四 runloop应用 获取通讯录并
开篇
转眼又要过年了,我的程序员生涯默默的又过了一年,年终篇就先闲扯几句。从接触简书到写第一篇到现在差不多也将近一年了,简书的布局风格确实是赏心悦目,习惯了这种写法之后,再看以前的博客感觉好乱,哈哈。写了几篇文章,感觉有实用的也有的感觉回头看看比较水,希望来年再接再厉写一点干货来继续沉淀自己吧。
商城系列文章:
iOS走近商城APP(一)
iOS走近商城APP(二 购物车常用控件)
iOS走近商城APP(三 WKWebView 商品规格选择框架封装)
本篇文章主要内容
- runloop的应用
runloop的实际应用
runloop的原理介绍 - 获取手机通讯录
iOS9 iOS8不同情况下的授权获取号码
对号码的显示 输入的处理
runloop的应用
实战往往是解释问题的有效途径,先上图
订单倒计时.png如上图所示,在tableview上或者滚动试图上用到定时器的情景还是比较常见的。在tableview加上定时器很简单
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(refreshLessTime) userInfo:@"" repeats:YES];
但是如果只是这么加上的话,会发现其实在滚动试图滚动,或者tableview滑动的时候,定时器是停止的,解决办法如下
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(refreshLessTime) userInfo:@"" repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
那么重点来了,为什么要这么做,runloop都包含什么呢?
Run loops是线程的基础架构部分,Cocoa和CoreFundation都提供了run loop对象方便配置和管理线程的run loop。
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
每个线程,包括程序的主线程(main thread)都有与之相应的run loop对象。主线程是默认启动的,因此第一次创建定时器的方法,虽然没有写,但是由于是在主线程注册的定时器,因此已经加入到runloop中。
其实相当于以下代码,默认加在了NSDefaultRunLoopMode中,
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(refreshLessTime) userInfo:@"" repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
那么NSDefaultRunLoopMode 与 NSRunLoopCommonModes有什么区别呢?
在runloop中系统默认注册了5个model
- kCFRunLoopDefaultMode(NSDefaultRunLoopMode)
默认 Mode,主线程一般都是在这个 Mode 下。 - UITrackingRunLoopMode
界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。 - UIInitializationRunLoopMode
在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。 - GSEventReceiveRunLoopMode
接受系统事件的内部 Mode,通常用不到。 - kCFRunLoopCommonModes
不常用
我们根据不同的情况来选择吧定时器加入到model中,也明白了为什么滚动的时候定时器停止,因为默认的时候是加在NSDefaultRunLoopMode上的,但是当tableview或者滚动试图滚动的时候触发的是UITrackingRunLoopMode所以定时器停止了,只有滚动停止切换到NSDefaultRunLoopMode上时定时器继续。
但是为什么最后加到了NSRunLoopCommonModes一样解决了问题呢?而且这里的5中model并没有NSRunLoopCommonModes,NSRunLoopCommonModes是什么呢?
NSRunLoopCommonModes并不是一种Mode,而是一种特殊的标记,包含三种mode(kCFRunLoopDefaultMode、NSTaskDeathCheckMode、UITrackingRunLoopMode),添加到NSRunLoopCommonModes中的还没有执行的任务,会在mode切换时,再次添加到当前的mode中,这样就能保证不管当前runloop切换到哪一个mode,任务都能正常执行。并且被添加到NSRunLoopCommonModes中的任务会存储在runloop 的commonModeItems中。
表情.jpg
一句代码却包含这么多,至于更深入的底层的知识有空大家自己研究一下吧,进行下一个部分。
获取通讯录并处理
首先要设置获取通讯录的权限,如下图
权限设置.png由于系统的不同获取权限以及协议方法的不同,要进行系统的判断,根据iOS9,以及iOS8做出不同的处理。
导入需要的头文件
#import <AddressBookUI/AddressBookUI.h> //iOS8
#import <ContactsUI/ContactsUI.h> //iOS9
遵循的代理
<CNContactPickerDelegate,ABPeoplePickerNavigationControllerDelegate>
授权部分的代码
if(UMSYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"9.0")) {
//让用户给权限,没有的话会被拒
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if (status == CNAuthorizationStatusNotDetermined) {
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (error) {
}else
{
CNContactPickerViewController * picker = [CNContactPickerViewController new];
picker.delegate = self;
picker.displayedPropertyKeys = @[CNContactPhoneNumbersKey];//只显示手机号
[self presentViewController: picker animated:YES completion:nil];
}
}];
}
if (status == CNAuthorizationStatusAuthorized) {//有权限时
CNContactPickerViewController * picker = [CNContactPickerViewController new];
picker.delegate = self;
picker.displayedPropertyKeys = @[CNContactPhoneNumbersKey];
[self presentViewController: picker animated:YES completion:nil];
}
else{
[SVProgressHUD showInfoWithStatus:@"您未开启通讯录权限,请前往设置中心开启"];
}
}else{
__weak typeof(self)weakSelf = self;
ABAddressBookRef bookref = ABAddressBookCreateWithOptions(NULL, NULL);
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
/*kABAuthorizationStatusNotDetermined = 0, // 未进行授权选择
kABAuthorizationStatusRestricted, // 未授权,且用户无法更新,如家长控制情况下
kABAuthorizationStatusDenied, // 用户拒绝App使用
kABAuthorizationStatusAuthorized // 已授权,可使用*/
if (status == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(bookref, ^(bool granted, CFErrorRef error) {
if (error) {
}
if (granted) {
NSLog(@"授权成功");
ABPeoplePickerNavigationController *peosonVC = [[ABPeoplePickerNavigationController alloc] init];
peosonVC.peoplePickerDelegate = weakSelf;
peosonVC.displayedProperties = @[[NSNumber numberWithInt:kABPersonPhoneProperty]];
[weakSelf presentViewController:peosonVC animated:YES completion:nil];
}
});
}
if (status == kABAuthorizationStatusAuthorized) {
ABPeoplePickerNavigationController *peosonVC = [[ABPeoplePickerNavigationController alloc] init];
peosonVC.peoplePickerDelegate = weakSelf;
peosonVC.displayedProperties = @[[NSNumber numberWithInt:kABPersonPhoneProperty]];
[weakSelf presentViewController:peosonVC animated:YES completion:nil];
}else
{
[SVProgressHUD showInfoWithStatus:@"您未开启通讯录权限,请前往设置中心开启"];
}
}
如果走的iOS9.0及其以上走的是上面的代码,iOS8走的是else中的代码。获取通讯录后的处理的协议方法如下
#pragma mark - 点击某个联系人的某个属性(property)时触发并返回该联系人属性(contactProperty)。 iOS 9 以后写法
//只实现该方法时,可以进入到联系人详情页面(如果predicateForSelectionOfProperty属性没被设置或符合筛选条件,如不符合会触发默认操作,即打电话,发邮件等)。
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty {
NSLog(@"%@",contactProperty);
CNContact *contact = contactProperty.contact;
NSLog(@"givenName: %@, familyName: %@", contact.givenName, contact.familyName);
if (![contactProperty.value isKindOfClass:[CNPhoneNumber class]]) {
[SVProgressHUD showInfoWithStatus:@"请选择11位手机号"];
return;
}
CNPhoneNumber *phoneNumber = contactProperty.value;
NSString * Str = phoneNumber.stringValue;
NSCharacterSet *setToRemove = [[ NSCharacterSet characterSetWithCharactersInString:@"0123456789"]invertedSet];
NSString *phoneStr = [[Str componentsSeparatedByCharactersInSet:setToRemove]componentsJoinedByString:@""];
if (phoneStr.length != 11) {
[SVProgressHUD showInfoWithStatus:@"请选择11位手机号"];
return;
}
NSLog(@"-=-=%@",phoneStr);
//号码赋值用于提交后台
phoneNumberStr = phoneStr;
//号码处理用于展示
NSMutableString * str = [[NSMutableString alloc ] initWithString:phoneStr];
[str insertString:@"-" atIndex:3];
[str insertString:@"-" atIndex:8];
phoneStr = str;
self.phoneNumberFiled.text = phoneStr ;
}
#pragma mark - ios8 选中联系人的某个属性的时候调用 7.0以下 不做考虑
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
// 获取该联系人多重属性--电话号
ABMutableMultiValueRef phoneMulti = ABRecordCopyValue(person, kABPersonPhoneProperty);
// 获取该联系人的名字,简单属性,只需ABRecordCopyValue取一次值
ABMutableMultiValueRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *name = (__bridge NSString *)(firstName);
// 点击某个联系人电话后dismiss联系人控制器,并回调点击的数据
[self dismissViewControllerAnimated:YES completion:^{
// 从多重属性——电话号中取值,参数2是取点击的索引
NSString *aPhone = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phoneMulti, ABMultiValueGetIndexForIdentifier(phoneMulti,identifier)) ;
// 去掉电话号中的 "-"
aPhone = [aPhone stringByReplacingOccurrencesOfString:@"-" withString:@"" ];
if (aPhone.length != 11) {
[SVProgressHUD showInfoWithStatus:@"请选择11位手机号"];
return;
}
//号码赋值用于提交后台
phoneNumberStr = aPhone;
//号码处理用于展示
NSMutableString * str = [[NSMutableString alloc ] initWithString:aPhone];
[str insertString:@"-" atIndex:3];
[str insertString:@"-" atIndex:8];
aPhone = str;
self.phoneNumberFiled.text = aPhone;
}];
[peoplePicker dismissViewControllerAnimated:YES completion:nil];
}
显示设置.png
其中对于获取通讯录后显示如图所示的设置具体代码为
//号码处理用于展示
NSMutableString * str = [[NSMutableString alloc ] initWithString:aPhone];
[str insertString:@"-" atIndex:3];
[str insertString:@"-" atIndex:8];
aPhone = str;
但是选中设置之后同时要考虑输入框直接输入的样式设置以及删除时的问题设置,输入框的代码如下:
首先给输入框添加事件
[_phoneNumberFiled addTarget:self action:@selector(textFieldDidEditing:) forControlEvents:UIControlEventEditingChanged];
具体方法的实现如下
-(void)textFieldDidEditing:(UITextField *)textField{
if (textField == self.phoneNumberFiled) {
if (textField.text.length > i) {
if (textField.text.length == 4 || textField.text.length == 9 ) {//输入
NSMutableString * str = [[NSMutableString alloc ] initWithString:textField.text];
[str insertString:@"-" atIndex:(textField.text.length-1)];
textField.text = str;
}if (textField.text.length >= 13 ) {//输入完成
textField.text = [textField.text substringToIndex:13];
[textField resignFirstResponder];
}
i = textField.text.length;
}else if (textField.text.length < i){//删除
if (textField.text.length == 4 || textField.text.length == 9) {
textField.text = [NSString stringWithFormat:@"%@",textField.text];
textField.text = [textField.text substringToIndex:(textField.text.length-1)];
}
i = textField.text.length;
}
}
}
这样就实现了输入自动显示 为 012 - 3456 - 7890的样式了,自动添加"-',删除的时候同样做出自动处理。
这样就实现我们想要的效果了,哈哈。
感谢
在写的时候也翻阅了许多文章学习,怕有表达不当误导大家,再次谢谢各位乐于分享的作者了。
参考文章举例
深入理解RunLoop
RunLoop 总结:RunLoop的应用场景(二)
后记
文章就到这里了,估计也是今年的最后一篇了,回家,买房,亲戚...各种琐事恐怕中间没时间再写了,哈哈。希望来年写更多有用的,同时希望自己和大家一样技术有很大进步,默默给自己和大家说一句春节快乐!