通过Native.js访问iOS原生通讯录界面(ContactsUI/AddressBookUI)iOS14以上系统不可用

网上有关于Native.js访问Android原生通讯录的案例(可以参考:Android调用系统通讯录控件,native.js实现监听startActivityForResult后返回结果),但是关于iOS的一直没找到,所以决定自己写,写的过程中发现果然有坑。下面将一一道来,最后把Android和iOS的统一封装到了一个js文件中,可以很方便的使用(急需使用的小伙伴可以直接点击Demo链接下载:5+App demo链接 , uni-app demo链接)。
原创文章,欢迎转载.转载请注明出处: https://www.jianshu.com/p/b78b02d64472

iOS访问通讯录

在写NJS(Native.js)的代码之前,首先写了一份iOS访问通讯录的代码,以便根据这份代码翻译成对应的NJS代码。由于AddressBookUI在iOS9之后已经被废弃,当前已经是iOS12了,所以决定用iOS9之后的新框架ContactsUI。实现的代码如下:

//
//  ViewController.m
//  ContactsDemo
//
//  Created by xian on 2018/11/5.
//  Copyright © 2018年 xian. All rights reserved.
//

#import "ViewController.h"
#import <ContactsUI/ContactsUI.h>

@interface ViewController ()<CNContactPickerDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

#pragma mark - 访问通讯录按钮点击事件
- (IBAction)vistContacts:(UIButton *)sender {
    //通讯录控制器
    CNContactPickerViewController *contactPickerVC = [CNContactPickerViewController new];
    //设置代理
    //contactPickerVC.delegate = self;
    [contactPickerVC setDelegate:self];
    //从当前控制器present到通讯录控制器
    //[self presentViewController:contactPickerVC animated:YES completion:nil];
    //  先获取当前控制器(这样写为了和NJS代码对应)
    UIViewController *currentVC = [self viewControllerByView:self.view];
    [currentVC presentViewController:contactPickerVC animated:YES completion:nil];
}

#pragma - 根据view获取当前控制器
/*写这段代码的目的是为了在MUI项目中使用
 *因为在MUI项目中,要想获取当前页面的控制器,要通过webView来获取
 */
- (UIViewController *)viewControllerByView:(UIView *)view{
    while (view) {
        UIResponder *responder = [view nextResponder];
        if ([responder isKindOfClass:[UIViewController class]]) {
            return (UIViewController *)responder;
        }
        view = [view superview];
    }
    return nil;
}

#pragma mark - CNContactPickerDelegate 代理方法
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact{
    //姓名/公司
    NSMutableString *name = [NSMutableString string];
    //姓
    NSString *familyName = contact.familyName;
    //名
    NSString *givenName = contact.givenName;
    //公司
    NSString *organizationName = contact.organizationName;
    [name appendString:familyName];
    [name appendString:givenName];
    if (name.length <= 0) {
        [name appendString:organizationName];
    }
    //手机号码
    NSString *phoneNo = @"";
    NSArray *phoneNumbers = contact.phoneNumbers;
    if (phoneNumbers.count > 0) {
        //这里只取第一个手机号
        CNLabeledValue<CNPhoneNumber *> *phone = phoneNumbers.firstObject;
        CNPhoneNumber *phoneNumber = phone.value;
        phoneNo = phoneNumber.stringValue;
    }
    NSLog(@"姓名/公司:%@,手机号码:%@", name, phoneNo);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

NJS调用iOS原生通讯录(ContactsUI,仅支持模拟器)

有了上面iOS访问通讯录的OC代码,可以很轻松的翻译成对应的NJS代码,如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <title></title>
    <script src="js/mui.min.js"></script>
    <link href="css/mui.min.css" rel="stylesheet"/>
    <script type="text/javascript" charset="utf-8">
        mui.init();
        mui.plusReady(function(){
            mui("button")[0].addEventListener("tap", function(){
                //访问通讯录
                visitContacts();
            });
        });
        /**
         * 访问通讯录
         */
        function visitContacts(){
            var contactPickerVC = plus.ios.newObject("CNContactPickerViewController");
            //实现代理方法【- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact;】
            //同时生成遵守CNContactPickerDelegate协议的代理对象delegate
            var delegate = plus.ios.implements("CNContactPickerDelegate", {
                "contactPicker:didSelectContact:":function(picker, contact){
                    console.log(JSON.stringify(picker));
                    console.log(JSON.stringify(contact));
                    //姓名/公司
                    var name = "";
                    //姓氏
                    var familyName = contact.plusGetAttribute("familyName");
                    //名字
                    var givenName = contact.plusGetAttribute("givenName");
                    //公司
                    var organizationName = contact.plusGetAttribute("organizationName");
                    name = familyName+givenName;
                    if (name.length <= 0) {
                        name = organizationName;
                    }
                    //电话号码
                    var phoneNo = "";
                    var phoneNumbers = contact.plusGetAttribute("phoneNumbers");
                    if (phoneNumbers.plusGetAttribute("count") > 0) {
                        var phone = phoneNumbers.plusGetAttribute("firstObject");
                        var phoneNumber = phone.plusGetAttribute("value");
                        phoneNo = phoneNumber.plusGetAttribute("stringValue");
                    }
                    console.log("姓名/公司:"+name+"  手机号码:"+phoneNo);
                    mui.toast("姓名/公司:"+name+"  手机号码:"+phoneNo);
                }
            });
            //给通讯录控制器contactPickerVC设置代理
            plus.ios.invoke(contactPickerVC, "setDelegate:", delegate);
            //获取当前UIWebView视图
            var currentWebview = plus.ios.currentWebview();
            //根据当前UIWebView视图获取当前控制器
            var currentVC = viewControllerByView(currentWebview);
            //由当前控制器present到通讯录控制器
            plus.ios.invoke(currentVC, "presentViewController:animated:completion:", contactPickerVC, true, null);
        }
        
        /**
         * 供iOS系统调用
         * 根据view获取到当前控制器
         * @param {Object} view
         */
        function viewControllerByView(view){
            if (plus.os.name != "iOS") {
                return null;
            }
            //UIViewController类对象
            var UIViewController = plus.ios.invoke("UIViewController", "class");
            while(view){
                var responder = plus.ios.invoke(view, "nextResponder");
                if (plus.ios.invoke(responder, "isKindOfClass:", UIViewController)) {
                    return responder;
                }
                view = plus.ios.invoke(view, "superview");
            }
            return null;
        }
    </script>
</head>
<body>
    <header class="mui-bar mui-bar-nav">
        <h1 class="mui-title">调用通讯录</h1>
    </header>
    <div class="mui-content">
        <button type="button" style="display: block; margin: 100px auto;">访问通讯录</button>
    </div>
</body>
</html>

好不容易将OC代码翻译成了NJS代码,真机运行之后成功进入了iOS通讯录界面,选中一个联系人后可以正常返回到MUI页面,但是代理方法没有返回任何信息(确定已经成功实现了代理方法,因为假如没有成功实现代理方法的话,选中一个联系人是会跳到详情界面的,感觉这应该是NJS的Bug)。
最后,我在模拟器上试了一下,成功拿到了联系人信息!更加确信是NJS的Bug,于是到官网报了如下Bug:
【报Bug】Native.js调用iOS原生通讯录界面(ContactsUI)时,选中联系人,真机无法获取到回调值。

NJS调用iOS原生通讯录(AddressBookUI,仅支持真机)

报了Bug后,想了一下何不用AdressBookUI试试呢,虽然苹果官方已经废弃,但是iOS上还是可以正常使用滴,于是决定碰碰运气。还是老套路,先写OC代码,在上面OC代码基础上加上通过AddressBookUI访问通讯录的代码:

/**
 * 第二种方法(使用AddressBookUI)
 */
#pragma - 访问通讯录按钮点击事件
- (IBAction)visitAddressBook:(UIButton *)sender {
    //通讯录控制器
    ABPeoplePickerNavigationController *peoplePickerNavController = [[ABPeoplePickerNavigationController alloc]init];
    //设置代理
    //peoplePickerNavController.peoplePickerDelegate = self;
    [peoplePickerNavController setPeoplePickerDelegate:self];
    //从当前控制器present到通讯录控制器
    //[self presentViewController:peoplePickerNavController animated:YES completion:nil];
    //  先获取当前控制器(这样写为了和NJS代码对应)
    UIViewController *currentVC = [self viewControllerByView:self.view];
    [currentVC presentViewController:peoplePickerNavController animated:YES completion:nil];
}

#pragma mark - ABPeoplePickerNavigationControllerDelegate 代理方法
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person{
    //姓名/公司
    NSMutableString *name = [NSMutableString string];
    //姓
    NSString *lastName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonLastNameProperty));
    //名
    NSString *firstName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));
    //公司
    NSString *organizationName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonOrganizationProperty));
    [name appendString:lastName];
    [name appendString:firstName];
    if (name.length <= 0) {
        [name appendString:organizationName];
    }
    
    //电话号码
    NSString *phoneNo = @"";
    ABMultiValueRef phoneNumbers = ABRecordCopyValue(person, kABPersonPhoneProperty);
    CFIndex count = ABMultiValueGetCount(phoneNumbers);
    if (count > 0) {
        //这里只取第一个手机号
        phoneNo = CFBridgingRelease(ABMultiValueCopyValueAtIndex(phoneNumbers, 0));
    }
    CFRelease(phoneNumbers);
    NSLog(@"姓名/公司:%@,手机号码:%@", name, phoneNo);
}

注意:上面的代码需要引入AddressBookUI.h文件,为了规范最好让当前控制器遵守ABPeoplePickerNavigationControllerDelegate代理

将上述OC代码翻译成对应NJS代码时发现了戏剧性的Bug,代理方法【- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person;】返回的数据类型和iOS原生API完全对不上:

  • 本该返回peoplePicker(ABPeoplePickerNavigationController类型)的地方返回的是CNContact类型的对象;
  • 本该返回person(ABRecordRef类型)的地方什么都没有返回;
  • 非常巧的是CNContact刚好是ContactsUI里存储联系人信息的类型;
  • 不从CNContact实例对象里取联系人信息的时候,模拟器和真机都可以正常执行代理方法,一旦从CNContact实例对象中取值,模拟器便不会再执行这个方法,但是真机可以正常执行而且还可以获取选中的联系人信息。

最后翻译的NJS代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <title></title>
    <script src="js/mui.min.js"></script>
    <link href="css/mui.min.css" rel="stylesheet"/>
    <script type="text/javascript" charset="utf-8">
        mui.init();
        mui.plusReady(function(){
            //访问通讯录
            mui("button")[0].addEventListener("tap", function(){
                visitAddressBook();
            });
        });
        /**
         * 访问通讯录
         */
        function visitAddressBook(){
            var peoplePickerNavController = plus.ios.newObject("ABPeoplePickerNavigationController");
            //实现代理方法【- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person;】
            //同时生成遵守ABPeoplePickerNavigationControllerDelegate协议的代理对象peoplePickerDelegate
            var peoplePickerDelegate = plus.ios.implements("ABPeoplePickerNavigationControllerDelegate", {
                "peoplePickerNavigationController:didSelectPerson:":function(peoplePicker, person){
                    //这里的peoplePicker竟然是CNContact实例对象,person是undefined
                    console.log(JSON.stringify(peoplePicker));
                    console.log(JSON.stringify(person));
                    console.log(typeof person);
                    
                    //所以之前的代码不用改
                    var contact = peoplePicker;
                    //姓名/公司
                    var name = "";
                    //姓氏
                    var familyName = contact.plusGetAttribute("familyName");
                    //名字
                    var givenName = contact.plusGetAttribute("givenName");
                    //公司
                    var organizationName = contact.plusGetAttribute("organizationName");
                    name = familyName+givenName;
                    if (name.length <= 0) {
                        name = organizationName;
                    }
                    //电话号码
                    var phoneNo = "";
                    var phoneNumbers = contact.plusGetAttribute("phoneNumbers");
                    if (phoneNumbers.plusGetAttribute("count") > 0) {
                        var phone = phoneNumbers.plusGetAttribute("firstObject");
                        var phoneNumber = phone.plusGetAttribute("value");
                        phoneNo = phoneNumber.plusGetAttribute("stringValue");
                    }
                    console.log("姓名/公司:"+name+"  手机号码:"+phoneNo);
                    mui.toast("姓名/公司:"+name+"  手机号码:"+phoneNo);
                }
            });
            //给通讯录控制器peoplePickerNavController设置代理
            plus.ios.invoke(peoplePickerNavController, "setPeoplePickerDelegate:", peoplePickerDelegate);
            //获取当前UIWebView视图
            var currentWebview = plus.ios.currentWebview();
            //根据当前UIWebView视图获取当前控制器
            var currentVC = viewControllerByView(currentWebview);
            //由当前控制器present到通讯录控制器
            plus.ios.invoke(currentVC, "presentViewController:animated:completion:", peoplePickerNavController, true, null);
        }
        
        /**
         * 供iOS系统调用
         * 根据view获取到当前控制器
         * @param {Object} view
         */
        function viewControllerByView(view){
            if (plus.os.name != "iOS") {
                return null;
            }
            //UIViewController类对象
            var UIViewController = plus.ios.invoke("UIViewController", "class");
            while(view){
                var responder = plus.ios.invoke(view, "nextResponder");
                if (plus.ios.invoke(responder, "isKindOfClass:", UIViewController)) {
                    return responder;
                }
                view = plus.ios.invoke(view, "superview");
            }
            return null;
        }
    </script>
</head>
<body>
    <header class="mui-bar mui-bar-nav">
        <h1 class="mui-title">调用通讯录</h1>
    </header>
    <div class="mui-content">
        <button type="button" style="display: block; margin: 100px auto;">访问通讯录</button>
    </div>
</body>
</html>

通过上面的NJS代码,总算可以真机访问通讯录了。但是仍然需要注意这是NSJ的Bug,哪天官方修复了这个Bug,这段代码就会失效。

封装

下面将iOS模拟器和真机的代码以及Android的代码封装到一个js文件中。使用的时候只需调用下面这个方法(通过回调函数callBack拿到选中的联系人姓名和手机号码):

  • nativeCommon.contacts.getContact(function callBack(name, phoneNumber));

封装后的代码如下:

/**
 * nativeCommon,通过Native.js调用原生API
 */
var nativeCommon = {
    /**
     * 通讯录模块
     */
    contacts:{
        getContact:function(callBack){
            switch (plus.os.name){
                case "iOS":
                    if (plus.device.model === "iPhoneSimulator") {
                        //模拟器
                        nativeCommon.contacts.ios.visitContacts(function(name, phoneNumber){
                            callBack(name, phoneNumber);
                        });
                    } else {
                        //真机
                        nativeCommon.contacts.ios.visitAddressBook(function(name, phoneNumber){
                            callBack(name, phoneNumber);
                        });
                    }
                    break;
                case "Android":
                    // Android通过plus.contacts.getAddressBook可弹出通讯录授权提示框
                    plus.contacts.getAddressBook(plus.contacts.ADDRESSBOOK_PHONE, function (addressbook) {
                        nativeCommon.contacts.android.visitContacts(function(name, phoneNumber){
                            callBack(name, phoneNumber);
                        });
                    }, function (e) {
                        plus.nativeUI.alert("Get address book failed: " + e.message);
                    });
                    break;
                default:
                    break;
            }
        },
        ios:{//供iOS系统调用
            /**
             * 访问通讯录,将获取的联系人信息通过callBack返回
             * 仅限模拟器使用(Native.js 的bug)
             * @param {Object} callBack回调
             */
            visitContacts: function(callBack){
                var contactPickerVC = plus.ios.newObject("CNContactPickerViewController");
                //实现代理方法【- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact;】
                //同时生成遵守CNContactPickerDelegate协议的代理对象delegate
                var delegate = plus.ios.implements("CNContactPickerDelegate", {
                    "contactPicker:didSelectContact:":function(picker, contact){
                        console.log(JSON.stringify(picker));
                        console.log(JSON.stringify(contact));
                        //姓名/公司
                        var name = "";
                        //姓氏
                        var familyName = contact.plusGetAttribute("familyName");
                        //名字
                        var givenName = contact.plusGetAttribute("givenName");
                        //公司
                        var organizationName = contact.plusGetAttribute("organizationName");
                        name = familyName+givenName;
                        if (name.length <= 0) {
                            name = organizationName;
                        }
                        //电话号码
                        var phoneNo = "";
                        var phoneNumbers = contact.plusGetAttribute("phoneNumbers");
                        if (phoneNumbers.plusGetAttribute("count") > 0) {
                            var phone = phoneNumbers.plusGetAttribute("firstObject");
                            var phoneNumber = phone.plusGetAttribute("value");
                            phoneNo = phoneNumber.plusGetAttribute("stringValue");
                        }
                        if(callBack){
                            callBack(name, phoneNo);
                        }
                    }
                });
                //给通讯录控制器contactPickerVC设置代理
                plus.ios.invoke(contactPickerVC, "setDelegate:", delegate);
                //获取当前UIWebView视图
                var currentWebview = plus.ios.currentWebview();
                //根据当前UIWebView视图获取当前控制器
                var currentVC = nativeCommon.contacts.ios.getViewControllerByView(currentWebview);
                //由当前控制器present到通讯录控制器
                plus.ios.invoke(currentVC, "presentViewController:animated:completion:", contactPickerVC, true, null);
            },
            /**
             * 访问通讯录,将获取的联系人信息通过callBack返回
             * 仅限真机使用(Native.js 的bug)
             * @param {Object} callBack
             */
            visitAddressBook:function(callBack){
                var peoplePickerNavController = plus.ios.newObject("ABPeoplePickerNavigationController");
                //实现代理方法【- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person;】
                //同时生成遵守ABPeoplePickerNavigationControllerDelegate协议的代理对象peoplePickerDelegate
                var peoplePickerDelegate = plus.ios.implements("ABPeoplePickerNavigationControllerDelegate", {
                    "peoplePickerNavigationController:didSelectPerson:":function(peoplePicker, person){
                        //这里的peoplePicker竟然是CNContact实例对象,person是undefined
                        console.log(JSON.stringify(peoplePicker));
                        console.log(JSON.stringify(person));
                        console.log(typeof person);
                        
                        //所以之前的代码不用改
                        var contact = peoplePicker;
                        //姓名
                        var name = "";
                        //姓氏
                        var familyName = contact.plusGetAttribute("familyName");
                        //名字
                        var givenName = contact.plusGetAttribute("givenName");
                        //公司
                        var organizationName = contact.plusGetAttribute("organizationName");
                        name = familyName+givenName;
                        if (name.length <= 0) {
                            name = organizationName;
                        }
                        //电话号码
                        var phoneNo = "";
                        var phoneNumbers = contact.plusGetAttribute("phoneNumbers");
                        if (phoneNumbers.plusGetAttribute("count") > 0) {
                            var phone = phoneNumbers.plusGetAttribute("firstObject");
                            var phoneNumber = phone.plusGetAttribute("value");
                            phoneNo = phoneNumber.plusGetAttribute("stringValue");
                        }
                        if (callBack) {
                            callBack(name, phoneNo);
                        }
                    }
                });
                //给通讯录控制器peoplePickerNavController设置代理
                plus.ios.invoke(peoplePickerNavController, "setPeoplePickerDelegate:", peoplePickerDelegate);
                //获取当前UIWebView视图
                var currentWebview = plus.ios.currentWebview();
                //根据当前UIWebView视图获取当前控制器
                var currentVC = nativeCommon.contacts.ios.getViewControllerByView(currentWebview);
                //由当前控制器present到通讯录控制器
                plus.ios.invoke(currentVC, "presentViewController:animated:completion:", peoplePickerNavController, true, null);
            },
            /**
             * 根据view获取到当前控制器
             * @param {Object} view
             */
            getViewControllerByView: function(view){
                //UIViewController类对象
                var UIViewController = plus.ios.invoke("UIViewController", "class");
                while(view){
                    var responder = plus.ios.invoke(view, "nextResponder");
                    if (plus.ios.invoke(responder, "isKindOfClass:", UIViewController)) {
                        return responder;
                    }
                    view = plus.ios.invoke(view, "superview");
                }
                return null;
            }
        },
        android:{//供android系统调用
            visitContacts:function(callBack){
                var REQUESTCODE = 1000;
                main = plus.android.runtimeMainActivity();
                var Intent = plus.android.importClass('android.content.Intent');
                var ContactsContract = plus.android.importClass('android.provider.ContactsContract');
                var intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
                main.onActivityResult = function(requestCode, resultCode, data) { 
                    if (REQUESTCODE == requestCode) {
                        var phoneNumber = "";
                        var resultString = "";
                        var context = main;
                        plus.android.importClass(data);
                        var contactData = data.getData();
                        var resolver = context.getContentResolver();
                        plus.android.importClass(resolver);
                        var cursor = resolver.query(contactData, null, null, null, null);
                        plus.android.importClass(cursor);
                        cursor.moveToFirst();
                        //姓名
                        var givenName = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)) || "";
                        var contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
                        var pCursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId, null, null);
                        if (pCursor.moveToNext()) {
                                phoneNumber =   pCursor.getString( pCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                        }
                        if (callBack) {
                                callBack(givenName, phoneNumber);
                        }
                        cursor.close();
                        pCursor.close();
                    }
                };
                main.startActivityForResult(intent, REQUESTCODE);
            }
        }
    }
}

注意:这里只返回选中联系人的姓名和手机号码,如果有多个号码只返回第一个;假如项目中需要返回所有的手机号码,可以修改对应的代码将手机号码组装成数组返回。

使用案例:

使用前要先引入封装的js文件,本文是将其封装在了native.common.js文件中,使用案例的代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <title></title>
    <script src="js/mui.min.js"></script>
    <link href="css/mui.min.css" rel="stylesheet"/>
    <script type="text/javascript" src="js/native.common.js" ></script>
    <script type="text/javascript" charset="utf-8">
        mui.init();
        mui.plusReady(function(){
            mui("button")[0].addEventListener("tap", function(){
                //访问通讯录
                nativeCommon.contacts.getContact(function(name, phoneNumber){
                    console.log("姓名/公司:"+name+",电话号码:"+phoneNumber);
                    mui.toast("姓名/公司:"+name+",电话号码:"+phoneNumber);
                });
            });
        });
    </script>
</head>
<body>
    <header class="mui-bar mui-bar-nav">
        <h1 class="mui-title">调用通讯录</h1>
    </header>
    <div class="mui-content">
        <button type="button" style="display: block; margin: 100px auto;">访问通讯录</button>
    </div>
</body>
</html>

适配uni-app:

最近uni-app项目中用到了这一块功能,发现会闪退。经排查发现是“根据view获取到当前控制器”这段代码中通过viewnextResponder方法获取到的responder为空:

  /**
   * 根据view获取到当前控制器
   * @param {Object} view
   */
  getViewControllerByView: function(view){
      if (plus.os.name != "iOS") {
          return null;
      }
      //UIViewController类对象
      var UIViewController = plus.ios.invoke("UIViewController", "class");
      while(view){
          //uni-app中responder为undefined
          var responder = plus.ios.invoke(view, "nextResponder");
          //uni-app中由于responder为undefined,所以调用"isKindOfClass:"方法闪退
          if (plus.ios.invoke(responder, "isKindOfClass:", UIViewController)) {
              return responder;
          }
          view = plus.ios.invoke(view, "superview");
      }
      return null;
  }

解决方案:

既然在uni-app中通过当前view获取控制器的方式行不通,那就直接拿到跟控制器,由跟控制器去present到通讯录控制器:

  • 获取跟控制的iOS代码:

    #pragma mark - 获取跟控制
    - (UIViewController *)getRootViewController {
        UIApplication *sharedApplication = [UIApplication sharedApplication];
        id<UIApplicationDelegate> appDelegate = [sharedApplication delegate];
        UIWindow *appWindow = [appDelegate window];
        return [appWindow rootViewController];
    }
    
  • 转换成NJS代码如下:

    /**
     * 获取跟控制器
     */
    getRootViewController: function(){
        //UIApplication类对象
        var UIApplication = plus.ios.invoke("UIApplication", "class");
        var sharedApplication = plus.ios.invoke(UIApplication, "sharedApplication");
        var appDelegate = plus.ios.invoke(sharedApplication, "delegate");
        var appWindow = plus.ios.invoke(appDelegate, "window");
        return plus.ios.invoke(appWindow, "rootViewController");
    },
    
  • 跳转通讯录控制器的代码如下:

    • visitContacts中跳转通讯录的代码:
    /*注释掉由当前控制器跳转通讯录的代码
    //获取当前UIWebView视图
    var currentWebview = plus.ios.currentWebview();
    //根据当前UIWebView视图获取当前控制器
    var currentVC = nativeCommon.contacts.ios.getViewControllerByView(currentWebview);
    //由当前控制器present到通讯录控制器
    plus.ios.invoke(currentVC, "presentViewController:animated:completion:", contactPickerVC, true, null);
    */
    //获取跟控制器
    var rootVc = nativeCommon.contacts.ios.getRootViewController();
    //由跟控制器present到通讯录控制器
    plus.ios.invoke(rootVc, "presentViewController:animated:completion:", contactPickerVC, true, null);
    
    • visitAddressBook中跳转通讯录的代码:
    /*注释掉由当前控制器跳转通讯录的代码
    //获取当前UIWebView视图
    var currentWebview = plus.ios.currentWebview();
    //根据当前UIWebView视图获取当前控制器
    var currentVC = nativeCommon.contacts.ios.getViewControllerByView(currentWebview);
    //由当前控制器present到通讯录控制器
    plus.ios.invoke(currentVC, "presentViewController:animated:completion:", peoplePickerNavController, true, null);
    */
    //获取跟控制器
    var rootVc = nativeCommon.contacts.ios.getRootViewController();
    //由跟控制器present到通讯录控制器
    plus.ios.invoke(rootVc, "presentViewController:animated:completion:", peoplePickerNavController, true, null);
    

这种由跟控制器present到通讯录控制器的方式不仅适用于uni-app也适用于的5+App。uni-app的demo链接已经放到文章中,有需要的小伙伴可以点击下载。

5+App demo链接:https://github.com/w-wh/VisitContactsDemo
uni-app demo链接:https://github.com/w-wh/VisitContactsDemo_uni-app

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,937评论 3 118
  • 1. 这是一个帮派,年龄都在70岁左右,并且我只能在暗地里这样称呼她们,如果让她们知道我叫她们“老人”,她们一定不...
    文晓玲阅读 567评论 6 6
  • 考核指标是衡量自己负责的工作在一段时期内干到到了什么程度,取得什么成果。一个岗位的考核指标可能很多,但如果面面俱到...
    w小郭阅读 403评论 2 1
  • 1.进行串口通信开发用第三方的ORSSerialPort时,xcode9以上的版本新建项目(不是xcode9以下项...
    nadou23阅读 353评论 0 0