欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端

程序员文章站 2022-09-26 21:47:02
Xamarin.Forms读取并展示Android和iOS通讯录 TerminalMACS客户端 本文同步更新地址: https://dotnet9.com/11520.html https://terminalmacs.com/861.html 阅读导航: 一、功能说明 二、代码实现 三、源码获取 ......

xamarin.forms读取并展示android和ios通讯录 - terminalmacs客户端

本文同步更新地址:

阅读导航:

  • 一、功能说明
  • 二、代码实现
  • 三、源码获取
  • 四、参考资料
  • 五、后面计划

一、功能说明

完整思维导图:https://github.com/dotnet9/terminalmacs/blob/master/docs/terminalmacs.xmind
Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端

本文介绍图中右侧画红圈处的功能,即使用xamarin.forms获取和展示android和ios的通讯录信息,下面是最终效果,由于使用的是真实手机,所以联系人姓名及电话号码打码显示。

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端

并简单的进行了搜索功能处理,之所以说简单,是因为通讯录列表是全部读取出来了,搜索是直接从此列表进行过滤的。

下图来自:https://www.xamboy.com/2019/10/10/getting-phone-contacts-in-xamarin-forms/, 本功能是参考此文所写,所以直接引用文中的图片。

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端

二、代码实现

1、共享库工程创建联系人实体类:contacts.cs

namespace terminalmacs.clients.app.models
{
    /// <summary>
    /// 通讯录
    /// </summary>
    public class contact
    {
        /// <summary>
        /// 获取或者设置名称
        /// </summary>
        public string name { get; set; }
        /// <summary>
        /// 获取或者设置 头像
        /// </summary>
        public string image { get; set; }
        /// <summary>
        /// 获取或者设置 邮箱地址
        /// </summary>
        public string[] emails { get; set; }
        /// <summary>
        /// 获取或者设置 手机号码
        /// </summary>
        public string[] phonenumbers { get; set; }
    }
}

2、共享库创建通讯录服务接口:icontactsservice.cs

包括:

  • 一个通讯录获取请求接口:retrievecontactsasync
  • 一个读取一条通讯结果通知事件:oncontactloaded
using system;
using system.collections.generic;
using system.threading;
using system.threading.tasks;
using terminalmacs.clients.app.models;

namespace terminalmacs.clients.app.services
{
    /// <summary>
    /// 通讯录事件参数
    /// </summary>
    public class contacteventargs:eventargs
    {
        public contact contact { get; }
        public contacteventargs(contact contact)
        {
            contact = contact;
        }
    }

    /// <summary>
    /// 通讯录服务接口,android和ios终端具体的通讯录获取服务需要继承此接口
    /// </summary>
    public interface icontactsservice
    {
        /// <summary>
        /// 读取一条数据通知
        /// </summary>
        event eventhandler<contacteventargs> oncontactloaded;
        /// <summary>
        /// 是否正在加载
        /// </summary>
        bool isloading { get; }
        /// <summary>
        /// 尝试获取所有通讯录
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        task<ilist<contact>> retrievecontactsasync(cancellationtoken? token = null);
    }
}

3、ios工程中添加通讯录服务,实现icontactsservice接口:

using contacts;
using foundation;
using system;
using system.collections.generic;
using system.io;
using system.linq;
using system.threading;
using system.threading.tasks;
using terminalmacs.clients.app.models;
using terminalmacs.clients.app.services;

namespace terminalmacs.clients.app.ios.services
{
    /// <summary>
    /// 通讯录获取服务
    /// </summary>
    public class contactsservice : nsobject, icontactsservice
    {
        const string thumbnailprefix = "thumb";

        bool requeststop = false;

        public event eventhandler<contacteventargs> oncontactloaded;

        bool _isloading = false;
        public bool isloading => _isloading;

        /// <summary>
        /// 异步请求权限
        /// </summary>
        /// <returns></returns>
        public async task<bool> requestpermissionasync()
        {
            var status = cncontactstore.getauthorizationstatus(cnentitytype.contacts);

            tuple<bool, nserror> authotization = new tuple<bool, nserror>(status == cnauthorizationstatus.authorized, null);

            if (status == cnauthorizationstatus.notdetermined)
            {
                using (var store = new cncontactstore())
                {
                    authotization = await store.requestaccessasync(cnentitytype.contacts);
                }
            }
            return authotization.item1;

        }

        /// <summary>
        /// 异步请求通讯录,此方法由界面真正调用
        /// </summary>
        /// <param name="canceltoken"></param>
        /// <returns></returns>
        public async task<ilist<contact>> retrievecontactsasync(cancellationtoken? canceltoken = null)
        {
            requeststop = false;

            if (!canceltoken.hasvalue)
                canceltoken = cancellationtoken.none;

            // 我们创建了一个十进制的taskcompletionsource
            var taskcompletionsource = new taskcompletionsource<ilist<contact>>();

            // 在cancellationtoken中注册lambda
            canceltoken.value.register(() =>
            {
                // 我们收到一条取消消息,取消taskcompletionsource.task
                requeststop = true;
                taskcompletionsource.trysetcanceled();
            });

            _isloading = true;

            var task = loadcontactsasync();

            // 等待两个任务中的第一个任务完成
            var completedtask = await task.whenany(task, taskcompletionsource.task);
            _isloading = false;

            return await completedtask;

        }

        /// <summary>
        /// 异步加载通讯录,具体的通讯录读取方法
        /// </summary>
        /// <returns></returns>
        async task<ilist<contact>> loadcontactsasync()
        {
            ilist<contact> contacts = new list<contact>();
            var haspermission = await requestpermissionasync();
            if (haspermission)
            {

                nserror error = null;
                var keystofetch = new[] { cncontactkey.phonenumbers, cncontactkey.givenname, cncontactkey.familyname, cncontactkey.emailaddresses, cncontactkey.imagedataavailable, cncontactkey.thumbnailimagedata };

                var request = new cncontactfetchrequest(keystofetch: keystofetch);
                request.sortorder = cncontactsortorder.givenname;

                using (var store = new cncontactstore())
                {
                    var result = store.enumeratecontacts(request, out error, new cncontactstorelistcontactshandler((cncontact c, ref bool stop) =>
                    {

                        string path = null;
                        if (c.imagedataavailable)
                        {
                            path = path = path.combine(path.gettemppath(), $"{thumbnailprefix}-{guid.newguid()}");

                            if (!file.exists(path))
                            {
                                var imagedata = c.thumbnailimagedata;
                                imagedata?.save(path, true);


                            }
                        }

                        var contact = new contact()
                        {
                            name = string.isnullorempty(c.familyname) ? c.givenname : $"{c.givenname} {c.familyname}",
                            image = path,
                            phonenumbers = c.phonenumbers?.select(p => p?.value?.stringvalue).toarray(),
                            emails = c.emailaddresses?.select(p => p?.value?.tostring()).toarray(),

                        };

                        if (!string.isnullorwhitespace(contact.name))
                        {
                            oncontactloaded?.invoke(this, new contacteventargs(contact));

                            contacts.add(contact);
                        }

                        stop = requeststop;

                    }));
                }
            }

            return contacts;
        }


    }
}

4、在ios工程中的info.plist文件添加通讯录权限使用说明

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端

5、在android工程中添加读取通讯录权限配置:androidmanifest.xml

<uses-permission android:name="android.permission.read_contacts"/>

完整权限配置如下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versioncode="1" android:versionname="1.0" package="com.companyname.terminalmacs.clients.app">
	<uses-sdk android:minsdkversion="21" android:targetsdkversion="28" />
	<application android:label="terminalmacs.clients.app.android"></application>
	<uses-permission android:name="android.permission.access_network_state" />
  <uses-permission android:name="android.permission.read_contacts"/>
  <uses-permission android:name="android.permission.write_external_storage" />
</manifest>

6、在android工程中添加通讯录服务,实现icontactserver接口:contactsservice.cs

using acr.userdialogs;
using android;
using android.app;
using android.content;
using android.content.pm;
using android.database;
using android.provider;
using android.runtime;
using android.support.v4.app;
using plugin.currentactivity;
using system;
using system.collections.generic;
using system.io;
using system.linq;
using system.threading;
using system.threading.tasks;
using terminalmacs.clients.app.models;
using terminalmacs.clients.app.services;

namespace terminalmacs.clients.app.droid.services
{
    /// <summary>
    /// 通讯录获取服务
    /// </summary>
    public class contactsservice : icontactsservice
    {
        const string thumbnailprefix = "thumb";
        bool stopload = false;
        static taskcompletionsource<bool> contactpermissiontcs;
        public string tag
        {
            get
            {
                return "mainactivity";
            }
        }
        bool _isloading = false;
        public bool isloading => _isloading;
        //权限请求状态码
        public const int requestcontacts = 1239;
        /// <summary>
        /// 获取通讯录需要的请求权限
        /// </summary>
        static string[] permissionscontact = {
            manifest.permission.readcontacts
        };

        public event eventhandler<contacteventargs> oncontactloaded;
        /// <summary>
        /// 异步请求通讯录权限
        /// </summary>
        async void requestcontactspermissions()
        {
            //检查是否可以弹出申请读、写通讯录权限
            if (activitycompat.shouldshowrequestpermissionrationale(crosscurrentactivity.current.activity, manifest.permission.readcontacts)
                || activitycompat.shouldshowrequestpermissionrationale(crosscurrentactivity.current.activity, manifest.permission.writecontacts))
            {
                // 如果未授予许可,请向用户提供其他理由用户将从使用权限的附加上下文中受益。
                // 例如,如果请求先前被拒绝。
                await userdialogs.instance.alertasync("通讯录权限", "此操作需要“通讯录”权限", "确定");
            }
            else
            {
                // 尚未授予通讯录权限。直接请求这些权限。
                activitycompat.requestpermissions(crosscurrentactivity.current.activity, permissionscontact, requestcontacts);
            }
        }

        /// <summary>
        /// 收到用户响应请求权限操作后的结果
        /// </summary>
        /// <param name="requestcode"></param>
        /// <param name="permissions"></param>
        /// <param name="grantresults"></param>
        public static void onrequestpermissionsresult(int requestcode, string[] permissions, [generatedenum] android.content.pm.permission[] grantresults)
        {
            if (requestcode == requestcontacts)
            {
                // 我们请求了多个通讯录权限,因此需要检查相关的所有权限
                if (permissionutil.verifypermissions(grantresults))
                {
                    // 已授予所有必需的权限,显示联系人片段。
                    contactpermissiontcs.trysetresult(true);
                }
                else
                {
                    contactpermissiontcs.trysetresult(false);
                }

            }
        }

        /// <summary>
        /// 异步请求权限
        /// </summary>
        /// <returns></returns>
        public async task<bool> requestpermissionasync()
        {
            contactpermissiontcs = new taskcompletionsource<bool>();

            // 验证是否已授予所有必需的通讯录权限。
            if (android.support.v4.content.contextcompat.checkselfpermission(crosscurrentactivity.current.activity, manifest.permission.readcontacts) != (int)permission.granted
                || android.support.v4.content.contextcompat.checkselfpermission(crosscurrentactivity.current.activity, manifest.permission.writecontacts) != (int)permission.granted)
            {
                // 尚未授予通讯录权限。
                requestcontactspermissions();
            }
            else
            {
                // 已授予通讯录权限。
                contactpermissiontcs.trysetresult(true);
            }

            return await contactpermissiontcs.task;
        }

        /// <summary>
        /// 异步请求通讯录,此方法由界面真正调用
        /// </summary>
        /// <param name="canceltoken"></param>
        /// <returns></returns>
        public async task<ilist<contact>> retrievecontactsasync(cancellationtoken? canceltoken = null)
        {
            stopload = false;

            if (!canceltoken.hasvalue)
                canceltoken = cancellationtoken.none;

            // 我们创建了一个十进制的taskcompletionsource
            var taskcompletionsource = new taskcompletionsource<ilist<contact>>();

            // 在cancellationtoken中注册lambda
            canceltoken.value.register(() =>
            {
                // 我们收到一条取消消息,取消taskcompletionsource.task
                stopload = true;
                taskcompletionsource.trysetcanceled();
            });

            _isloading = true;

            var task = loadcontactsasync();

            // 等待两个任务中的第一个任务完成
            var completedtask = await task.whenany(task, taskcompletionsource.task);
            _isloading = false;

            return await completedtask;
        }

        /// <summary>
        /// 异步加载通讯录,具体的通讯录读取方法
        /// </summary>
        /// <returns></returns>
        async task<ilist<contact>> loadcontactsasync()
        {
            ilist<contact> contacts = new list<contact>();
            var haspermission = await requestpermissionasync();
            if (!haspermission)
            {
                return contacts;
            }

            var uri = contactscontract.contacts.contenturi;
            var ctx = application.context;
            await task.run(() =>
            {
                // 暂时只请求通讯录id、displayname、photothumbnailuri,可以扩展
                var cursor = ctx.applicationcontext.contentresolver.query(uri, new string[]
                {
                        contactscontract.contacts.interfaceconsts.id,
                        contactscontract.contacts.interfaceconsts.displayname,
                        contactscontract.contacts.interfaceconsts.photothumbnailuri
                }, null, null, $"{contactscontract.contacts.interfaceconsts.displayname} asc");
                if (cursor.count > 0)
                {
                    while (cursor.movetonext())
                    {
                        var contact = createcontact(cursor, ctx);

                        if (!string.isnullorwhitespace(contact.name))
                        {
                            // 读取出一条,即通知界面展示
                            oncontactloaded?.invoke(this, new contacteventargs(contact));
                            contacts.add(contact);
                        }

                        if (stopload)
                            break;
                    }
                }
            });

            return contacts;

        }

        /// <summary>
        /// 读取一条通讯录数据
        /// </summary>
        /// <param name="cursor"></param>
        /// <param name="ctx"></param>
        /// <returns></returns>
        contact createcontact(icursor cursor, context ctx)
        {
            var contactid = getstring(cursor, contactscontract.contacts.interfaceconsts.id);

            var numbers = getnumbers(ctx, contactid);
            var emails = getemails(ctx, contactid);

            var uri = getstring(cursor, contactscontract.contacts.interfaceconsts.photothumbnailuri);
            string path = null;
            if (!string.isnullorempty(uri))
            {
                try
                {
                    using (var stream = android.app.application.context.contentresolver.openinputstream(android.net.uri.parse(uri)))
                    {
                        path = path.combine(path.gettemppath(), $"{thumbnailprefix}-{guid.newguid()}");
                        using (var fstream = new filestream(path, filemode.create))
                        {
                            stream.copyto(fstream);
                            fstream.close();
                        }

                        stream.close();
                    }


                }
                catch (exception ex)
                {
                    system.diagnostics.debug.writeline(ex);
                }

            }
            var contact = new contact
            {
                name = getstring(cursor, contactscontract.contacts.interfaceconsts.displayname),
                emails = emails,
                image = path,
                phonenumbers = numbers,
            };

            return contact;
        }

        /// <summary>
        /// 读取联系人电话号码
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="contactid"></param>
        /// <returns></returns>
        string[] getnumbers(context ctx, string contactid)
        {
            var key = contactscontract.commondatakinds.phone.number;

            var cursor = ctx.applicationcontext.contentresolver.query(
                contactscontract.commondatakinds.phone.contenturi,
                null,
                contactscontract.commondatakinds.phone.interfaceconsts.contactid + " = ?",
                new[] { contactid },
                null
            );

            return readcursoritems(cursor, key)?.toarray();
        }

        /// <summary>
        /// 读取联系人邮箱地址
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="contactid"></param>
        /// <returns></returns>
        string[] getemails(context ctx, string contactid)
        {
            var key = contactscontract.commondatakinds.email.interfaceconsts.data;

            var cursor = ctx.applicationcontext.contentresolver.query(
                contactscontract.commondatakinds.email.contenturi,
                null,
                contactscontract.commondatakinds.email.interfaceconsts.contactid + " = ?",
                new[] { contactid },
                null);

            return readcursoritems(cursor, key)?.toarray();
        }

        ienumerable<string> readcursoritems(icursor cursor, string key)
        {
            while (cursor.movetonext())
            {
                var value = getstring(cursor, key);
                yield return value;
            }

            cursor.close();
        }

        string getstring(icursor cursor, string key)
        {
            return cursor.getstring(cursor.getcolumnindex(key));
        }

    }
}

需要添加 plugin.currentactivityacr.userdialogs 包。

7、android工程添加权限处理判断类

permission.util

using android.content.pm;

namespace terminalmacs.clients.app.droid
{
    public static class permissionutil
    {
        /**
		* 通过验证给定数组中的每个条目的值是否为permission.granted,检查是否已授予所有给定权限。
		*
		* see activity#onrequestpermissionsresult (int, string[], int[])
		*/
        public static bool verifypermissions(permission[] grantresults)
        {
            // 必须至少检查一个结果.
            if (grantresults.length < 1)
                return false;

            // 验证是否已授予每个必需的权限,否则返回false.
            foreach (permission result in grantresults)
            {
                if (result != permission.granted)
                {
                    return false;
                }
            }
            return true;
        }
    }
}

mainactivity.onrequestpermissionresult是权限申请结果处理函数,在此函数中调用contactsservice.onrequestpermissionsresult通知通讯录服务权限处理结果。

mainactivity.cs

using acr.userdialogs;
using android.app;
using android.content.pm;
using android.os;
using android.runtime;
using terminalmacs.clients.app.droid.services;
using terminalmacs.clients.app.services;

namespace terminalmacs.clients.app.droid
{
    [activity(label = "terminalmacs.clients.app", icon = "@mipmap/icon", theme = "@style/maintheme", mainlauncher = true, configurationchanges = configchanges.screensize | configchanges.orientation)]
    public class mainactivity : global::xamarin.forms.platform.android.formsappcompatactivity
    {
        icontactsservice contactsservice = new contactsservice();
        protected override void oncreate(bundle savedinstancestate)
        {
            tablayoutresource = resource.layout.tabbar;
            toolbarresource = resource.layout.toolbar;

            base.oncreate(savedinstancestate);

            xamarin.essentials.platform.init(this, savedinstancestate);
            global::xamarin.forms.forms.init(this, savedinstancestate);
            userdialogs.init(() => this);

            // 将通讯录服务实例传递给共享库,由共享库使用读取通讯录接口
            loadapplication(new app(contactsservice));
        }
        public override void onrequestpermissionsresult(int requestcode, string[] permissions, [generatedenum] android.content.pm.permission[] grantresults)
        {
            xamarin.essentials.platform.onrequestpermissionsresult(requestcode, permissions, grantresults);

            // 通讯录服务处理权限请求结果
            contactsservice.onrequestpermissionsresult(requestcode, permissions, grantresults);
            
            base.onrequestpermissionsresult(requestcode, permissions, grantresults);
        }
    }
}

8、创建通讯录viewmodel,并使用通讯录服务

using system;
using system.collections;
using system.collections.generic;
using system.collections.objectmodel;
using system.linq;
using system.threading.tasks;
using system.windows.input;
using terminalmacs.clients.app.models;
using terminalmacs.clients.app.services;
using xamarin.forms;

namespace terminalmacs.clients.app.viewmodels
{
    /// <summary>
    /// 通讯录viewmodel
    /// </summary>
    public class contactviewmodel : baseviewmodel
    {
        /// <summary>
        /// 通讯录服务接口
        /// </summary>
        icontactsservice _contactservice;
        /// <summary>
        /// 标题
        /// </summary>
        public new string title => "通讯录";
        private string _searchtext;
        /// <summary>
        /// 搜索关键字
        /// </summary>
        public string searchtext
        {
            get { return _searchtext; }
            set
            {
                setproperty(ref _searchtext, value);
            }
        }
        /// <summary>
        /// 通讯录搜索命令
        /// </summary>
        public icommand raisesearchcommand { get; }
        /// <summary>
        /// 通讯录列表
        /// </summary>
        public observablecollection<contact> contacts { get; set; }
        private list<contact> _filteredcontacts;
        /// <summary>
        /// 通讯录过滤列表
        /// </summary>
        public list<contact> filteredcontacts

        {
            get { return _filteredcontacts; }
            set
            {
                setproperty(ref _filteredcontacts, value);
            }
        }
        public contactviewmodel(icontactsservice contactservice)
        {
            _contactservice = contactservice;
            contacts = new observablecollection<contact>();
            xamarin.forms.bindingbase.enablecollectionsynchronization(contacts, null, observablecollectioncallback);
            _contactservice.oncontactloaded += oncontactloaded;
            loadcontacts();
            raisesearchcommand = new command(raisesearchhandle);
        }

        /// <summary>
        /// 过滤通讯录
        /// </summary>
        void raisesearchhandle()
        {
            if (string.isnullorempty(searchtext))
            {
                filteredcontacts = contacts.tolist();
                return;
            }

            func<contact, bool> checkcontact = (s) =>
            {
                if (!string.isnullorwhitespace(s.name) && s.name.tolower().contains(searchtext.tolower()))
                {
                    return true;
                }
                else if (s.phonenumbers.length > 0 && s.phonenumbers.tolist().exists(cu => cu.tostring().contains(searchtext)))
                {
                    return true;
                }
                return false;
            };
            filteredcontacts = contacts.tolist().where(checkcontact).tolist();
        }

        /// <summary>
        /// bindingbase.enablecollectionsynchronization 为集合启用跨线程更新
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="context"></param>
        /// <param name="accessmethod"></param>
        /// <param name="writeaccess"></param>
        void observablecollectioncallback(ienumerable collection, object context, action accessmethod, bool writeaccess)
        {
            // `lock` ensures that only one thread access the collection at a time
            lock (collection)
            {
                accessmethod?.invoke();
            }
        }

        /// <summary>
        /// 收到事件通知,读取一条通讯录信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void oncontactloaded(object sender, contacteventargs e)
        {
            contacts.add(e.contact);
            raisesearchhandle();
        }

        /// <summary>
        /// 异步读取终端通讯录
        /// </summary>
        /// <returns></returns>
        async task loadcontacts()
        {
            try
            {
                await _contactservice.retrievecontactsasync();
            }
            catch (taskcanceledexception)
            {
                console.writeline("任务已经取消");
            }
        }
    }
}

9、添加通讯录页面展示通讯录数据

<?xml version="1.0" encoding="utf-8" ?>
<contentpage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:ios="clr-namespace:xamarin.forms.platformconfiguration.iosspecific;assembly=xamarin.forms.core"
             mc:ignorable="d"
             title="{binding title}"
             x:class="terminalmacs.clients.app.views.contactpage"
             ios:page.usesafearea="true">
    <contentpage.content>
        <stacklayout>
            <searchbar x:name="filtertext"
                        heightrequest="40"
                        text="{binding searchtext}"
                       searchcommand="{binding raisesearchcommand}"/>
            <listview   itemssource="{binding filteredcontacts}"
                        hasunevenrows="true">
                <listview.itemtemplate>
                    <datatemplate>
                        <viewcell>
                            <stacklayout padding="10"
                                         orientation="horizontal">
                                <image  source="{binding image}"
                                        verticaloptions="center"
                                        x:name="image"
                                        aspect="aspectfit"
                                        heightrequest="60"/>
                                <stacklayout verticaloptions="center">
                                    <label text="{binding name}"
                                       fontattributes="bold"/>
                                    <label text="{binding phonenumbers[0]}"/>
                                    <label text="{binding emails[0]}"/>
                                </stacklayout>
                            </stacklayout>
                        </viewcell>
                    </datatemplate>
                </listview.itemtemplate>
            </listview>
        </stacklayout>
    </contentpage.content>
</contentpage>

三、源码获取

已编译的android客户端:

  • 3.ios读取通讯录功能代码也已添加,但由于本人没有ios测试环境,所以未验证,有条件的朋友可以测试下ios的通讯录读取功能,如果代码不起作用,可参考本文参考的文章检查ios代码。

四、参考资料

getting phone contacts in xamarin forms:

参考文章末尾有源代码链接。

五、后面计划

xamarin.forms客户端基本信息获取,比如imei、imsi、本机号码、mac地址等。