Normal view

There are new articles available, click to refresh the page.
Before yesterdayAndy的小书架

Keynote 讲演——做一份苹果风格的演示文稿

8 April 2020 at 17:37

本篇文章介绍一下乔老爷子最喜欢的软件——Keynote 的基础和进阶使用。

我为什么把目光投向了 Keynote

「演示文稿」是一个演讲的重要部分,能够起到突出重点、引导观众、提示演讲者等作用,对于成功举办一场演讲或完成一次汇报可谓是至关重要的。如今,制作一份精美的演示文稿已经成为办公场景下的一个非常常见的任务,无论是对资料进行整理,还是准备一份演讲稿,演示文稿基本上都能够胜任。Microsoft PowerPoint 是目前应用最为广泛的演示文稿制作软件,多个页面(我更习惯称为「幻灯片」)的线性展示构成了演示文稿的主体。除此之外,以 Prezi 为代表的非线性演示文稿制作软件凭借着令人惊艳的动作效果也有着不少的支持者。

然而,PowerPoint 的功能固然强大,但正是繁杂的功能有时候反而让我摸不着头脑,需要一个工具的时候往往不知道在哪里可以找到,并且由于自定义程度比较高,想要实现一些自定义的效果时需要比较多的步骤才能完成。相比之下,Apple 自家的 Keynote 在功能性和易用性上做到了一个比较好的平衡,让使用者能够方便快速地实现自己想要的效果。

其实吧,说句实在话,用 Keynote 能够做到的效果,用 PowerPoint 同样能够做到,但是这中间多花的大量时间,对于部分人来说可能是非常珍贵的。在 Keynote 中寥寥几下的操作,换到 PowerPoint 或许需要半个小时甚至更久。私以为 Keynote 和 PowerPoint 在功能上并没有非常大的差别,只是在使用上我会更喜欢 Keynote 的设计,制作幻灯片更重要的是如何恰当地表达演讲者的观点。

关于 Keynote 的官方指南和帮助,可以在应用的菜单栏「帮助」->「Keynote 讲演帮助」处获得。另外,在 Apple Teacher 的学习资源网站中,也有关于 Keynote 的官方教程电子书,大家可以自行前往下载。

Keynote 基础使用

本节介绍 Keynote 的基础使用方法。

简单操作

在新建一份演示文稿的时候,Keynote 允许用户从内置的模主题库中选择一个主题开始演示文稿的制作,可以根据实际的应用场景选择合适的主题。其中,最著名的主题莫过于「渐变」,这是苹果发布会所选用的主题。当然,也不是所有的场合使用这个主题就是正确的,不正确的使用可能会带来适得其反的效果。在选择主题的时候,最好根据实际的投影设备的情况,选择 16:9 或者 4:3 的尺寸,避免到场播放时出现奇怪的显示效果。

内置主题

进入应用后,主界面与 iWork 套件的其他两款软件基本上相同:上方的工具栏提供了几个常用的操作按钮方便用户点选,可以在工具栏区域右键单击,进行工具栏的自定义,将自己常用的按钮放在工具栏中;左方为导航栏,展示了当前演示文稿中的所有幻灯片缩略图,可以在工具栏的「显示」按钮中切换展示方式,如缩略图、仅幻灯片、看片台、大纲等;中间最大的区域是幻灯片的编辑区域和演讲者注释区域;右方为检查器,当选中幻灯片本身的时候对幻灯片的属性进行调整,当选中幻灯片中的对象的时候对对象的属性进行调整。

应用主界面

点击工具栏中的「添加」按钮可以根据「母版」添加幻灯片,不同的演示文稿主题都会事先设计几张有占位符的幻灯片,在使用的时候只需要用自己的内容填充到占位符的位置就可以很轻松地完成一页幻灯片的制作。当然,「母版」的效果完全可以从一张空白的幻灯片页实现出来,但是对于一些简单的场合,使用母版可以提升不少的效率。另外一种间接使用母版的方式是选中当前幻灯片页后,在右方「格式」检查器中选择「更改母版」或者在「外观」选项中勾选自己想要添加的内容。注意到检查器中有一个「幻灯片编号」的选项,类似于文档中的页码,但是在检查器中勾选这个选项只会对当前幻灯片添加页码,如果希望对全部幻灯片添加页码,可以点击菜单栏「幻灯片」->「显示所有幻灯片上的幻灯片编号」。

检查器设计幻灯片布局外观

如果演示文稿的内容是由几个相对独立的部分构成的,希望在左侧导航栏中让幻灯片呈现出分组效果,可以选中幻灯片,将其向右拖移一小段距离就可以了,具体效果见下面的动图,缩进的程度最大为5。在设计的过程中,有时候会希望暂时屏蔽掉某一页幻灯片,可以在导航栏中右键点击幻灯片,选择「跳过幻灯片」即可。

幻灯片分组、跳过幻灯片

最后再补充一个几乎在不少 macOS 软件中都有的一个操作吧。我们知道 macOS 下的「拷贝(Copy)」和「粘贴」的快捷键分别是 Command + C 和 Command + V,但是还有一个同样非常常用的操作叫做「复制(Duplicate)」,快捷键是 Command + D,实际上就是「拷贝 + 粘贴」的组合。在 Keynote 中,无论是对幻灯片本身还是幻灯片中的元素,都可以使用 Command + D 的方式来创建一个副本,这个副本与原对象具有完全相同的属性,包括动画效果都是一样的,这一操作对于 Keynote 中的「神奇移动」效果有着至关重要的作用,在后面的文章中会进一步介绍。

添加对象

要想让一张幻灯片丰富起来,当然少不了除了文字以外的内容了。Keynote 上方的工具栏中提供了多种对象的插入按钮,包括表格、图表、文本、形状、媒体文件等。选中这些对对象后,在右侧的检查器中可以对其进行多方面的调整,包括修改文本字体、设计表格样式、为图片增加图片框和阴影、调整位置及大小等,内容非常丰富,在这里就不展开讲了,大家可以自己调整着玩一下。为了增加幻灯片的交互动作,每一个对象都可以设置链接,点击对象可以跳转到指定的幻灯片或者网络 URL,借助超链接,甚至可以用 Keynote 制作一份具有用户交互功能的演示文稿,在一定程度上跳脱出线性逻辑的限制。如果幻灯片上的对象比较多,并且存在重叠、很难点击到的时候,可以点击菜单栏「显示」->「显示对象列表」,就会展示出当前幻灯片中的所有对象,方便点选。

为了使幻灯片看上去美观,对齐元素是必不可少的。在幻灯片上拖动一个对象时,Keynote 会实时显示对象在幻灯片上的坐标,也可以在检查器的「排列」选项卡中作更加精细的调整。然而,在很多时候我们更加关心的是元素之间的相对位置例如是否对齐、宽度是否相等、是否居中等。在 Keynote 中拖移对象时,会显示出该对象与周围对象之间的对齐线,帮助我们快速确定多个对象是否居中、间隔是否相等之类的信息。

对齐参考线

在动图中可以看到,对象被拖到接近参考线附近的位置的时候会自动吸附过去,避免了手动调整的麻烦。如果不希望自动吸附,在拖拽的时候按住 Command 键就不会显示参考线了。如果希望自己手动添加一条基准线进行对齐,可以点击菜单栏「显示」->「显示标尺」(或快捷键 Command + R)显示标尺,然后从标尺出点按并拖移即可添加基准线,拖离幻灯片编辑区域即可删除基准线。

说到按住 Command 进行拖拽,对于图片和形状,如果按住 Option 键对图片尺寸进行拖拽的话,就会以图片或形状的中心点为基准进行缩放;否则,会以对称方向的点为基准进行缩放。

对齐对象还可以在检查器中进行。选中希望进行排列的对象,在检查器的「排列」选项卡中,点按「对齐」和「分布」两个下拉菜单即可选择希望的对齐和分布方式。对齐方式按照对齐的轴不同分为:左对齐、居中(纵轴)对齐、右对齐、顶部对齐、中间(横轴)对齐、底部对齐。分布方式按照方向不同分为:水平分布、垂直分布、平均分布。有兴趣的读者可以自己拖几个矩形出来试试就知道是怎么用的了。在检查器中进行调整进一步简化了对齐对象的方式,但是我个人感觉反而有点奇怪,还是使用参考线的方式更加自然。

对于图片来说,Keynote 提供了两个简单但是非常实用的功能:遮罩与 Alpha,在工具栏中可以找到,或者双击图片就可以进入遮罩功能。遮罩实际上是对图片的缩放和裁剪,对于一些图片边缘不想要的部分可以裁掉。Alpha 则可以去除图片中相似的背景色,实现简单的抠图效果。当然这跟 PS 等专业的图片处理软件的差距不是一点半点,仅在颜色边界比较明显的情况下可以用 Alpha 简单处理,一旦有更加复杂的需要请打开你的 Photoshop。

Alpha 抠图

幻灯片播放

制作好演示文稿后,是时候播放出来感受一下成果了。点击工具栏的「播放」即可从当前幻灯片页开始全屏播放演示文稿,每次点击鼠标都会触发对象动画或者页面过渡动画。除了最基本的播放以外,还可以对演示文稿的放映进行录制和预演,「录制」是指录制幻灯片的播放,有点像录网课的感觉,而「预演」则会显示各种提示信息辅助演讲者进行演讲,如下一张幻灯片的预览、演讲者注释等,具体的放映设置可以在菜单栏「播放」->「自定演讲者显示」中进行设置,选择适合自己的演讲者视角布局。

在连接投影仪的情况下,「预演」播放在笔记本上显示的内容是演讲者视图,而投影出来的内容是单页的幻灯片内容,因此如果演讲者坐在固定位置进行演讲的话,可以适当使用预演播放进行辅助,防止自己忘记下一张幻灯片要讲什么。当然,最好的情况还是对自己的演讲内容烂熟于心,这样的演讲效果才会更好。

演讲者视角

录制和预演功能都可以在菜单栏的「播放」中找到。

iCloud 同步

和 Pages、Numbers 一样,对于 Apple 全家桶用户来说,Keynote 是 iOS 和 macOS 上都有的应用程序,可以通过 iCloud 实现不同设备下的数据同步。Keynote 会在「iCloud 云盘」中会创建一个专属的目录来作为默认的保存位置,不同设备之间的数据会进行实时同步,包括新建、删除、编辑等。如果因为网络中断等问题造成两份表格的内容不一致,Keynote 会提示用户选择一份保留下来的记录,然后重新将所有设备上的内容变成相同的。

要想开启 Keynote 的 iCloud 同步功能,iOS 端在「设置」->「Apple ID」->「iCloud」中勾选「iCloud 云盘」和「Keynote 讲演」即可,macOS 端在「系统偏好设置」「Apple ID」「iCloud」中勾选「iCloud 云盘」,并在 iCloud 云盘的选项中勾选「Keynote 讲演」即可。有关 iWork 和 iCloud 的使用,可以参考我的其他文章:

知乎专栏 - iCloud 与 Apple 自带 App

共享与多人协作

Keynote 内置共享和多人协作功能。点击菜单栏的「共享」->「发送副本」可以将当前表格文件的副本以邮件、AirDrop 等方式发送出去。点击菜单栏的「共享」->「与其他人协作...」或点击工具栏的「协作按钮」可以将邀请其他用户共同参与当前表格的编辑,发送者可以设置文件的访问权限,以防被不具有修改权限的访问者意外修改了文件内容。这一功能在其他的 Apple 自带软件中都有,在文件协作者也是使用 Apple 设备的情况下可以非常方便地进行多人协作。

在特殊时期,如果有远程进行文稿演示的需要的话,除了利用远程会议软件以外,Keynote 提供了「Keynote 直播」的功能,允许其他用户通过网络观看演示文稿的播放。由于本人没有使用过这个功能,有需要的朋友可以查询 Keynote 的帮助或者亲自尝试一下。

Keynote 进阶使用

本节介绍 Keynote 的进阶使用方法。

创建与编辑母版

前面提到,「母版」能够简化演示文稿的制作流程,通过预先放置好的占位符来规定对象的位置。Keynote 自带有几个简单的母版帮助用户设计幻灯片页面。如果需要对已有的母版进行修改或者自己设计一份新的母版的话,可以在左方导航栏中右键单击,选择「编辑母版幻灯片」,进入到母版编辑的界面中。母版设计与幻灯片设计基本相同,只是这时候设计的是一套通用的标准。

母版编辑状态下,在左方导航栏右键单击幻灯片可以为母版重命名,便于后续使用时进行区分。母版编辑界面中默认开启了标尺的显示,可以拖拽基准线辅助对齐,在自带的母版设计中可以看到 Apple 也是通过这种方式来对齐对象的。在母版中添加的对象默认是不能够由使用者改变的,如果需要设置为占位符,需要选中对象后,在右侧检查器的「格式」->「样式」页中,勾选「定义为文本(媒体)占位符」,使用者才能够在使用母版时对定义为占位符的对象进行修改,没有被定义为占位符的对象都是不能够直接修改的,需要在母版编辑状态下才能修改。占位符的「显示」框的内容是用来展示给使用者的内容,可以自行设置。

除了自己设定为占位符之外,主题自带的母版中有一些已经定义好的元素,例如标题、段落、图片等,如果不想自己重新弄的话,可以将自带母版的对象复制到自己后续添加的母版中,就能够使用原生的标题、段落和图片了。

母版编辑

当然,也可以像常见的 PowerPoint 模板一样,直接在演示文稿中放置对象起到占位符的作用,由使用者来填充内容,而不是采用母版的方式。两种方法并没有优劣之分,用得顺手就行。

动画效果

一份演示文稿最吸引人的地方莫过于设计良好的动画效果,而这也是 Keynote 的强大之处。

在 Keynote 中动画分为两种:对象的动作效果和幻灯片之间的过渡效果。动作效果规定了一张幻灯片上的对象以什么样的方式进入、移动和退出。选中想要添加动作效果的对象,在右侧检查器的「动画效果」中,可以为对象设置「构件出现」、「动作」、「构件消失」三个阶段的动画,大部分的内置动画效果都是非常惊艳的。在选定了一个动画效果后,可以对效果进行微调,例如持续时间、方向、多个对象同时执行动画的播放方式等,检查器下方的「构件顺序」可以调整多个动画的执行顺序、开始条件、动画分组等,为用户提供了较高的自由度,实现自己想要的动画效果。

对象动作效果

通过恰当的分组和开始时机,可以让不同对象的动画有机地结合起来,使动画更加合理。关于演示文稿的制作,推荐一个挺不错的视频,视频的关注点更多的是在如何利用 Keynote 制作一份好的演示文稿,而不仅仅是功能的简单介绍。

BiliBIli - 许岑 Keynote 课程

过渡效果是指上一页幻灯片以什么样的方式切换到下一页幻灯片。选中想要添加过渡的幻灯片,同样是在右侧检查器的「动画效果」中,可以为当前幻灯片添加切换到下一页幻灯片的动画效果。和对象的动作效果一样,也有很多内置的过渡效果是非常惊艳的。其中,「神奇移动」或许是最神奇的一个了(不然怎么叫神奇移动)。「神奇移动」能够根据前后两张幻灯片上的元素,使两张幻灯片中的共有元素以非常平滑的方式变换到合适的位置。实际上,这个效果通过为对象添加动作效果也能够实现,而且还不用多做一页幻灯片,但是在对象比较多、变换比较复杂的情况下,手动添加动作效果是一件非常麻烦的事情,甚至可能做不出来;而借助神奇移动,对象就会很神奇地自己移动过去了。

神奇(的)移动

为了保证神奇移动的正常运行,前后两张幻灯片上的元素最好是「完全一样」的,为此,可以使用快捷键 Command + D 将幻灯片进行复制,然后在下一张幻灯片中随意修改元素的大小、位置和样式,这样就保证前后两张幻灯片上的元素是完全一样的。

关于动画的使用,我个人的建议是在完成所有的布局设计之后,才添加动作和过渡效果,以免在后面设计的过程中添加或删除了某个对象、某页幻灯片之后,可能「牵一发而动全身」,导致一大堆的幻灯片需要作出相应的调整。

神奇移动和动作效果的结合,不知道能迸发出怎样的火花呢?

演讲者注释

顾名思义,演讲者注释是给演讲者的一个提醒,当启用「预演」播放的时候,可以将演讲者注释显示出来,将一些提醒自己的关键字放在上面,以免忘词。点击菜单栏的「显示」->「显示演讲者注释」或者使用快捷键 Shift + Command + P 即可在幻灯片的编辑界面中调出演讲者注释。这部分内容在播放的时候是不会被观众看到的。

演讲者注释也可以当作演示文稿的文字补充,当用演示文稿作为阅读资料的时候,可以避免幻灯片中包含大量的说明性文字,这部分的内容可以转移到演讲者注释中作为补充材料。

将 iPhone 变成翻页笔

iOS 的 Keynote 使用方法基本上和 macOS 一致,但由于屏幕尺寸的限制,iOS 上的功能可能藏得比较深,不容易看见,因此在 iPhone 上更适合查看演示文稿。然而,Keynote 允许将 iPhone 变成 Mac 播放 Keynote 的遥控翻页笔。点击 iPhone 的 Keynote 界面右上角的图标,iPhone 会搜寻附近的 Mac 设备,按照提示连接成功后就可以用手机来控制 Keynote 的翻页、显示激光笔、涂鸦标注等。该功能的使用需要 iPhone 和 Mac 处在同一个无线网络下,在找不到 Wi-Fi 的情况下,打开 iPhone 的热点、Mac 连接进去就可以了。从此再也不用担心没有翻页笔啦!

开启控制翻页功能

当然你要用 iPad 来控制的话我也不拦着你。

其他

本段内容与我介绍 Numbers 的文章相同:

Numbers 表格——每个人都能做自己的统计学家

Keynote 支持以文本方式插入并显示 LaTeX 公式。点击菜单栏「插入」->「方程」,或使用快捷键 Command + Option + E 即可唤出 LaTeX 编辑器界面。在编辑框内输入 LaTeX 或 MathML 公式,点击插入即可。在方程编辑区域下方有一个「方程预览」区域,可以预览 LaTeX 代码的运行结果,检查 LaTeX 代码是否有误。

Keynote 还提供了一个类似「Time Machine」的版本复原功能,它会定期对文件的快照进行备份,供用户回到文件过去的状态。依次点击菜单栏「文件」->「复原到」->「浏览所有版本...」,可以看到当前的表格文件以类似于 Time machine 的方式呈现出来,点击上下箭头或右方的时间轴可以选择回退的时间。这一功能相当于给了用户一颗“后悔药”,即使在改错内容的情况下,也能够还原到出错之前的版本。

总结

作为 Apple 官方御用的幻灯片制作软件,Keynote 承担了 Apple 所有发布会的幻灯片制作,甚至其他品牌的产品发布会的幻灯片也可能是使用 Keynote 制作的。Keynote 凭借其强大的功能和简单易上手的特性,使得制作酷炫的幻灯片不再是设计高手的专利,而是每个人都可以做到的事情。对于普通用户来说,Keynote 或许是 Microsoft PowerPoint 的绝佳替代品,丰富的动画效果和能够快速上手的使用体验,这可能是 Apple 在办公软件领域打的唯一一场“胜仗”。当然,熟练使用 PowerPoint 可以实现更加复杂的幻灯片设计,两款软件并没有明显的优劣之分——都是工具,唯手熟尔。

使用 Apple 设备的各位在以后需要制作幻灯片的时候,不妨考虑一下这个 Apple 的御用软件,说不定你也能做一份 Apple 风格的演示文稿呢。

Notification Center 与 UNUserNotification

24 March 2020 at 22:48

重新温习一下两个截然不同的 notification——用于内部消息传递的 Notification Center 和 iOS 应用推送 User Notification。

一、介绍

“notification”意为“通知”,即由一方向另一方发出一个提醒,告诉对方某些事情已经完成,或者已经达到某个状态等。在实际生活中,这种消息通知的模式随处可见:快递小哥发短信说有快递到楼下了、外卖小哥打电话说有外卖到楼下了、老板通知你下午要开会,等等。我们的大脑在接收并处理这些通知之后,又会发出“通知”协调我们的身体执行相应的动作:大脑通知脚要下楼了、大脑通知脚要下楼了、大脑通知手要开始写报告了,等等。最终,这些通知可能会到达某一个终端,并触发其执行相应的动作。

在 iOS 开发中,根据通知的发送方和接收方的不同,大致有两种不同类型的通知:

  1. 应用发给用户的通知
  2. 系统发给开发者的通知

第一种通知是所有智能手机用户都非常熟悉的推送,例如在特定的时间 App 会弹出推送框向用户推送消息;第二种通知是在开发过程中使用的,开发者可以向一个“通知管理者”注册某个通知,告诉“管理者”「我需要向哪些对象发送通知」,然后在必要的时候向“管理者”发送相应的通知即可,“通知管理者”会向注册时登记的对象发送相应的通知。

二、用于开发的通知

本节讨论用于开发的通知。

Notification 是 iOS 系统下重要的消息传递机制之一,来自系统的通知封装了不同的事件信息,而自定义的通知的内容可以根据实际需要来设定。

Notification 介绍

系统内部进行消息传递的通知,实现了观察者模式。iOS 的实现方式是需要在通知中心(NotificationCenter)中注册通知,告诉通知中心需要向哪些对象发送通知,然后在需要的时候 post 相应的通知即可。对于接收通知的对象,可以选择仅接收自己感兴趣的通知。区分不同通知的方法是采用字符串或枚举作为通知的 id。

NotificationCenter

NotificationCenter 是一种通知的分发机制,可以向已注册的观察者进行信息的广播(A notification dispatch mechanism that enables the broadcast of information to registered observers)。

官方文档解读

Apple Developer 介绍了几个 NotificationCenter 类的基础用法。

  • (1)获得默认的通知中心:
NotificationCenter.default

NotificationCenter 类有一个名为 default 的类属性,用于获取进程默认的通知中心。每个线程有一个独立的 Notification Center。

⚠️注意点:

  1. 通知中心分发给观察者处理采用同步机制,也就是说,当某一对象发送一个通知时,会一直阻塞在发送方法内,直到通知中心将该通知分发给所有观察者并且全部成功处理返回后,发送者才能执行其所在线程内的后续代码。如果需要异步发送通知,可以使用 NotificationQueue(后面提及)。
  2. 在多线程的应用程序中,通知总是在发送的线程中传送,这个线程可能不同于观察者注册所在的线程。
  • (2)添加与移除观察者(接收通知的对象):
func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?)

func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol

func removeObserver(_ observer: Any)

func removeObserver(_ observer: Any, name aName: NSNotification.Name?, object anObject: Any?)

参数说明:

  1. observer:消息的接收对象
  2. selector:接收对象接收到通知后执行的方法,需要使用@objc进行修饰。如果需要在通知的时候进行数据通信,selector 需要有一个 Notification 类型的参数。
  3. name:通知名称,用于区分不同的通知,接收者仅接收这个名字的通知。实际上是一个关联值为 String 的 enum,通过 Notification.Name.init()创建。
  4. object:消息的来源,接收者只接受从这个发送者发出来的消息。设置为 nil 表明接收对象接收所有来源的某个通知。
  5. queue:将 block 参数添加到哪一个 OperationQueue 上,如果为 nil,block 会在 post 的线程上同步执行
  6. block:接收对象接收到通知后执行的闭包,逃逸闭包,基本上和 selector 相同,但是更加 Swifty 一些。该 block 会被复制到到通知中心,直至 observer 被移除。
  • (3)发送通知:
func post(Notification)

func post(name: NSNotification.Name, object: Any?, userInfo: [AnyHashable : Any]?)

func post(name: NSNotification.Name, object: Any?)

参数含义与 addObserver 基本相同,多出来的 userInfo 是用过数据通信的参数,在接受者的 selector/block 会有一个 Notification 类型的参数,通过参数的 userInfo 属性获取到通知发过来的参数。

注意:在 post 的时候并没有显示指明哪些对象接收通知,所有存在的 MyObserver 实例都会收到这个通知。因此示例代码会引发一个 warning,提示 observers 变量被写入(置空)但未被读取。

自定义通知的注册和响应

通知类型其实就是一个字符串,我们也可以使用自己定义的通知,同时也可以通过 userInfo 参数传递用户自定义数据。

Example code:

class MyObserver {
  
  var name: String = ""
  
  init(name: String) {
    self.name = name
    // 设置通知名称
    let notificationName = NSNotification.Name(rawValue: "DownloadImageNotification")
    // 添加观察者
    NotificationCenter.default.addObserver(self, selector: #selector(downloadImage(notification:)), name: notificationName, object: nil)
  }

  @objc private func downloadImage(notification: Notification) {
    // 使用 notification 参数进行数据通信
    let userInfo = notification.userInfo as! [String:Any]
    let value1 = userInfo["value1"] as! String
    let value2 = userInfo["value2"] as! Int
    print("\(name)获取到通知,用户数据是[\(value1), \(value2)]")
    sleep(3)
    print("\(name)执行完毕")
  }

  deinit {
    // 移除观察者
    NotificationCenter.default.removeObserver(self)
    print("删除\(name)")
  }
  
}

class ViewController: UIViewController {
  
  override func viewDidLoad() {
    super.viewDidLoad()
    // create custom observers
  	var observers = [MyObserver(name: "观察器1")]
    print("发送通知")
    let notificationName = Notification.Name(rawValue: "DownloadImageNotification")
    // 发送同名通知
    NotificationCenter.default.post(name: notificationName, object: self, userInfo: ["value1":"用户名", "value2":12345])
    print("通知完毕")
    observers = []
  }

}

运行结果:

普通 post 运行结果

可以看到,在主线程调用 post 发出通知后,会立即在 post 的线程上同步执行 selector。如果在主线程上发出通知后执行的操作比较耗费时间,需要将耗时任务分到异步线程中执行,例如:

@objc private func downloadImage(notification: Notification) {
  // 使用 GCD 将任务放入全局异步线程中
  DispatchQueue.global().async {
    // userInfo is used to transfer user defined data
    let userInfo = notification.userInfo as! [String:Any]
    let value1 = userInfo["username"] as! String
    let value2 = userInfo["id"] as! Int
    print("\(self.name)获取到通知,用户名:\(value1), 密码:\(value2)")
    sleep(3)
    print("\(self.name)执行完毕")
  }
}

运行结果变为下图,此时主线程没有被阻塞。:

将耗时任务分到全局异步线程中执行

系统通知的注册和响应

除了自定义的通知以外,还可以使对象接收 iOS 系统发来的通知。系统通知实际上是一个有着特殊名字的通知,Notification.Name 的枚举值包含了一系列系统通知,具体可用选项比较多,请参考官方文档

Example code:

// create an instance of notification center
let nc = NotificationCenter.default

// fetch the main queue
let queue = OperationQueue.main

// add an observer which listens to the UIApplicationDidEnterBackground notification.
let observer = nc.addObserver(forName: .UIApplicationDidEnterBackground, object: nil, queue: queue) {
  noti in
  print("Entering background...")
}

可以看到接收系统通知和接收自定义通知基本上是一样的。

NotificationQueue

在 NotificationCenter 中,post 出去的通知会马上到达 observer 手中。如果我们需要使通知延迟一段时间后再进行广播,可以使用 NotificationQueue 对 Notification 进行管理。NotificationQueue 维护一个队列,enqueue 的通知不会立刻发送到 observer,而是在 dequeue 的时候将满足条件的 Notification 按先进先出的顺序逐个进行发送。

官方文档解读

  • (1)创建队列:
NotificationQueue.init(notificationCenter: NotificationCenter)

可以将当前线程的通知发送到另一个线程上。

  • (2)获得默认队列:
NotificationQueue.default

获得当前线程的默认通知队列。一般来说一个线程拥有一个通知中心、维护一个通知队列。

  • (3)通知入列与出列:
func enqueue(Notification, postingStyle: NotificationQueue.PostingStyle, coalesceMask: NotificationQueue.NotificationCoalescing, forModes: [RunLoop.Mode]?)

func enqueue(Notification, postingStyle: NotificationQueue.PostingStyle)

func dequeueNotifications(matching: Notification, coalesceMask: Int)

参数说明:

  1. matching:用于进行判断的“模板”通知。
  2. postingStyle:枚举值,指明发送通知的时间,可以是 asap、whenIdle、now 三者之一,分别表示当前通知回调结束时、线程空闲时、立刻发送。
  3. coalesceMask:屏蔽位,指明屏蔽通知的方式。0 表示不屏蔽,奇数表示屏蔽与 matching 同名的通知,偶数表示屏蔽与 matching 同发送者的通知。
  4. forModes:规定只能在 RunLoop 处于哪些模式中的时候才能发送通知。

在 dequeue 的时候,通知是按照 enqueue 的顺序出来的,如果需要屏蔽的话,通知中心就不会将被屏蔽的通知广播出去。

示例代码

import UIKit
import NotificationCenter

class MyObserver {
    var name: String = ""
    init(name: String) {
        self.name = name
        // 设置通知名称
        let notiName1 = Notification.Name(rawValue: "StatusNotification")
        let notiName2 = Notification.Name(rawValue: "DownloadImageNotification")
        let notiName3 = Notification.Name(rawValue: "AlertNotification")
        
        NotificationCenter.default.addObserver(self, selector: #selector(statusCheck(notification:)), name: notiName1, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(downloadImage(notification:)), name: notiName2, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(handleAlert(notification:)), name: notiName3, object: nil)
    }
    
    @objc private func statusCheck(notification: Notification) {
        // userInfo is used to transfer user defined data
        let userInfo = notification.userInfo as! [String:Any]
        guard let type = userInfo["type"] as? String, let username = userInfo["username"] as? String, let userID = userInfo["id"] as? Int else {
            print("Error in parsing parameters")
            return
        }
        print("\(self.name)获取到通知\(notification.name)")
        print("类型:\(type), 用户名:\(username), ID:\(userID)")
        print("\(self.name)执行完毕\(notification.name)")
        print("--------------------")
        sleep(2)
    }
    
    @objc private func downloadImage(notification: Notification) {
        // userInfo is used to transfer user defined data
        let userInfo = notification.userInfo as! [String:Any]
        guard let imageName = userInfo["imageName"] as? String, let url = userInfo["url"] as? String else {
            print("Error in parsing parameters")
            return
        }
        print("\(self.name)获取到通知\(notification.name)")
        print("图片名:\(imageName), 链接:\(url)")
        print("\(self.name)执行完毕\(notification.name)")
        print("--------------------")
        sleep(2)
    }
    
    @objc private func handleAlert(notification: Notification) {
        // userInfo is used to transfer user defined data
        let userInfo = notification.userInfo as! [String:Any]
        guard let reason = userInfo["reason"] as? String, let code = userInfo["code"] as? Int else {
            print("Error in parsing parameters")
            return
        }
        print("\(self.name)获取到通知\(notification.name)")
        print("原因:\(reason), 错误代码:\(code)")
        print("\(self.name)执行完毕\(notification.name)")
        print("--------------------")
        sleep(2)
    }
    
    deinit {
        // 在 deinit 中移除观察者
        NotificationCenter.default.removeObserver(self)
        print("删除\(name)")
    }
    
}

class ViewController: UIViewController {
    // create custom observers
    let observers = [MyObserver(name: "观察器1")]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("发送通知")
        
        let notiName1 = Notification.Name(rawValue: "StatusNotification")
        let notiName2 = Notification.Name(rawValue: "DownloadImageNotification")
        let notiName3 = Notification.Name(rawValue: "AlertNotification")
        
        let noti1 = Notification(name: notiName1, object: self, userInfo: ["type":"log in", "username":"Andy", "id":12345])
        let noti2 = Notification(name: notiName2, object: self, userInfo: ["imageName":"Apple", "url":"https://www.apple.com"])
        let noti3 = Notification(name: notiName3, object: self, userInfo: ["reason":"Page Not Found", "code":404])
        let noti4 = Notification(name: notiName1, object: self, userInfo: ["type":"log out", "username":"Andy", "id":12345])
        
        print("入列三个通知")
        let queue = NotificationQueue.default
        queue.enqueue(noti1, postingStyle: .whenIdle)
        queue.enqueue(noti2, postingStyle: .whenIdle)
        queue.enqueue(noti3, postingStyle: .whenIdle)
        print("入列完毕")
        
        print("出列三个通知")
        queue.dequeueNotifications(matching: noti1, coalesceMask: 0)
        queue.dequeueNotifications(matching: noti2, coalesceMask: 0)
        queue.dequeueNotifications(matching: noti3, coalesceMask: 0)
        print("出列完毕")
        
        // 对比同步 post
        NotificationCenter.default.post(noti4)
        
        print("通知完毕")
    }
    
}

为方便起见,在代码中仅设置了一个 observer 对多个通知进行监听,将三个不同名的通知入列,规定在线程空闲的时候才发送通知,并且全部不屏蔽地发送。为进行对比,在出列完毕后增加了一个普通的同步 post。

运行结果如下:

NotificationQueue 运行结果

可以看到,在 enqueue 通知后,接收者没有马上收到消息,而是在 dequeue 通知后并且达到 postingStyle 设置的时机才会统一发送通知,而作为对比的同步 post 是马上阻塞当前线程发送通知。如果将 postingStyle 设置为 .now 的话,会跟普通的 post 一样马上发送通知。coalesceMask 设置为 0 时不会进行屏蔽;如果将任意一个 coalesceMask 改成 1,对应的通知就不会发送;如果将任意一个 coalesceMask 改成 2,由于三个通知的发送者都是 self,因此 noti1、noti2、noti3 全部被屏蔽。

DistributedNotificationCenter

前面提到的 NotificationCenter 是在应用进程内向对象发送通知。Apple 提供了 DistributedNotificationCenter 类来实现不同的应用进程之间的通信。每一个应用进程都有一个默认的 DistributedNotificationCenter,用来接收其他进程发过来的通知,同时负责将本进程的通知广播到其他进程的 DistributedNotificationCenter 中。

在使用上,DistributedNotificationCenter 和进程内的 NotificationCenter 差别不大,同样是通过一个名为 default 的类实例获得默认的通知中心,通过 addObserverremoveObserver 添加、移除观察者,通过 post 发送通知。增加了一个 Bool 类型的 suspend 属性来设置是否挂起通知中心,不接收和发送进程间的通知。

三、向用户发送的通知

本节讨论 iOS 推送。

UserNotifications——用户通知

从 iOS 10 起,用户通知(即呈现给用户的消息推送)发生了较大变化,Apple 的目的是为了进一步简化用户通知的使用,并向开发者提供更加强大的功能,使用户通知能够呈现更多的内容。

使用 UserNotifications 创建用户推送主要包括以下几个步骤:

  1. Create a trigger. Depending on the situation that triggers a notification, select a different constructor.
  2. Create the content of the notification, using UNMutableNotificationContent.
  3. Create the request, using the trigger and content above.
  4. Add the request to the notification center, using UNUserNotificationCenter.current().add()
  5. Remember to ask the user for permission to show notification.
  6. To provide attachment, use the UNNotificationAttachment class to add attachment to the notification.
  7. To add an action in the notification, use UNNotificationAction and UNNotificationCategory
    1. Create the action for the notification
    2. Defines the category of the action

Just to remind: the majority of notification needs an identifier. Do not mess them up! It is recommended to use enum or at least constants to manage these identifiers.

整个过程可以用一个简单的图来说明:

创建用户推送的流程

获得默认通知中心

和 NotificationCenter 相同,UserNotifications 同样有一个通知中心对整个 App 的消息推送进行管理。可以用 UNUserNotificationCenter.current() 获得默认的用户通知中心。

获取用户权限

需要弹出推送的 App 需要申请获得用户权限,也就是下面这个 Alert 框:

用户权限弹窗

这个框不是由开发者自己写的,而是由 iOS 系统弹出来的。可以使用通知中心的 requestAuthorization(options:completionHandler:) 申请不同的权限,包括推送弹窗、提示音、角标等,并且在回调的 handler 中处理用户的选择。

let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
    
    if let error = error {
        // Handle the error here.
    }
    
    // Enable or disable features based on the authorization.
    if granted {
      // user permit
    } else {
      // user deny
    }
}

通常是在应用第一次打开的时候向用户请求权限,因此通常是在 AppDelegate 的方法中调用。

检查当前的提醒设置

用户可能会随时更改提醒的权限,比如突然不想接收 App 的提醒了,这时再进行推送时无效的。可以使用通知中心的 getNotificationSettings(completionHandler:) 获得当前的提醒设置,例如用户是否允许弹窗等。

let center = UNUserNotificationCenter.current()
center.getNotificationSettings { settings in
    guard (settings.authorizationStatus == .authorized) ||
          (settings.authorizationStatus == .provisional) else { return }

    if settings.alertSetting == .enabled {
        // Schedule an alert-only notification.
    } else {
        // Schedule a notification with a badge and sound.
    }
}

设置推送触发器

本地推送可以设置在某个时间或满足某些条件时向用户发送通知,这是通过设置触发器 UNNotificationTrigger 来实现的,包括四种不同的触发器:

  • UNCalendarNotificationTrigger
  • UNTimeIntervalNotificationTrigger
  • UNLocationNotificationTrigger
  • UNPushNotificationTrigger

从它们的名字可以知道,分别是在给定日期、给定时间间隔、给定位置进行推送,最后一个是手动进行推送。不同的 trigger 有不同的初始化方法,按照要求进行设置即可。

设置推送内容

通过 UNMutableNotificationContent 来设置推送显示的内容,包括标题(title)、副标题(subtitle)、主体(body)、角标数字(badge)、提示音(sound)、自定义内容(userInfo)、附件(attachments)等,其中附件可以包含文本文件、图片甚至视频,但是有一定的条件限制,详情需要查阅官方文档

let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationStringForKey("Hello!", arguments: nil)
content.body = NSString.localizedUserNotificationStringForKey("Hello_message_body", arguments: nil)
content.sound = UNNotificationSound.default()

附带一提,在 Assets.xcassets 中,有两栏用来设置 iPhone 和 iPad 上的提醒图标。

创建推送请求

设置好触发器 trigger 和推送的内容 content 后,就可以通过 UNNotificatonRequest 来创建推送请求了。不同的推送请求通过 String 类型的 identifier 来进行区分,最后将 request 添加到用户通知中心即可。

// Create the request
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, 
            content: content, trigger: trigger)

// Schedule the request with the system.
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request) { (error) in
   if error != nil {
      // Handle any errors.
   }
}

可以通过 func getPendingNotificationRequests(completionHandler: ([UNNotificationRequest]) -> Void) 获得所有还没触发的用户通知。

推送请求一旦创建,在满足触发条件之前都会保持 active 的状态。如果在创建 trigger 的时候设置了 repeats 为 true 的话。可以根据 identifier 来取消某个推送或者取消所有推送。

func removePendingNotificationRequests(withIdentifiers: [String])

func removeAllPendingNotificationRequests()

设置代理

遵循 UNUserNotificationDelegate 协议的类可以作为 UserNotification 的代理,接收并处理用户输入、如何处理推送等。Apple 提示我们需要在 App 完成启动之前完成代理的设置,令 AppDelegate 遵遁 UNUserNotificationDelegate 即可,并在 application(_:didFinishLaunchingWithOptions:) 中设置代理为 self。

UNUserNotificationDelegate 定义了三个方法:

// 用户行为响应
func userNotificationCenter(UNUserNotificationCenter, didReceive: UNNotificationResponse, withCompletionHandler: () -> Void)

// 显示本地推送
func userNotificationCenter(UNUserNotificationCenter, willPresent: UNNotification, withCompletionHandler: (UNNotificationPresentationOptions) -> Void)

func userNotificationCenter(UNUserNotificationCenter, openSettingsFor: UNNotification?)

必须设置代理之后,本地推送才能生效。

处理用户输入

有时候,我们需要让推送具有与用户交互的功能,用户在看到推送之后,只需要点按可用的选项就能够向 App 发出指令,而不需要真正进入到 App 中。通过 UNNotificationAction 和 UNNotificationCategory 相结合来声明一个能够响应用户输入的 action。

  • UNNotificationAction

可以理解为通知附带的按钮,用户可以点击按钮进行交互。

// Define the custom actions.
let acceptAction = UNNotificationAction(identifier: "ACCEPT_ACTION",
      title: "Accept", 
      options: UNNotificationActionOptions(rawValue: 0))
let declineAction = UNNotificationAction(identifier: "DECLINE_ACTION",
      title: "Decline", 
      options: UNNotificationActionOptions(rawValue: 0))

不同的 action 通过 identifier 来进行区分;title 为按钮的标签;option 为 action 的选项,包括 authenticationRequired(仅在设备解锁的时候可以交互)、destructive(该行为可能导致不可逆的后果)、foreground(用户需要打开 App 作进一步的交互)。

  • UNNotificationCategory

Category 将 action 进行分类,在创建 content 的时候设置 categoryIdentifier,可以规定哪些推送支持怎么样的行为。

// Define the notification type
let meetingInviteCategory = 
      UNNotificationCategory(identifier: "MEETING_INVITATION",
      actions: [acceptAction, declineAction], 
      intentIdentifiers: [], 
      hiddenPreviewsBodyPlaceholder: "",
      options: .customDismissAction)
// Register the notification type.
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.setNotificationCategories([meetingInviteCategory])

// set content's category identifier
content.categoryIdentifier = "MEETING_INVITATION"

处理用户输入则是在代理方法 func userNotificationCenter(UNUserNotificationCenter, didReceive: UNNotificationResponse, withCompletionHandler: () -> Void) 中进行的,通过 response 参数的 actionIdentifier 属性来区分不同的 action,需要和创建 action 的时候定义的 identifier 完全相同才能正确识别。

func userNotificationCenter(_ center: UNUserNotificationCenter,
       didReceive response: UNNotificationResponse,
       withCompletionHandler completionHandler: 
         @escaping () -> Void) {
       
   // Get the meeting ID from the original notification.
   let userInfo = response.notification.request.content.userInfo
   let meetingID = userInfo["MEETING_ID"] as! String
   let userID = userInfo["USER_ID"] as! String
        
   // Perform the task associated with the action.
   switch response.actionIdentifier {
   case "ACCEPT_ACTION":
      sharedMeetingManager.acceptMeeting(user: userID, 
                                    meetingID: meetingID)
      break
        
   case "DECLINE_ACTION":
      sharedMeetingManager.declineMeeting(user: userID, 
                                     meetingID: meetingID)
      break
        
   // Handle other actions…
 
   default:
      break
   }
    
   // Always call the completion handler when done.    
   completionHandler()
}

示例代码

最后贴一段示例代码吧(部分来源 Apple Developer 官网):

  • AppDelegate.swift
import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        // 设置 UserNotifications 代理
        UNUserNotificationCenter.current().delegate = self
        
        // 请求本地推送的权限
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
            if let error = error {
                print("Oops, we've met an error: \(error)")
            }
            
            if granted {
                print("The user grants us the permission to push notifications😃")
            } else {
                print("The user denies our permission😣")
            }
        }
        return true
    }
  
}

// 代理方法
extension AppDelegate: UNUserNotificationCenterDelegate {
    public func userNotificationCenter(
      _ center: UNUserNotificationCenter, 
      didReceive response: UNNotificationResponse, 
      withCompletionHandler completionHandler: @escaping () -> Void) {
        print(response.actionIdentifier)
        completionHandler()
    }
    
    func userNotificationCenter(
      _ center: UNUserNotificationCenter,
      willPresent notification: UNNotification,
      withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // New in iOS 10, we can show notifications when app is in foreground, by calling completion handler with our desired presentation type.
        print("something")
        // 设置提醒的方式:.alert, .sound, etc
        completionHandler(.alert)
    }
}

  • ViewControler.swift
import UIKit
import UserNotifications

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 设置推送内容
        let content = UNMutableNotificationContent()
        content.title = "This is title"
        content.subtitle = "This is subtitle"
        content.body = "This is body"
        content.sound = UNNotificationSound.default
        
        // 定义用户交互方式
        let acceptAction = UNNotificationAction(identifier: "ACCEPT_ACTION",
              title: "Accept",
              options: UNNotificationActionOptions(rawValue: 0))
        let declineAction = UNNotificationAction(identifier: "DECLINE_ACTION",
              title: "Decline",
              options: UNNotificationActionOptions(rawValue: 0))
        
        // 定义推送能够响应的交互类别
        let meetingInviteCategory =
              UNNotificationCategory(identifier: "MEETING_INVITATION",
              actions: [acceptAction, declineAction],
              intentIdentifiers: [],
              hiddenPreviewsBodyPlaceholder: "",
              options: .customDismissAction)
        
        // 向通知中心注册交互类别
        let notificationCenter = UNUserNotificationCenter.current()
        notificationCenter.setNotificationCategories([meetingInviteCategory])
        content.categoryIdentifier = "MEETING_INVITATION"
        
        // 设置一个 10 秒的触发器
        // 若希望重复提醒(repeats 为 true),则两次推送的时间间隔(timeInterval)必须大于 60,否则会崩溃。
        let tenSecondsTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
        
        let request = UNNotificationRequest(identifier: "tenSeconds", content: content, trigger: tenSecondsTrigger)
        
        UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
                print(error)
            } else {
                print("Notification request added.")
            }
        }
    }
    
}

五、总结

本篇文章对 NotificationCenter 和 UNUserNotification 进行了学习,对于我来说认识了「观察者模式」,并且知道了如何根据条件对用户发起推送。需要注意的点是 NotificationCenter 和 UNUserNotification 都非常依赖于字面量的 identifier 区分不同的通知,最好将其转换成 struct 或 enum,防止手误。

另外附上一个更加完整的 demo,将 NotificationCenter 和 UserNotifications 的功能进行简单的整合。

参考链接

  1. https://www.jianshu.com/p/209ef870e131
  2. https://juejin.im/post/59422d5861ff4b006cc66be1
  3. https://developer.apple.com/documentation/foundation/notificationcenter
  4. https://developer.apple.com/documentation/usernotifications/unusernotificationcenter

Organize your life——使用「备忘录」、「提醒事项」和「日历」

19 March 2020 at 11:45

要想生活有效率,提醒备忘和日历。

介绍

在如今这个快节奏的社会,大家都希望把自己的工作、生活整理得井井有条,这要求我们除了合理安排自己的时间之外,还需要及时处理生活中的各种事项——经验告诉我,生活中的许多烦恼都是因为「没能及时处理各种事项」导致的。然而遗憾的是,生活中有太多事情等着我们去处理,手忙脚乱、没有条理地去应付,反而会适得其反,使自己陷入「笨手笨脚 -> 心情不靓 -> 笨手笨脚」的死循环中。对于我来说,最佳的处理事情的方式是「全身心地投入到当前正在做的事情中」,并借助外部工具来提醒自己接下来在什么时候需要做什么事情。这有点像一个「队列」的数据结构:工具将我需要处理的事情入列,我将事情出列后进行处理。当然,这个队列还需要有优先级以及能够随时插入新的任务:对于优先级较高的任务,需要调整处理事项的顺序;对于需要紧急处理的任务,需要能够停下手上的事情,处理完紧急事项后再回来继续原来的工作。

为了寻找趁手的工具,我依然奉行着自己的准则:「能用自带的就不用第三方的」。这一次,我把目光投向了 Apple 设备的自带三大效率应用——「备忘录」、「提醒事项」和「日历」。一开始,我对于这三个应用都抱有一种不太关注的态度,毕竟这三款效率应用基本上是每一部智能手机和电脑的标配,在 Android、iOS、Windows、macOS 上都能看到它们的身影,似乎只是一个随系统附带的“赠品”,它们在日常生活中的应用似乎也仅限于处理一些非常简单的任务。然而,随着我对使用场景的不断挖掘,这三款系统自带的 App 为我带来了无与伦比的效率提升。

「备忘录」——最简单的全能笔记本

作为「记性不好星人」的一员,在没有电子设备的过去,我更习惯用便签纸、笔记本等文具来帮助自己来记下自己容易忘记的事情。进入数字时代,具有备忘功能的 App 取代了文具的功能,并在某些方面比实体笔记本带来了更好的体验。备忘录是 iOS 和 macOS 上的自带 App,可以通过 iCloud 进行 Apple 设备之间的同步和 Handoff 接力,在 macOS 和 iOS 的 iCloud 管理界面即可开启备忘录的 iCloud 同步;对于 Handoff 接力功能,在 macOS 和 iOS 的系统偏好设置中可以开启,开启后,在一台设备上进行的操作,可以马上切换到另一台设备在之前的位置继续。个人认为 Handoff 对于备忘录和接下来要介绍的提醒事项、日历来说意义远没有 iCloud 同步大,是否开启看个人喜好吧。

备忘录的使用非常简单,主界面中最大的区域是编辑区域,能够对条目内容进行编辑,本质上是一个富文本编辑器;左方边栏分别是文件夹列表和条目列表;上方工具栏可以对文字内容进行设置,包括查看附件、删除条目、新建条目、改变字号、创建表格、创建清单等。

备忘录主界面

iOS 13 和 macOS Catalina 对备忘录进行了全新的设计,引入了「画廊视图」,能够通过缩略图对条目内容进行预览,双击缩略图对条目进行编辑,看上去会比列表视图好看点。

画廊视图

备忘录的内容编辑和普通的富文本编辑器并没有太大的差别,在菜单栏的「格式」中可以对文本格式进行设置,包括改变字号、插入表格、创建清单等,相同的操作也可以在工具栏中进行或者使用快捷键完成。通过这些不太丰富但也足够使用的功能,能够快速创建一个有着漂亮样式的条目,对于不熟悉 Markdown 语法的用户,甚至能够用备忘录完成一篇文章的简单排版。备忘录还支持添加多种附件,包括照片、视频、扫描件、地图、网站、文稿等,一般来说只需要将文件直接拖拽到备忘录中就可以插入到当前条目中,另外还能够在其他的应用程序——如 Safari、地图等——将需要的内容发送到备忘录中

管理备忘录中的附件内容

备忘录支持同时使用 iCloud 账户和本地账户,在偏好设置中可以选择「默认账户」和「启用“我的 Mac 上”账户」。本地账户的笔记内容仅会保存在当前设备上,macOS 和 iOS 端都可以进行设置。为了进一步提高笔记内容的安全性,在偏好设置中还能够设定密码对备忘录账户进行锁定。

备忘录偏好设置

iOS 和 iPadOS 上的备忘录使用方法和 macOS 上基本相同,但在 iPad 上,得益于 Apple Pencil 的硬件加持,备忘录已经变成了我的主力电子笔记本了。为了在 iPad 上让备忘录看上去与传统的笔记本更加相似,可以在「设置」->「备忘录」->「横线与网格」中选择自己喜欢的默认横线或网格样式。

打开备忘录的网格线

打开网格线后,在 iPad 上使用 Apple Pencil 进行手写笔记的书写就变得与传统的笔记本非常相似了。备忘录提供了多种不同的笔触,例如水笔、荧光笔、铅笔等,并且有不同颜色可以切换,另外还有框选和直尺的附加功能。另一方面,备忘录的一个重要的应用场景是快速记录下一闪而过的想法,但是珍贵的 idea 很容易在「解锁 iPad -> 打开应用 -> 开始记录」的流程中稍纵即逝。作为原生 App 的备忘录,支持快速创建新的条目,在「设置」->「备忘录」->「在锁定屏幕上访问“备忘录”」中打开该功能,就能够在设备锁屏的情况下,直接使用 Apple Pencil 轻触屏幕就能够记录自己的想法了。

iPad 备忘录充当电子笔记本

除了备忘录以外,iPad 上还有不少第三方的手写笔记应用,例如 Notability、GoodNotes、OneNote 等。对于只使用过 OneNote 的我来说,OneNote 更像是一个有着无限大书写区域的白板,可以随意在上面写写画画、插入文档,但是对我来说这种设计不太适合用来记笔记,并且 OneDrive (not for business) 的同步速度堪忧。当然,OneNote 作为一款老牌全平台的免费笔记软件也是一个不错的选择,页面的无限扩展也为笔记内容的排版带来了更高的自由度。至于 Notability 和 GoodNotes 两款新晋笔记 App,网上已经有数不胜数的教程和评测了,有需要的朋友可以自行搜索相关内容。

对于我来说,备忘录提供的功能已经足够应付我的日常工作、学习生活,摇身一变成为我的全能电子笔记本。目前,我基本上把所有的笔记内容转移到了备忘录中,尤其是手写笔记。但是,在笔记的有序归纳整理方面,备忘录的功能几乎为零,只提供了一个列表结构对笔记进行一级分类。对于一些重要的、需要反复查看和仔细整理的资料,更推荐使用 Bear、Evernote 等专门的笔记应用对知识做进一步的整理和归纳。

「提醒事项」——What should I do next?

同样在 iOS 13 和 macOS Catalina 中迎来新生的还有提醒事项。作为一款 Todo-list 应用,提醒事项已经做到了简洁得不能再简洁了——记录下你要做的事情,done。但是,简洁并不意味着简单。提醒事项为我们提供了三个智能分类:今天、计划和旗标,会对所有添加到提醒事项中的条目自动进行一个简单的分类,方便我们快速查找特定的条目。当然,用户也能够创建列表进行自定义的分类,将不同的任务按照自己的方式进行归类。

提醒事项主界面

对于一条特定的提醒事项,可能需要在某些特定的条件下提醒我们去完成。点击事项右边的感叹号图标,能够对事项进行更进一步的设置,包括添加备注、设置提醒条件、设定优先级、添加 URL 和图像等。在实际的应用中,日期提醒应该是最常用到的。对于一些一次性的临时任务,可以设定在某个时间对我们发出提醒,避免耽误任务的执行。

进一步设置提醒事项条目

提醒事项同样支持 iCloud 同步与 Handoff 接力。全新改版后的提醒事项让我删除了所有其他 Todo-List 应用,我并不需要其他各种花里胡哨的功能,Todo-List 只需要做好它的本职工作——Tell me what to do next, and I'll do that。

「日历」——保持日程记录的仪式感

老实说,我一直没有使用日历的习惯,就算是实体的日历也并非我生活中的常客。当我尝试着使用日历去记录我的日程之后,我对日历的使用有了新的看法。在日历上记录日程更像是一种仪式,告诉自己「我在 3 月 19 日需要参加班级会议」、「4 月 17 日有一个漫展开幕」,这种仪式感是其他应用无法比拟的。「日程」这一概念更强调的是「已经确定在某一天需要进行的事项」,使用日历能够更加清晰地记录下这些事项,在一年的时间结束之后,能够回顾自己在过去的一年中都经历了哪些重要的事情。

iOS 和 macOS 自带的日历应用同样走的是简约风格,一进入到主界面就是一个类似于月历的界面设计。一个非常有意思的小细节是,日历 App 的图标上的日期会根据实际的日期进行动态的改变。

日历主界面

可以在日历中切换显示方式为日、周、月、年,对实体的日历、月历、年历进行了一个模拟。在偏好设置中可以添加显示公历以外的历法,如农历、希伯来历、伊斯兰历等,甚至支持对网络日历的订阅,可以通过「文件」->「新建日历订阅」,输入想要订阅的 URL 即可订阅网络日历。双击某一天的空白处可以创建新的日程,创建日程后可以添加日程备注、设置日程的提醒时间等,对于同样是 Apple 设备的伙伴来说,还可以将日程进行共享、邀请他人参与日程等。除了创建日程之外,还能够根据自己的需要创建新的日历——Apple 的取名方式有点迷,在这里我认为更像是一个标签系统,为不同的日程打上标签进行分类管理。从宏观上来看,日历的设计和一众远程办公软件有异曲同工之处,两者都是将要做的事情与具体的日期紧密相连,这或许才是日历的正确打开方式吧,

设置新的日程

比较有意思的是,日历提供了一个出发提醒的功能,能够根据当前定位、日程位置信息和地图提供的交通状况,告诉用户什么时候出发可以准时到达目的地,对于一些对时间要求比较高的日程来说不失为一个贴心的功能。

设置出发时间的提醒

日历同样支持 iCloud 同步与 Handoff 接力。值得注意的是,Apple 似乎认为日历应该有更大的潜在发展空间,为此在 Automator 的新建窗口中直接提供了「日历提醒」的自动化流程,能够在某个日期自动触发。

Automator 支持日历提醒的自动化流程

除了直接创建日历提醒之外,还能够在普通的 Automator Workflow 工作流程中添加对日历的支持,功能主要集中在日程的创建、获取内容、日程提醒等,实际上就是让 Automator 自动执行一些每天都要执行的 routine work,配合 Automator 的其他操作创建新的日程,避免遗忘。

Automator 对日历的支持

Automator 是一个神奇的工具,通过半编程的方式来自动完成一些繁杂的任务。由于我对 Automator 的使用不多,无法作出准确的使用教程。或许在我深入学习 Automator 的使用技巧后可以分享一下自己的经验...?

有朋友可能会觉得日历和提醒事项的功能相互重叠。在我看来,两者确实有交叉的地方,我觉得重复日程、确定时间的日程更适合使用日历来记录,例如会议、纪念日等;而临时事项、可能经常发生变动的事项更适合使用提醒事项来记录,例如每日任务、临时要去做的紧急事情等,

One More Thing——番茄钟

在使用提醒事项和日历安排好自己的任务之后,就需要全身心地投入到当前任务中,这就是「番茄钟」应用的存在意义——将时间分成「工作时间」和「休息时间」两部分,在工作时间只干活、不休息,在休息时间只休息、不干活,防止自己被不必要的干扰打断工作流程。实践证明,番茄钟能够让我更快地进入应有的工作状态,专注度进一步提升,更有效率地完成手头上的工作。

iOS 上的「潮汐」是一款非常不错的番茄钟应用,提供多种白噪声背景。它的「翻转手机来专注」是一个我非常喜欢的功能,能够有效地斩断我玩手机的想法。潮汐还内置了睡眠和小憩的闹钟功能,能够以更加轻柔的方式唤醒,让起床不再困难。

潮汐 - 睡眠、专注与冥想的白噪音番茄钟

macOS 上暂时没有找到令人眼前一亮的番茄钟应用,目前我使用的是 Be Focused,简单易用,能够规定自己每天需要完成一定数量的番茄钟,不失为一个督促自己干活的方式。

Be Focused - 工作和学习的计时器

当然,要想让番茄钟发挥作用,自觉还是最重要的——开着番茄钟玩游戏可不是提高效率的方式。

总结

「整理生活」是一项技术活,每个人都应该有一套属于自己的方法,帮助自己管理生活中的大事小事。对于我来说,充分利用原生 app 来帮助自己安排每天要做的事情,然后倾注全部的精力完成好手上的工作,可能是最适合我的工作流:备忘录作为我的电子笔记本,能够帮助我记录下重要的想法、灵光一现的 idea 和学习笔记;提醒事项和日历会在适合的时间提醒我应该做什么事情,让我不再需要分心记住接下来有哪些事情要做;番茄钟能够保持我对手头任务的专注度,不被其他不重要的事情打断。

系统原生 App 与第三方 App 的通力合作,让我得以高效率地完成「真正重要的任务」,这或许也是一种「断舍离」的体现吧!


本文为原创文章,转载请注明。

2020年,我「还」在用哪些数码产品?

13 March 2020 at 15:58

老设备也有春天

作为一名勤(口)俭(袋)持(没)家(钱)的在校学生,我对比数码产品之类的比较贵重的东西都有着一种强烈的爱惜之情,希望能够在充分发挥物品的功能的同时,尽可能地延长物品的使用年限,让它们能够陪伴我更久一些。对于数码产品,我一向是抱着「喜新不厌旧」的心态,有新的设备能够使用自然是非常开心的,但是对于一些比较旧的设备我也不会嫌弃。我相信男生都是喜欢捣鼓数码设备的吧,就像小孩子拿到新玩具一样,我在拿到新设备的时候就会有一种非常开心的感觉。随着科技的发展,数码设备的成本将会越来越低,功能越来越丰富,普通人也能够享受到科技发展带来的便利。

正如标题所描述的那样,我想在这篇文章中分享一下在 2020 年的今天,我「仍然」在使用哪些数码产品呢?

2015 年 Early Retina MacBook Pro 13.3 寸

在进入大学之前,爸妈为我添置了我的第一台笔记本电脑——2015年款的13.3寸Retina MacBook Pro,那个时候这台笔记本刚刚上市,售价 8k+,让当时的我着实吃了一惊,没有想到爸妈会给我买一台这么贵的电脑(谢谢爸妈😊)。那时候的我对电脑系统接触得不多,也不知道 MacBook 的系统和其他电脑的系统是不一样的,因此也没有对 Windows 系统有强烈的依赖,很顺利地就过渡到了 Mac 的系统中(当时还是 OS X Yosemite)。爸妈的同事还贴心地帮我用 Boot Camp 安装了双系统 Windows,这为我后面的学校课程学习提供了不小的帮助。

接口最多的最后一代 MacBook Pro

MacBook 给我的第一印象就是轻、薄、好看,摸上去的感觉很舒服。也许是上天的安排吧,这台 Mac 打开了我学习计算机和软件开发知识的大门,走进了 iOS 开发的世界(虽然仍然是个菜鸟)。直到今天,这台笔记本依旧是我不二的主力设备,Xcode、PyCharm、Typora、MarginNote 等一众优秀的软件让这台 MacBook Pro 依然不愧为生产力工具,丰富的接口也免除了新款 MacBook 特有的需要多个转接口的麻烦。唯一一个小遗憾就是,笔记本的内置硬盘只有 128 G,用来存储数据实在是不够用,于是乎,我增加了两块移动硬盘作为数据存储的解决方案,一块是 Seagate 的 1T 移动硬盘,另一块是 Toshiba 的 2T 移动硬盘,加上 5G 的 iCloud 免费空间和 5G 的 OneDrive 免费空间,数据存储和同步的问题基本得到解决。

众所周知,2015 年的 MacBook Pro 的视网膜屏存在涂层脱落的问题,在我得知这个消息的时候电脑已经使用了三年了,于是抱着试一试的心态,预约前往广州天环广场的 Apple Store 咨询了一下这个问题。Genius Bar 的小哥很爽快地就直接给我换了,大概只花了半天的时间就换好了屏幕,看上去就跟换了一台新的电脑一样。虽然现在好像又出现了涂层脱落的问题,但是影响不算特别大,将就着用了。换完屏幕后,我又为我的爱机贴上了贴纸,贴完之后简直就是一台新机器了有!没!有!

A 面和 D 面的贴纸

当然啦,毕竟是四年前的机器了,这台电脑也存在一定的问小毛病包括电池状况不佳、键盘磨损等,打算在疫情结束之后去 Apple Store 看看能不能解决,让这台笔记本继续发光发热吧!

高频率使用 command 键和空格键导致的磨损

iPhone 6s

2016年的时候,打着学习 iOS 开发的名义、同时也为了能够更加完整地体验 Apple 的产品,我入手了一代神机 iPhone 6s,当时恰逢 iPhone 7 上市、6s 价格下调,为了避免手机出现空间不足的尴尬,我直接就入手了一台 128G 的 iPhone 6s,导致的结果就是我的手机容量和电脑是一样的哈哈。平心而论,iPhone 6s 确实是一台十分优秀的机器,A9 处理器在 2020 年的今天依旧坚挺,处理各种任务都游刃有余,据说还有望撑到 iOS 14 升级...? 128G 的容量也让这台手机变成了一台游戏机,装一堆游戏、存一堆照片、看一堆视频眼都不眨,从来没有担心过空间不够用的问题。

作为我的第二台 Apple 设备,iPhone 的加入使得 MacBook Pro 和 iPhone 两者之间的生态链更加完整,两台设备之间可以进行更加紧密的协作,包括 iCloud、Handoff、AirDrop 等技术都极大地提高了我的工作学习效率。感兴趣的朋友可以关注一下我的知乎专栏

和 MacBook 一样,手机用久了也会有不少的毛病。2019 年的时候我在 Apple Store 为手机更换了电池后,明显感觉到手机的流畅度有一定程度的提升,续航时间也得到了保证。希望这台手机也能够陪伴我走完研究生的生涯吧!

Apple Watch Series 3

Apple Watch 对我来说是个意外的收获——朋友送的。在对 Apple 的产品有了一定的使用经验后,我对 Apple Watch 也有了一定的期待。这只 Apple Watch Series 3 是GPS 版的,需要配合手机才能有更好的使用体验,但是这对我来说并不是一个问题,毕竟手机是 24 小时都在身边的。我对于 Apple Watch 的使用主要包括:

  • 看时间,这是一只手表的基本素养。
  • 记录自己每天的运动消耗和锻炼时间。
  • 当作闹钟和计时器。
  • 转发 iPhone 上的通知,可以不用拿出手机扫一眼是不是重要的事情。
  • 实在腾不出手的情况下可以接电话。
  • 提醒自己每隔一段时间进行一次深呼吸和站立。
  • 找不到手机的时候,可以通过 Apple Watch 使手机发出声音,对于我这种大头虾来说是一个非常实用的功能。

在我看来,Apple Watch 更多地扮演的是「辅助手机」的角色,能够方便快捷地执行一些简单的日常操作,而不需要掏出手机。除此之外,Apple Watch 还是一个专属的「健康管家」,提醒我每天保持运动、保持健康的心态,才能够以更好的状态去面对生活中的种种困难。

Apple Watch——it tells time

iPad 2019

作为一件奖励自己 2019 年表现的礼物,iPad 2019 是真正意义上自己花钱买的第一台 Apple 设备。自从进了 Apple 的坑以后,就愈发想凑齐 Apple 的不同设备,体验到更加完美的苹果生态圈。由于是花自己的钱买的设备,极其有限的预算让我把目光瞄向了最便宜的 iPad —— iPad 2019。

被不少人诟病的 iPad 2019

在确定购买哪一款 iPad 之前,我也经历过一段非常纠结的时光,在 iPad Air 3 和 iPad 2019 之间举棋不定。不少人觉得 iPad 2019 是一台「智商机」,但是我认为,对于预算确实非常有限的消费者来说,iPad 2019 在实际的使用体验上已经足够好了:足够大的 10.2 寸屏幕、对于学习来说足够能打的 A10 芯片、能够使用 Apple Pencil,种种因素都令我对手上的这台机器心满意足。使用 Apple Pencil 在 iPad 上写字的时候,其实并不会感觉到非全贴合屏带来的影响,虽然反光比较严重,但是调整角度后还是能够克服的。当然我还是觉得如果预算不是决定购买 iPad 的第一要素、或者不介意小屏幕的话,iPad Air 3 和 iPad mini 5 绝对是性价比更高的选择。

为了让 Apple Pencil 更加好用,我为它加上了防滑笔尖和防滑套:

Apple Pencil 改造

对于 iPad 的使用,我写了几篇文章分享一下自己的经验:

Zotero 跨设备文献管理

iWork - 如何充分利用 iCloud 和自带 App 实现 Apple 设备之间的互通

Numbers 表格——每个人都能做自己的统计学家

唯一需要注意的是...看视频真的太舒服了希望不要变成爱奇艺专属设备吧😂

当当阅读器

当当阅读器是我用自己的钱买的第一台设备,在拿到学校奖学金之后就用了一部分的钱给父母和自己买了点小礼物。作为一个喜爱阅读的人,我觉得选择合适的阅读媒介对于获得良好的阅读体验是非常重要的。事实证明,购买一个电子阅读器是一个非常划算的选择,对于小说、传记、科普等不需要进行深度阅读的书籍,电子阅读器能够更加真实地还原纸质书的感觉,并且克服了纸质书笨重的缺点,阅读体验也比手机更好。

至于为什么买的是当当阅读器而不是 Kindle,没错还是💰的问题(我太难了)。当当阅读器作为国产阅读器,较低的售价、丰富的功能、非常方便的书籍文件传输功能以及当当书城的支持,使得当当阅读器成为了我心目中的性价比之王。当当书城的书籍数量应该是目前国内唯一能够与亚马逊抗衡的吧,书籍的种类、价格都非常接地气。当然也能够自己在网上搜索书籍资源,再导入到阅读器中进行阅读。值得一提的是,可以尝试一下用电子阅读器看漫画,你会打开新世界的大门的。

阅读体验不输 Kindle 的当当阅读器

当当阅读器是在入手 iPad 之前买的,但是我觉得阅读器是能够与 iPad 共存的,不会说买了 iPad 以后阅读器就吃灰了。在 iPad 上我使用 iBooks 阅读一些在阅读器上打不开的 epub 文件,并且 iBooks 在标注和笔记上面还是比阅读器要方便一些的。关于 iBooks 的使用感兴趣的读者可以看一下我的文章:

iBooks - 如何充分利用 iCloud 和自带 App 实现 Apple 设备之间的互通

总的来说,对于原本就喜欢阅读的小伙伴来说,电子阅读器确实是一个非常实用的工具,能够减轻随身携带书籍的负担;但是对于原来就不怎么阅读,或者以为买了阅读器就能够倒逼自己多读书的朋友来说,电子阅读器是最好的压泡面神器。

小米 4

大一时候使用的古董手机,「为发烧而生」的小米在小米 4 上做的确实不错,很容易就热了。年少无知的我也不知道心疼手机,换了两次屏后就换成了 iPhone,没想到四年后找回来依然能用,GBA模拟器、PSP模拟器、微信完美运行,除了有点担心电池会不会爆炸之外几乎完美,当作备用机也是一个不错的选择。

真·钉子户

One More Thing...

最后许个愿望吧,希望自己以后能每年添一部新的设备hhh

Numbers 表格——每个人都能做自己的统计学家

17 February 2020 at 12:05

长文、多图预警。
知乎小透明一枚,居然有人翻牌让我深入写写 iWork 软件的使用教程🤣那么我就分三篇文章来写写 Pages、Numbers 和 Keynote 三款 Apple 平台办公软件的基础和进阶使用教程吧!

我为什么把目光投向了 Numbers

作为一名手持 Apple 全家桶的(伪)果粉,我认为 Apple 自家软件都有着不错的设计,抱着一种「能用自带的就避免下载第三方软件」的心态,我尝试着将 Apple 平台的自带软件充分利用起来。在 iPhone、iPad 和 Mac 上,Apple 已经为消费者提供了许多优秀的应用软件,使消费者在机器到手的时候就已经能够应付日常生活的各种任务了。

回到文档制作这一主题上来,Microsoft Office 凭借着强大的功能,已然成为了业界标准,以至于现在大家已经把「给我一份文稿 / 表格 / 演示文稿」的说法变成了「给我一份 Word / Excel / PPT」了,可见 Office 的巨大影响力。然而,Office 为用户提供了功能非常强大的办公软件,却似乎并没有得到最充分的利用——大多数普通用户对于办公软件的需求并没有那么高,就像某个说法「80% 的用户用着软件 20% 的功能」。这么一来,有时候 Office 的高级功能对于轻度用户来说就比较复杂了,掌握它们也是一个时间成本比较高的事情。

与微软相反,Apple 的 iWorks 办公套件更注重的是普通用户的使用体验,虽然在功能上可能不如 Microsoft Office 那么丰富和全面,但是对于日常使用和简单的进阶使用场景来说,我认为 iWorks 在使用上会比 Office 更加合理和人性化。本篇文章,我就和大家分享一下 iWorks 办公套件中的一员——「Numbers 表格」的一些使用心得。

关于 Numbers 的官方指南和帮助,可以在应用的菜单栏「帮助」->「Numbers 表格帮助」处获得。另外,在 Apple Teacher 的学习资源网站中,也有关于 Numbers 的官方教程电子书,大家可以自行前往下载。

Numbers 基础使用

本节介绍「Numbers 表格」的基础使用方法。

简单操作

打开 Numbers,应用会显示对话框,让用户选择希望用 Numbers 打开哪个文件。点击左下角的「新建文稿」,会显示 Numbers 的模板选取器,里面包含了 Numbers 自带的多种内置模板,包括空白表格、核对清单、个人预算等常用的个人表格,甚至还包括了食谱、成绩簿、相关性项目等不同应用场景下的进阶模板,用户可以根据自己的需要选择合适的模板,就能够快速创建一份适合自己的表格。

Numbers 内置模板

此外,用户还能够自行设计表格,创建自己的表格模板。在完成模板的设计后,点击菜单栏「文件」->「存储为模板...」可以选择将当前文件保存为 Numbers 模板文件或者直接添加到模板选取器中,方便下次直接使用。

自定义 Numbers 模板

Numbers 的主界面比较简洁。最大的一块区域是表格的编辑区域,在此处进行表格文本内容的输入和编辑;上方是标题栏和工具栏,包括了一些快速操作的按钮,在工具栏空白处右键单击可以选择工具栏图表的显示方式和自定义工具栏,可以将自己比较常用的功能添加到工具栏中,提高工作效率;左侧是检查器面板,在选择了表格的内容后才会显示可用选项,主要包括对表格样式、文本样式、单元格样式的修改以及对表格数据的处理等。

Numbers 主界面

在需要对表格整列或整行进行操作的时候,可以直接选中列号(ABCDEFG...)或行号(1234567...),将鼠标指针放在列号或行号上,会显示出下拉菜单,并显示可以进行的操作;也可以在整列或整行上单击鼠标右键打开该功能。每行和每列的后面有一个「圆形 + 等号」的按钮,点击可以直接添加新的一行或一列。

为了加快有规律性内容的输入,Numbers 提供了自动填充单元格的功能。选中某一个或某几个单元格后,选中区域的上下左右边框中间各有一个黄色的圆点,对圆点进行拖拽能够自动进行单元格的填充,填充的规律由 Numbers 自动确定,如果无法确定填充规律,Numbers 会对选中的内容进行复制。并且,可以自动填充的内容并不局限于数字,还可以是日期、字母等。

自动填充单元格

在录入了表格的内容后,选中需要修改样式的单元格,在右侧检查器面板中可以对选中的单元格进行修改,修改的内容包括表格边框、表格名称、单元格数据格式、单元格条件填充、文本样式等,具体的调整效果可以自己多试试,一个工作表中可以包含多个表格,同一工作表的多个表格通过其名称进行区分。

在完成了表格的编辑和样式修改后,可以将文件以 .numbers 的格式保存在自己喜欢的路径,也可以另存为其他格式。点击菜单栏「文件」->「另存为...」,可以看到支持导出的格式包括 PDF、Excel、CSV 以及旧版本的 Numbers 文件格式,其中 PDF、Excel 和 CSV 都能够在 Windows 平台下打开,但是 Excel 和 CSV 都极有可能出现格式乱掉的情况。如果仅仅是希望在 Windows 下查看文件的话,建议使用 PDF 格式;如果需要修改但对格式要求不高的话,也可以仅在 Numbers 中编辑内容而不对格式进行任何修改,这样用 Excel 打开后的格式问题可能会稍有缓解。

除了导出为 Excel 文件以外,Numbers 也能够打开 .xls、.xlsx 的文件,但是同样会存在格式不兼容的问题。

使用文本框、形状、多媒体、批注

在 Numbers 中,除了普通的表格以外,还能够在表格中添加各种不同的「对象」,以丰富表格的视觉效果,可以插入的「对象」包括独立文本框、形状、多媒体文件等。插入不同「对象」的按钮可以在工具栏中找到,并且插入的图形是矢量图,可以对其进行混合、交叉、减少和排除的矢量计算,从而创建出属于你自己的图形。

工具栏选择插入内容

需要注意的是,采用「插入对象」的方式为表格添加图片和音频的时候,默认来源为「照片.app」和「音乐.app」中的内容,如果需要插入硬盘上的文件,可以依次点选工具栏的「媒体」->「选取」按钮,就能够选择硬盘上任意位置的文件添加到当前表格中,当然需要是 Numbers 能够支持的文件格式,具体可用的格式大家可以自行尝试。

选取硬盘上的文件

得益于 Apple 生态的高度整合性,Numbers 支持「从 iPhone 或 iPad 导入」的功能,点击工具栏的「媒体」按钮,可以看到该选项,可以导入的内容包括「拍照」「扫描文稿」「添加速绘」,点击对应的选项后,在 iPhone 或 iPad 上进行操作就能够直接将 iPhone 或 iPad 上得到的内容直接导入到 Numbers 表格中,而无需重新寻找,这一功能对于全家桶用户来说是又一提高效率的利器。

借助内置模板和插入对象的功能,用户能够用 Numbers 制作出样式精美的表格,可以像做 PPT 一样做出一份非常好看的报表了。

iCloud 同步

对于 Apple 全家桶用户来说,Numbers 是 iOS 和 macOS 上都有的应用程序,可以通过 iCloud 实现不同设备下的数据同步。Numbers 会在「iCloud 云盘」中会创建一个专属的目录来作为默认的保存位置,不同设备之间的数据会进行实时同步,包括新建表格、删除表格、编辑表格等。如果因为网络中断等问题造成两份表格的内容不一致,Numbers 会提示用户选择一份保留下来的记录,然后重新将所有设备上的内容变成相同的。

要想开启 Numbers 的 iCloud 同步功能,iOS 端在「设置」->「Apple ID」->「iCloud」中勾选「iCloud 云盘」和「Numbers 表格」即可,macOS 端在「系统偏好设置」「Apple ID」「iCloud」中勾选「iCloud 云盘」,并在 iCloud 云盘的选项中勾选「Numbers 表格」即可。有关 iWork 和 iCloud 的使用,可以参考我的其他文章

共享与多人协作

Numbers 内置共享和多人协作功能。点击菜单栏的「共享」->「发送副本」可以将当前表格文件的副本以邮件、AirDrop 等方式发送出去。点击菜单栏的「共享」->「与其他人协作...」或点击工具栏的「协作按钮」可以将邀请其他用户共同参与当前表格的编辑,发送者可以设置文件的访问权限,以防被不具有修改权限的访问者意外修改了文件内容。这一功能在其他的 Apple 自带软件中都有,在文件协作者也是使用 Apple 设备的情况下可以非常方便地进行多人协作。

多人协作

Numbers 进阶使用

本节介绍「Numbers 表格」的一些进阶使用方法。

公式

有时候我们还需要根据已有的表格数据,按照某条公式进行计算,从而进一步挖掘数据中的信息。Numbers 提供了一定数量的内置公式供用户使用,可以点击工具栏中的「插入」新建公式或选择一些常用的公式。如果在只选中了一个单元格的情况下添加了内置的公式,Numbers 会自动选择它认为正确的单元格来进行计算。

插入公式

比较有意思的是,Numbers 还提供了一个插入「股票报价」的功能,点击工具栏的「插入」->「股票报价」,输入上市公司的名称或代码就可以在当前单元格中插入该公司的股票报价。有了这个功能后,用户甚至能够用 Numbers 打造一个股价监视器😂由于我并不了解股价市场,这个功能没有用过,有需要的朋友可以尝试一下好不好用。

「股票报价」功能

在内置在空白单元格中输入「=」同样会呼出公式编辑的界面,右侧的检查器栏会显示出 Numbers 自带的函数列表,并且已经进行了一定程度的分类,包括三角函数、工程函数、统计函数、财务函数等。选中某条函数之后,在其下方会显示出该函数的介绍、输入参数、注释、示例等内容,能够帮助用户快速了解一个函数的使用方法。在检查器栏的上方还能够对内置的公式进行搜索。

公式检查器

在使用公式的时候,公式的输入可以看作是对表格数据的「引用」,而由于一个 Numbers 表格中可以包含多个工作表(参考 Excel 中「工作簿」和「工作表」的概念),根据数据存在位置的不同,可以分为「表内引用」与「表间引用」。「表内引用」是指公式需要一个存在与当前工作表的数据作为输入,而「表间引用」是指公式需要一个存在与另一个工作表中的数据作为输入。

下面通过一个简单的例子来说表内引用和表间引用的使用方法。为方便起见,示例表格文件中仅包含「苹果」和「微软」两个工作表,它们的内容是完全相同的。

一个🌰

Numbers 采用符号「::」来区分不同工作表的引用范围,例如假设当前工作表为「苹果」,则「微软::表格 1::工资 Michael」表示「“微软”工作表中名称为“表格 1”的表格中,“工资”列“Michael”行的数值」;对于当前工作表的数据,则不需要第一个「::」前面的工作表范围,即「工资 Michael」表示「“苹果”工作表中“工资”列“Michael”行的数值」(当然如果当前工作表下有多个表格的话还是需要区分开的)。通过简单的点击,Numbers 就能够自动使用正确的表内引用和表间引用格式。在公式需要输入数据的地方,点击当前工作表的数据就是表内引用,点击其他工作表的数据就是表间引用。下面用一个动图来显示一下具体的操作

表内引用和表间引用

关于 Numbers 公式的更多帮助,可以点击菜单栏的「帮助」->「公式与函数帮助」查看更加详尽的官方文档

数据整理与分析

除了使表格看上去更加赏心悦目以外,表格的一个重要功能就是对数据进行整理。下面介绍一下 Numbers 的数据整理方法。

在选定了有内容的单元格后,Numbers 会自动对选中内容进行一个简单的统计,并显示在软件下方,默认显示的内容是。可以点击右方的齿轮更改成自己最常用的统计功能。

对单元格内容进行自动统计

在选定了需要进行整理的单元格后,在 Numbers 的右侧检查器面板中可以选择「整理」,即可对选中的单元格内容进行整理,可用的操作包括分类、排序和过滤。

  1. 分类:Numbers 会将每一列的表头看作为一个「类别」。点击「添加类别」的下拉菜单,可以将表格按照各个类别的值进行分类显示。可以选择多个类别对表格内容进行分类显示。其中,「隐藏列」可以使该类别的数据在表格中隐藏起来(因为已经按照这个类别进行分类了,同一个分类下的值时相同的,略显多余)。一个例子如下图:

    分类前
    分类后

    例如,我想要查看表格中的男女员工在不同部门中的分布情况,则可以依次选择「性别」和「部门」两个分类,就能够看到男员工的部门分布和女员工的部门分布。点击右侧的垃圾桶图标可以删除该分类显示,拖拽左侧的横线可以对分类显示的先后顺序进行排序,表格中的显示会即时进行更新。

    利用分类功能,可以快速地将表格数据按照一定的分类标准分离开,从而更加直观地获知数据的分布情况。

  2. 排序:Numbers 能够根据给定列的值,对整个表格或选定的行进行排序。在「整理」检查器的「排序」页中,可以通过下拉菜单选择「对整个表格排序」和「对所选行排序」。通过「添加列...」下拉菜单可以添加需要排序的列。一个例子如下图:

    排序前
    排序后

    例如,我想要在不同部门的分类基础上,将员工的工资按照降序排列,则可以在按「部门」分类的基础上,添加「工资」列进行排序,并选择「降序」。和分类一样,可以选择多个列对表格内容进行排序,点击右侧的垃圾桶图标可以删除该排序显示,拖拽左侧的横线可以对排序显示的先后顺序进行排序,表格中的显示会即时进行更新。
    利用排序功能,可以快速将表格数据按照一定的规律进行重新排列。

  3. 过滤:Numbers 能够根据给定的条件,筛选出符合条件的数据。借助条件表达式的概念,可以选择「匹配所有过滤条件」和「匹配任意过滤条件」,这两个分别是「与」和「或」的逻辑关系。一个例子如下图:

    过滤前
    过滤后

    例如,我想要在不同部门的分类基础上,查找等级为 A 的员工,则可以在按「部门」分类的基础上,选择「员工等级」「是」「A」的过滤条件。过滤功能同样可以选择多个过滤条件对表格内容进行过滤,点击右侧的垃圾桶图标可以删除该过滤条件,拖拽左侧的横线可以对过滤条件的先后顺序进行排序,表格中的显示会即时进行更新。除此之外,点击「添加规则」还可以对已有的过滤条件追加新的规则,以实现更加复杂的逻辑关系。

    过滤与分类在功能上有一定的重叠之处——分类是「将原数据按照一定的类别进行重新排列」,而过滤则是「仅显示满足某些条件的数据」。

除了对表格数据进行分类、排序和过滤之外,Numbers 还提供了多种图标绘制的选项。点击工具栏的图标,即可选择插入不同类型的图表。图表类型包括「二维」、「三维」和「交互式」三种,前两种图表又可继续划分为柱状图、饼图、折线图等不同类型的图表,「交互式」则可以对图表进行操作,并对选中的数据进行动态的显示。

插入图表

创建图表后,点击「添加图表数据」即可选择图表的数据来源,在左下角可以选择「根据列绘制序列」或「根据行绘制序列」,前者以各行的值作为横坐标、各列的值作为纵坐标,后者以各列的值作为横坐标、各行的值作为纵坐标,在使用的时候注意区分。选中图表后,右侧检查器栏会显示对当前选中图表的样式设置:「图表」页包含对图表本身的设置,例如图表选项(标题、图例、边框等)、图表字体、图表颜色等;「坐标轴」页包含对柱状图 X 轴和 Y 轴的标签、刻度线、标度等内容的设置;「扇区」页包含对饼图每一块分区的标签、位置等内容的设置;「序列」页包含对图表一维数据的设置,还可以添加趋势线和误差线等。检查器栏的页签会根据图表类型的不同而发生相应的变化,具体的设置可以自行尝试。

二维和三维图表是平时我们最常见的图表,能够对某一类数据的进行可视化。例如,我想利用饼图对 Marketing 部门的工资分布情况进行可视化,则可以先对表格按「部门」进行分类,然后插入一个二维饼图,选择「根据行绘制序列」,并选择「Marketing」分类下的员工工资作为饼图数据即可。

二维饼图示例

交互式图表允许使用者对图表进行操作,查看在一定范围或一定时间段内,某类数据的动态变化情况。交互式图表的数据添加方式和二维、三维图表相同。由于我没有使用过这个表格,仅仅是试着看了一下效果,因此没有办法给出使用教程,有兴趣的可以参阅「Numbers 表格帮助」的内容。

交互式图表

可以看到,Numbers 提供了简单易用的数据分析和可视化工具,帮助我们对手头上的数据进行分析和处理,对于一般场合足够使用,对于难度比较大的数据分析任务可能需要在熟悉 Numbers 使用的基础上,花点脑筋想想如何达到自己的目的了。

其他

Numbers 支持以文本方式插入并显示 LaTeX 公式。点击菜单栏「插入」->「方程」,或使用快捷键 command + option + E 即可唤出 LaTeX 编辑器界面。在编辑框内输入 LaTeX 或 MathML 公式,点击插入即可。在方程编辑区域下方有一个「方程预览」区域,可以预览 LaTeX 代码的运行结果,检查 LaTeX 代码是否有误。

LaTeX 公式编辑

Numbers 还提供了一个类似「Time Machine」的版本复原功能,它会定期对文件的快照进行备份,供用户回到文件过去的状态。依次点击菜单栏「文件」->「复原到」->「浏览所有版本...」,可以看到当前的表格文件以类似于 Time machine 的方式呈现出来,点击上下箭头或右方的时间轴可以选择回退的时间。这一功能相当于给了用户一颗“后悔药”,即使在改错内容的情况下,也能够还原到出错之前的版本。

Numbers 版本复原功能

总结

Numbers 作为 Apple 自家的办公软件,能够满足日常生活的绝大多数场景,甚至在稍微专业一点的领域都能够游刃有余地完成数据处理与分析的任务。当然,与 Excel 这种几乎已经成为业界标准的软件比较的话,Numbers 还有很长的路要走。然而,我还是十分推荐普通的用户可以尝试一下 Numbers,因为它给了每一个人「做自己的统计学家」的机会,其内置的丰富功能能够帮助我们对自己的数据有一个更加清晰和直观的认识。

iBooks - 如何充分利用 iCloud 和自带 App 实现 Apple 设备之间的互通

28 January 2020 at 16:55

本篇是专题「如何充分利用 iCloud 和自带 App 实现 Apple 设备之间的互通」的第三篇文章,主要介绍如何在 Apple 平台上使用 iBooks 对电子书籍进行不失深度的阅读,同时与我拥有的另一个电子设备——「当当阅读器」进行对比。

Why iBooks?

作为一个喜爱装逼热爱阅读的研究生,除了阅读专业书籍和文献资料以外,我还喜欢在闲暇时间阅读一些自己喜欢的书籍。在拥有各种各样的电子设备之前,纸质版的书籍自然是不二选择。一方面,传统纸媒能够为读者带来更好的阅读体验,不容易对我们心灵的窗户产生伤害,手指划过纸张的感觉也能够加深读者对书籍的印象;另一方面,传统纸媒也承载着更多的情感,能够方便地在书上记录自己当时的感受,再次翻阅书籍的时候能够回忆起自己当初的心境。然而,传统的纸质书籍也有着难以克服的缺点:占据较多的空间,不便于保存;重量不轻,便携性较差;难以快速检索书籍上的笔记内容。当然,纸质书籍仍具有它的存在价值,电子阅读和传统阅读终究无法相互取代。

进入大学后,我陆续入手了当当阅读器和 iPad,电子阅读也逐渐进入到我的日常生活中,这两个设备分别代表了目前电子阅读的两个趋势:电子墨水屏的阅读器力求还原纸质阅读的感受,在功能上专注于阅读本身,抛弃了其他多余的功能,以期为读者带来更加真实的阅读体验;基于 App 的数字阅读则在便携性上更进一步,随时随地掏出手机就能开始阅读,充分利用了日常生活中的碎片时间。对于这两种电子阅读的方式,我认为它们并不冲突,它们各自有发挥特长的空间。(小孩子才做选择,大人我全都要🐶)

本篇文章关注的重点是基于 App 的数字阅读。由于我手头上的设备是 iPhone、iPad 和 MacBook,因此在这三个平台上我选择的是 Apple 自带的 iBooks,在 iOS 13 / iPadOS / macOS Catalina 上更名为「图书」,但我更喜欢称之为 iBooks。在我看来,iBooks 具有以下三个吸引我的特性:

  1. Apple 自带软件,质量有一定的保证,并且对于存储空间较小的设备来说,能够避免安装第三方软件,降低焦虑感。

  2. 原生支持 iCloud,对于拥有多台 Apple 设备的用户来说体验较好。

  3. 支持 PDF 和 EPUB 两种目前比较主流的电子书格式,已经能够满足我的个人需求了。(在移动端和桌面端对 PDF 和 EPUB 的支持情况存在一定的差别)

iBooks 多设备同步的准备工作

作为 Apple 自家的软件,iBooks 对于 iCloud 的支持还是比较完善的。为了能够利用 iCloud 和 iBooks 打造个人书库,需要进行一些准备工作。

设置 macOS 端

在 macOS 上,依次点选「系统偏好设置」->「Apple ID」->「iCloud 云盘」->「选项」,然后勾选「图书」前面的复选框即可启用 iCloud 对 iBooks 的支持。

另外,还需要进入 iBooks 应用,依次点选「商店」->「登录」,并登录 iCloud 账号,按照提示输入账号密码后,再一次点选「商店」->「授权」->「对这台电脑授权...」,就完成了 macOS 端 iBooks 的基本设置。登录 iCloud 账号后可以通过 iCloud 同步书库内容以及书籍的阅读进度。

为了能够在不同设备之间同步书库内容,需要在 iBooks 的「偏好设置」->「通用」选项卡中勾选「在设备间同步精选集、书签和重点」。

设置 iOS / iPadOS 端

iOS / iPadOS 端对 iBooks 的设置是类似的,首先在「设置」->「Apple ID」->「iCloud」中开启「iCloud 云盘」以及「图书」两个选项,然后在「设置」->「图书」中开启「阅读中」和「iCloud 云盘」两个选项,就完成了移动端 iBooks 的设置。

使用 iBooks 进行阅读

在完成了 iBooks 的初步设置后,就能够愉快地利用 iPhone、iPad 或者 MacBook 来进行书籍的阅读。下面的讨论基于 iPad 和 MacBook 两个设备上的 iBooks 应用。

书籍管理

由于 iBooks 商店在国区不可用,电子书籍的添加无法在移动端进行,需要在桌面端手动进行。将下载好的 PDF 或 EPUB 文件直接拖进 macOS 端的 iBooks 应用中就能够将书籍添加到书库中,书籍文件会被复制到 iCloud 云盘中 iBooks 的对应位置,查看方法为:依次点选「系统偏好设置」->「Apple ID」->「管理...」->选择「Apple Books」->「在“访达”中显示」,就能够在 Finder 中看到 iBooks 存放文件的目录。

需要注意的是,在 Finder 的「iCloud 云盘」中没有办法直接看到这个目录,必须通过上述方法才能够进入该目录。书籍文件名旁边的小云朵表示该文件只保存在云端而没有保存在本地,通过右键单击书籍文件,选择「现在下载」可以将云端文件下载到本地,或者选择「移除下载项」将本地文件删除而仅保留在云端。在 macOS 端的 iBooks 应用中打开书籍,会自动将云端的书籍文件下载到本地。至于移动端,暂时没有办法手动释放占用的空间。

iBooks 以「书库」和「精选集」的形式来对书籍进行管理,「书库」中的书籍就是你拥有的所有书籍。「精选集」则是自己对分类,一本书籍可以被归类到不同的「精选集」中。这种分类方式类似于 Apple Music 中「资料库」和「播放列表」的关系。在「书库」中,iBooks 还会自动将书籍按照「图书」、「有声书」、「PDF」、「作者」、「类别」等几种不同的方式进行一个简单的分类。

iOS / iPadOS 端的 iBooks 应用界面经过了全新的设计,与其他 Apple 自带的 App 设计风格相近,可以方便地实现书籍的排序、切换视图、编辑等操作。

在桌面端和移动端的 iBooks 中都能够直接看到书籍是被保存在云端还是保存在本地,有一朵云的书籍就是仅保存在云端上的,没有的就是保存在本地。

书籍阅读

iBooks 能够识别到的书籍文件格式包括 .pdf、.epub 和 .ibooks,其中 .ibooks 是 Apple 自己设计的书籍文件格式,可以包含除了文本和图片以外的多媒体内容,例如视频、音频等,Apple 自家的教程绝大多数都是这个格式的。.ibooks 格式的电子书非常适合用来制作教学材料,能够让读者从多方面对要学习的内容有更加深刻的印象,例如 Apple Teacher 中的学习资料都是 .ibooks 格式的文件,在教程中包含大量文本以外的信息,使得学习的过程更加生动有趣。.pdf 和 .epub 则是目前比较主流的电子书格式,在 macOS 上默认使用「预览.app」打开 PDF 文件,使用内置的阅读器打开 EPUB 文件,而在移动端则都是用内置的阅读器打开 PDF 和 EPUB 文件。

在 iBooks 中打开书籍,就进入到书籍的阅读界面。移动端和桌面端的 iBooks 阅读界面都非常的清爽、简洁,最大限度地保留了书籍的内容,其他的功能则是缩到角落的位置,在需要的时候可以使用。常规的设置包括调整背景色、改变字体字号、调整亮度等。

macOS 端点击「显示」可以选择单页或双页阅读。

iPad 上可以选择滚动显示或翻页显示,如果是翻页显示的话,iPad 竖屏时为单页阅读,横屏时为双页阅读。

iBooks 的阅读器与其他大多数的阅读软件差别并不大(毕竟这是一个阅读器的基本素质),在较小尺寸的 iPhone 上进行阅读可能会觉得屏幕有点小,适当增大字号会看的没那么累;在 iPad 和 MacBook 就非常舒服了,「大屏幕 + 良好的屏幕素质」使读书这件事在 iPad 和 MacBook 上有着非常好的体验。

阅读记录同步

有时候,我们在电脑上阅读一本书籍,但是希望在通勤的路上能够用手机或平板继续阅读。iBooks 能够自动同步不同设备对同一本书籍的阅读进度,让用户的阅读体验更加一直、连贯。这一功能的启用不需要其他的设置,在完成了前面的基本设置后就能够享受到 iBooks 自动同步阅读进度的便利了。这一点相比于其他的阅读器来说不失为一个亮点,就我所知,第三方阅读器在跨设备同步阅读进度上的表现远没有 iBooks 好,大部分只能通过添加书签的方式来间接同步阅读进度。

标注系统

俗话说「好记性不如烂笔头」,在阅读的时候往往需要对书上有感而发的内容进行标注,在下次翻开书籍的时候能够提醒自己。macOS 端的 iBooks 能够对 .epub 和 .ibooks 格式的文件进行比较丰富的标注,而 PDF 则可以利用「预览」打开进行标注。对于 .epub 和 .ibooks,在 macOS 端选中文本就会弹出标注框,能够添加 5 种颜色的标记和下划线标记。除了标记以外,还能够对标记的内容添加笔记,方便记录自己的想法。

在 iPadOS 端,用手指长按选中文本,选择「重点」就能够对文本内容进行标记,基本操作与 macOS 端的相同。如果是使用 Apple Pencil 的话,直接用笔在文本上面拖移就能够进行标注,后续可以修改标注的样式,非常方便。

在标注系统方面,iBooks 为我们提供了多种标注样式,能够对书籍进行丰富的标注,并且能够。在本文后续的讨论中会进一步介绍 iBooks 的笔记系统。值得注意的是,标注的目的是帮助我们对书的内容进行更加清晰的分类,因此最好有一套属于自己的标注系统,例如橙色表示「定义」、绿色表示「做法」、红色表示「重点」等,这样标注后的内容才不会显得很乱。

笔记与学习

在对书本内容进行标注后,可以对标注的内容添加笔记,在移动端和桌面端都能够进行添加笔记的操作。

在 iPad 上,选择标记好的内容,弹出框的第三个、像一个便签纸的图标就是添加和编辑笔记的按钮,添加完的笔记可以在「查看目录」中选择相应的选项卡进行查看。

在 iBooks 中点击放大镜能够对书本内容和笔记内容进行检索,输入关键字就能够查找到与之相关的内容了。笔记的内容会通过 iCloud 自动同步到不同的设备上,这进一步提升了不同设备之间的协作性。

在笔记功能的基础上,macOS 端的 iBooks 还提供了一个非常实用的学习功能——学习卡。在笔记面板中,点击左上方的「学习」就可以对笔记内容进行学习。学习卡的正面是在书本中标记的内容或者 .ibooks 中提供的「词汇表词汇」,背面是对标记内容的笔记或者 .ibooks 中提供的对「词汇表词汇」的解释。

在学习卡界面的右上方可以设置「打乱学习卡」和「筛选学习卡」,可以选择不同是否显示「词汇表词汇」和显示哪些标注样式的内容。结合前面介绍的不同标注样式,在对不同的标注样式有自己的一套定义之后,就能够针对自己的实际情况——是对「定义」不熟悉还是「怎么做」不熟悉——通过学习卡筛选出自己薄弱的部分,并有针对性地进行二次复习,

通过「学习」功能,我们能够对书本内容进行更进一步的学习,以学习卡的形式来检验自己对书籍内容的掌握程度。比较遗憾的是,学习功能仅适用于 macOS 端的 iBooks,iOS / iPadOS 的 iBooks 暂时没有这个功能,希望 Apple 能够在后续版本中提供对学习卡功能的支持吧。

iOS 13 新引入的功能——阅读目标

在 iOS 13 / iPadOS 中,iBooks 添加了「阅读目标」的功能,可以设置每天的阅读时间,督促自己进行阅读。要启用这个功能,首先需要在「设置」->「图书」中开启「阅读目标」;如果希望阅读 PDF 的时间也计入其中的话,还需要开启下面的「包括 PDF」。

设置完成后,iBooks 就能够帮助我们记录阅读时长。可以在 iBooks 中调整每天的阅读时长,也可以分享自己的每天的阅读进度。阅读目标在多个设备上是累积统计的,即总的阅读时长等于在各个设备上的阅读时长的总和。坚持记录自己的阅读时间,在下一次年度总结的时候也可以用来衡量一下自己年初定下的目标是不是实现了呢。

与其他阅读方式的对比

如今,纸质书和电子阅读二分天下,分别有各自的拥护者。下面我对几种目前常见的阅读方式进行一个简单的对比,仅代表我自己的主观感受。

「iBooks」 V.S. 「纸质书」

电子阅读和纸质阅读的对比是一个永远也跨不过去的坎,纸质书作为一个存在了上千年的实体,在电子书出现之前一直都是。时至今日,纸质书依然受到许多人的欢迎。在我看来,纸质书和电子书是两个相辅相成的阅读方式,不可能互相取代,它们有各自的应用场景。

回到 iBooks 和纸质书的对比上,我认为两者各自的优势分别是:

  • iBooks 优势

    • 能够快速定位笔记和标注,更加强大的搜索功能,管理更加方便;
    • 成本相对纸质书来说要低,更方便利用碎片时间进行阅读。
  • 纸质书优势:

    • 更有质感,视觉效果最好,不容易伤眼;
    • 专注阅读时的干扰较少;
    • 具有收藏意义。

可以看到,iBooks 代表的电子阅读更适合在移动场景中进行阅读,借助于优秀的软件,也能够对书籍进行深度阅读;而纸质书由于需要占据一定的空间,不方便携带,更适合在固定的场所进行阅读。除此之外,两者的区别并不大。

「iBooks」 V.S. 「当当阅读器」

我手头上的电子墨水阅读器是「当当阅读器」。之所以选择了这一款阅读器而不是大名鼎鼎的 Kindle,是因为:

  1. 当当阅读器的价格更加合理,700 出头的价格对标 Kindle Paperwhite;

  2. 书籍管理更方便,支持多种电子书格式,用数据线就能够管理书籍,不像 Kindle 的传书过程比较麻烦;

  3. 支持国产!

iBooks 和当当阅读器的对比本质上是「基于 App 的数字阅读」和「电子墨水屏阅读」之间的对比,我认为两者各自的优势分别是:

  • iBooks 优势

    • 处理速度更快,翻页不用等待屏幕刷新;
    • 跨平台/设备同步,在手机、平板或者电脑上安装对应的 App 就能阅读,不需要单独的硬件设备;
    • 做笔记更加方便,电子墨水阅读器记笔记简直就是灾难。
  • 电子墨水阅读器优势:

    • 墨水屏不容易伤眼,阅读过程中产生的干扰较少;
    • 尺寸小巧玲珑,便于携带;
    • 整合书城的图书资源,如当当和亚马逊,搜索图书资源非常快捷方便;
    • 漫画神器。

就我自己来说,其实这三种阅读方式都占据着我生活的一个重要的部分(不然我也不会三个都有了),纸质书是一种收藏和精致阅读的象征,iBooks 代表着便利的跨设备阅读,而电子墨水阅读器基本上是纸质书的替代品,看小说和漫画的时候是不二选择。

结束语

早期的 iBooks 确实比较难用,基本上只能当作一个图书管理软件来使用,而第三方的图书管理软件如 Calibre 等做得比 iBooks 好太多。但随着版本的不断更替,iBooks 逐渐成长为一款能够担起一定阅读任务的阅读器,得益于 macOS 平台的优势,对于 PDF 和 EPUB 两种格式的文件都能够有比较好的支持,在标注系统上已经能够比肩专业的阅读器,独具特色的「学习」功能使 iBooks 具备了深度阅读的能力,能够对书本知识进行二次学习,从而形成一个正反馈的过程,这与 MarginNote 的思路是非常相近的。但是 iBooks 也存在需要改进的地方,包括支持的电子书格式较少、移动端书籍文件管理不太方便等。

总的来说,借助于 iCloud 服务,我认为普通用户完全能够使用 iBooks 来对书籍资料进行一个较为深入的阅读、消化,实现对自己的升华。

或许,你可以尝试着用 iBooks 开始阅读一本有深度的书籍了?


本文为原创文章,转载请注明。

Zotero 跨设备文献管理

15 January 2020 at 19:43

听说 iPad 除了用来盖泡面和吃灰,还能够用来看文献呢。

介绍

作为一名有着大量文献阅读需求的工科研究生,我通常是采用「打印 + 电脑」的方式对文献资料进行学习,这里的文献除了期刊论文、学位论文等文章以外,还包括了专业书籍等「大部头」的内容。对于电子版的专业书籍,我通常是在 MarginNote 这款软件上进行学习的,这是一款非常棒的阅读软件,集合了 PDF 阅读器、思维导图、复习卡等多种不同的学习功能,能够帮助我对书籍的知识按照自己的方法重新进行梳理、消化,全方面地对书本知识进行掌握,真正变成自己学到的知识(没有收广告费的呀)。而对于期刊论文,我通常是打印成纸质版,用普通的中性笔和荧光笔进行标注,传统的纸媒让我能够更快地定位到自己感兴趣的部分,从而判断是否需要对这篇文章精读;传统的标注工具从小学就开始使用了,完全不存在使用门槛的问题。

在去年年末购入了 iPad 2019 和 Apple Pencil 后,我尝试着将文献阅读的任务转移到 iPad 上进行,一方面,「iPad + Apple Pencil」的组合能够最大化模拟传统纸笔,在阅读文献的时候能够有更好的体验;另一方面,就我个人来说,在电脑上进行文献阅读还是比较容易受到干扰的,总会一不小心就打开视频、游戏什么的,导致工作流程中断。为了能够充分发挥 iPad 和 Mac 各自的优势,我希望最后达到的效果是在 iPad 上进行文献的阅读、标注,在 Mac 上进行文献的收集和管理,两台设备各司其职,又能够进行同步。

然而,随着文献数量的不断增多,如何对大量的文献进行有效的管理就成了一个急需解决的问题。在学习和研究的过程中,往往需要涉猎许多不同主题、不同方向的文献,如果只是简单地用「文件夹 + 重命名」的方式进行管理,需要花费不小的精力。经过一段时间的摸索后,我探索出了使用「文献管理软件 + 云盘」方式实现我需要的工作流。

本文的讨论主要针对期刊论文等文献的管理,对于专业书籍,我更倾向于在 MarginNote 中进行学习和管理,有兴趣的读者可以看一看我的 MarginNote 使用心得。

Zotero——开源免费的文献管理软件

文献管理软件是一种专门用于文献的整理、使用的软件,能够对文献进行分类管理、导出引用等操作。常见的文献管理软件包括 Zotero、Mendeley、EndNote 等,这三款软件都是跨平台的软件,在 Windows 和 macOS 上都有对应的版本可以安装。在根据下面的表格对比了这三款软件后,我最终选择了 Zotero 这款免费开源的文献管理软件。

Zotero Mendeley EndNote
价格 免费 免费 收费
Catalina 兼容性 兼容 兼容 不兼容
是否需要注册账号 无需注册即可使用 必须注册才能使用 无需注册也能使用
UI 设计 一般 一般 较好
云同步 能够使用第三方云盘进行同步 自带云同步,但速度比较慢 注册后可以使用自带的云同步
引用功能 自带的比较弱,但是有丰富的插件 比较强大,能识别的文献较多 非常强大,几乎成为业界标准

软件特色

Zotero 是一款免费开源的全平台文献管理软件,提供 Windows、macOS、Linux 三大操作系统的客户端下载,并且兼容 macOS Catalina。比较可惜的是 Zotero 没有官方的移动端应用,也没有内置 PDF 阅读器,但是换来的是更加轻便和自由的体验。

作为一款开源软件,Zotero 的 UI 设计只能说是中规中矩吧,通过图标来表示不同的功能选项,简单明了。

安装与使用方法

软件安装非常简单,直接在 Zotero 官网下载对应版本的安装包即可。Mac 端下载的是常见的 DMG 文件,双击加载后将应用拖拽到「应用程序」目录下即可。

文献收集与管理

将下载到的 PDF 文件直接拖拽到应用中就能够在,Zotero 会将 PDF 文件复制到默认文档路径中,在 macOS 上的默认路径是 ~/Zotero,可以点击左上方的按钮或者在侧边栏中点击鼠标右键,新建分类或新建文库对文献做进一步的整理。

对于期刊论文等文献,如果是在期刊的官网或者 Google 学术中下载的,大部分是会带有元数据的,这些元数据包括期刊名称、作者、发表时间等。文献的元数据是导出引用格式的基础,Zotero 通过元数据的信息来生成对该文献的应用。对于一些无法读取到元数据的文件,可以在 Zotero 中「右键单击文件」->「创建父条目」,并在下方的信息栏中输入相应的信息。

值得注意的是,分类并不对应硬盘中的实际目录。一份文献可以同时存在于多个不同分类中,但是它们都是对应于同一个 PDF 文件的。对于已经存在于某个分类中的文献,如果将它拖拽到另一个分类中,就会在新的分类下把这个文献添加进去。也就是说,Zotero 的分类系统并不是单纯的树形关系,更像是在给文献打标签,一篇文献可以有多个标签,即一份文献可以存在于多个分类中。

创建文献引用

在引用参考文献的时候,需要根据一定的格式对参考文献进行引用标注。手动一个一个输入的效率非常低,并且极易出错,而 Zotero 能够帮助你创建符合规范的引用格式。右键单击想要创建引用的文献,选择「由所选条目创建引文目录」,就会弹出对话框:

Zotero 内置了不少的引用样式,可以点击「管理样式...」添加新的样式,并且支持自定义样式,通过编辑 XML 文件模板就能够创建自己想要的样式了。选择好想要用的样式后,可以选择将样式导出为 RTF、HTML 或复制到剪贴板。经实测,Zotero 自带的样式管理功能还是比较弱,但是相信在开源社区有功能更加丰富的插件可以选择。

当然,Google 学术也提供了类似的功能。

与 Word 的整合

如果使用 Word 或者 LibreOffice 来完成文章的撰写,Zotero 提供了对应的插件,可以直接将生成的引用插入到文档中。在「首选项」->「引用」->「文字处理软件」中就可以安装 Word 或者 LibreOffice 的插件了。由于我没有使用过这个功能,有需要的读者可以自行查找相关的资料。

存在的不足

作为一款免费软件,Zotero 具备了比较强大的文献管理功能。得益于开源社区的贡献,Zotero 有着丰富的插件,可以满足不同的需求。

但是在使用 Zotero 的过程中,我发现还是有两个比较影响使用的问题的:

  1. 部分 PDF 文件无法导入元数据,大部分原因是 PDF 自身没有元数据,PDF 以外的格式的文件通常也是无法被 Zotero 识别到的。
  2. 无法通过文献所在的文件夹来区分文献的类别。Zotero 储存 PDF 文件的路径是 ~/Zotero/storage,在该目录下的文件夹名称都是有数字和字母组成的字符串,而并不是我们在 Zotero 中创建的分类的名字,因此想要在这个目录下面找到自己想要的文献几乎是不可能的。

第一个问题能够通过手动添加文献信息勉强解决,但是第二个问题,对于想要在 iPad 上阅读文献的我来说,着实是一个不好解决的问题。好在,我发现了一个强大的插件——ZotFile。

ZotFile——强大的 Zotero 文件管理插件

功能与特点

ZotFile 是一个 Zotero 的插件,能够实现文件夹/文件重命名、将文件发送到另一个位置等功能。它解决了上面提到的 Zotero 文件夹名称无法辨识的问题,使得我们的文件能够按照我们的分类进行重命名,并发送到另外一个位置上。这样,在 iPad 中就可以通过新的位置来读取到文献的 PDF 文件,在标注完成后,可以通过 ZotFile 取回标注后的 PDF 文件,覆盖电脑上的文件,从而实现桌面端和移动端的联动。

安装与设置

ZotFile 官网下载插件后,打开 Zotero,在「工具」->「插件」中,点击右上角的齿轮,选择「Install Add-on From File...」,选择下载好的插件压缩包,即可完成 ZotFile 的安装。

安装完成后,在「工具」中可以看到多了一个「ZotFile Preferences...」的选项,点击可以对 ZotFile 进行设置,总共有四个选项卡:

  • General Settings:ZotFile 可以在一个指定的目录内容发生变化的时候,将新的文件自动导入到 Zotero 中。可以专门设置一个文件夹作为中转站,将要导入的文献放进去,ZotFile 就会自动将文献导入到 Zotero 中。
  • Tablet Settings:ZotFile 可以将文献发送到一个指定的目录中,同时还可以将文献取回来。我们需要勾选「Use ZotFile to send and get files from tablet」,并在「Base Folder」处选择一个文件夹,这个文件夹就是 ZotFile 发送文献的目标位置。为了让这个位置能够被 iPad 读取到,我将它设置成了 iCloud 中的一个目录。最后需要选择的选项是「Create subfolders from zotero collections」,在发送到目标位置后根据文献在 Zotero 分类中的位置创建子文件夹,方便根据自己在 Zotero 中的分类情况来管理文献;「Rename files when they are sent to the tablet」,对原 PDF 进行重命名;「Automatically extract annotations when getting PDFs back from tablet」,自动提取在 iPad 上对文献的标注内容。
  • Renaming Rules:设置 ZotFile 在发送至目标位置时对原 PDF 文件进行重命名的规则。在网上下载到的 PDF 文件名往往凌乱不堪(尤其是从 arXiv 上下载到的文献),如果有需要的话,可以通过 ZotFile 对文件名进行自动重命名。ZotFile 可以针对专利和专利以外的文件区分不同的命名方式,具体的占位符格式需要参考 ZotFile 的文档。这里我设置的是「文献题目 - 作者」的格式,这样比较方便在同一分类下快速找到自己想要看的文献。
  • Advanced Settings:高级设置,普通使用下一般无需理会。

实现 iPad 与 Mac 之间的联动

下面简单介绍一下我是如何利用「Zotero + ZotFile + iCloud」的组合来实现文献阅读任务在 iPad 与 Mac 之间的联动的。

桌面端进行文献保存和管理

桌面端负责的是文献收集、保存与管理,在网络上搜索并下载文献后,拖拽到 Zotero 中保存,并创建自定义分类进行分类管理。我自己的习惯是按照文献的主题进行分类,具体方向再进行二次分类,另外在 arXiv 上看到但是没有正式发表的文献单独创建一个新的分类。在文献的备份方面,我将 Zotero 的默认文件保存路径修改为 OneDrive 上的目录,通过云盘进行备份,手动修改 Zotero 默认的文件存储路径的方式为:「首选项」->「高级」->「文件和文件夹」->「数据存储位置」。这里建议的操作是将原来的默认文件夹复制一份到想要切换的目录,然后在首选项中更改路径就可以了,原来的默认文件夹可以删除。

实测 OneDrive 的传输速度还是可以的,Zotero 的数据库会时常发生变动,通过 OneDrive 就能够对文献进行实时备份了,再也不用担心电脑故障导致辛苦收集的文献全部丢失的问题了。

移动端进行文献阅读

移动端负责的是文献阅读与标注,在 Zotero 完成文献的管理后,将想看的文献发送到 iPad 上进行阅读,完成 ZotFile 的设置后,对于我们想要发送到 iPad 上的文献,右键单击,依次选择「Manage Attachments 」->「Send to subfolders on tablet」,就能够将文献按照其在 Zotero 自定义分类中的情况,创建相应的子文件夹,发送到先前设定到的目标位置。

这里其实还有一个选项,直接就是「Send to tablet」,这个选项只会将 PDF 文件发送过去,而不会创建子文件夹,对于一些临时看的文献可以选择该选项。我选择将文献发送到 iCloud 的一个文件夹中,便于在 iPad 上进行访问。发送完成后,可以在 Zotero 的侧边栏发现新增了两个分类,分别为「Tablet Files」和「Tablet Files (modified) 」,前者里面放的就是我们发送到平板上的所有文件(其实这也是类似于打标签)。

发送到 iPad 后,就可以利用各种 PDF 阅读器对文献进行标注。自带的「文件」能够进行简单的批注,实际上手时也发现足够自己使用了,但是有一个我个人不太喜欢的问题,就是「文件」会挡住文献的右边缘,不利于充分利用空间。为了尽可能模拟在纸张上阅读文献的感觉,我找到了一个叫做「PDF Viewer」的免费软件,解决了边缘空白无法利用的问题。

在 iPad 上完成文献的阅读后,进入 Zotero,在「Tablet Files」分类中找到想要取回的文献,右键单击,依次选择「Manage Attachments 」->「Get from Tablet」,就可以将标注后的 PDF 文件重新取回到 Zotero 的文件存储位置,并覆盖原来的文件,使得标注的内容得以同步。标注后的 PDF 文件体积通常会变大不少。

软件设置好了以后,硬件也得设置好。Apple Pencil 抓着容易打滑,并且书写还是有一定的声音的,于是我就加了两个笔套:

笔尖套是淘宝上十几块钱十个的,防滑套是从晨光笔上弄下来的😂。

One More Thing——关于 MarginNote

对 MarginNote 的评价

MarginNote 是一款非常棒的「学习软件」,它做到的是「对知识的重新整理」,在对书籍进行初步阅读后,通过思维导图和复习卡,实现对书本内容的重组,然后进一步对书本内容进行精读,抓住重要的知识点,加深自己的理解。我在 MarginNote 2 的时候就已经付费购买了 Mac 端的 MarginNote,后来在「数码荔枝」处购买了一年订阅(没有收广告费的呀x2),iPad 上也准备购买,这是一个绝对物超所值的软件。

为什么在该应用场景不适宜使用 MarginNote?

既然 MarginNote 有着如此优秀的阅读体验,为什么我没有利用 MarginNote 来进行文献管理和阅读呢?

首先,MarginNote 在文献管理上不如专业的文献管理软件,一旦文档数量比较多,对所有文档一个一个打标签会很麻烦。其次,MarginNote 对文档的标注仅在软件内部可见,标注的笔记内容像是一个图层一样,覆盖在文档上,想要在其他软件中看到标注内容只能重新导出为 PDF,但是重新导出后的 PDF 元数据会发生变更。再者,MarginNote 能够兼容的云盘数量不多,iCloud 基本上是唯一的选择,免费的 5G 空间稍显不足,在不对 iCloud 空间进行扩容的情况下并非最好的选择。这三个原因使得 MarginNote 在期刊文献管理与阅读的任务上难以满足我的个人需求。

MarginNote 最佳的应用场合

我认为 MarginNote 最适合用来对专业书籍进行学习,专业书籍往往包含丰富的内容,需要我们进行消化再吸收,成为自己的知识,而 MarginNote 能够很好地帮助我们对系统的知识进行系统的学习。

总结

本文探讨了如何使用文献管理软件 Zotero 对文献进行管理的方法,结合 ZotFile 插件和 iCloud 等云盘,实现了「在桌面端管理文献,在移动端阅读文献,两者同步」的工作流,使得文献阅读和管理的工作能够在 iPad 和 Mac 上联动。

还是那句老话,工具是为人服务的,有了趁手的工具,认真干活才是正路。


本文为原创文章,转载请注明。

我的2019年度总结

2 January 2020 at 12:00

Unexpected 2019

这是我在 2018 年底完全不会预想到的一年

还算顺利地完成本科毕设,熬过了线性系统,开始了研究生阶段的学习。本科的小伙伴们都各自为了自己的梦想在努力拼搏,希望大家以后能保持联系😊

最帅的时刻估计就是停留在毕业照了

很幸运地获得了 WWDC Scholarship,认识来自世界各地的开发者们,在 San Jose 共度全球 iOS 开发者的“春晚”🎉还认识了很多志同道合的伙伴,WWDC 结束之后也还有联系,大家一起加油😄,各位大佬们带带小弟(主要还是自己菜)

大中华区的Scholars
大家一起加油哇

非常高兴能够有机会参加各个高校的 iOS Club 学生沙龙活动,和同学们分享一下自己的经历。其实吧我感觉好像也没有什么好分享的😂自己也是个普普通通的大学生(主要还是自己菜x2),也很羡慕能够奖项和 paper 拿到手软的各种大神,希望自己在接下来的一年也能做出能让自己满意的成果吧🤗

第一次参加的iOS Club沙龙活动

在江门市中心医院认识了宝哥、业航、昌林还有医院的师兄师姐,一起度过了一段很开心很有意义的研究生活。两位桂林片王有空去找你们玩或者你们来广州我好好招待你们😃还有机会见面的😎

下图就当作是今年给自己的奖励啦😝穷苦学生觉得 iPad 2019 已经足够满足自己要求了,吃性能和屏幕的任务还是交给 MacBook 和 iPhone 处理比较合适,iPad 就安安静静地做好一个深度阅读器吧!

心心念的iPad

可能算今年是一个人生的关键点吧。2020加油啦💪

iWork - 如何充分利用 iCloud 和自带 App 实现 Apple 设备之间的互通

6 October 2019 at 16:20

本篇是专题「如何充分利用 iCloud 和自带 App 实现 Apple 设备之间的互通」的第二篇文章,主要介绍如何在 Apple 平台上使用 iWork 办公套件完成不过于复杂的日常任务。

目录

  • 办公套件的选择
    • Microsoft Office
    • 金山 WPS
    • iWork
    • 开源解决方案
    • 其他
  • iWork 在多设备上的联动
    • Pages
    • Numbers
    • Keynote
  • 办公套件之外的
    • 文本文档工具
      • Markdown
      • LaTeX
    • 数据处理工具
    • 演示文稿工具
  • 结束语

办公套件的选择

「办公套件」已经不是办公人员的专属了——无论是财务人员、行政人员、办公室白领,抑或是在校学生,都有「制作一份不错的文档[1]」这一需求。在这个计算机如此普及的时代,掌握制作文档的能力可以说是一个不可或缺的技能:一份结构清楚的报告能够向你的上级展现出自己的工作态度,同时也能帮助自己对手头上的工作有一个清晰的认识和规划;一份图文并茂的表格或许能够直观地反映出大数据中蕴藏的商机,从而挖掘出新的用户痛点;一份生动的演示文稿能够向客户展现出产品的特色与优势,为公司在客户中留下一个良好的印象。对于学生来说,完成课程论文、分析实验数据、做好自我介绍等,这些任务不但要求他们对自己「要做什么」有一个清晰的认识,还要求他们知道「要怎么做」才能够达到事半功倍的效果。可以说,制作文档的需求存在于每一个使用计算机的用户上。

俗话说「工欲善其事,必先利其器」,要想制作出令人赏心悦目的文档,一套趁手的办公套件是必不可少的——你总不能指望自己能够通过记事本和图片浏览器就能够在规定的时间里完成一份合格的文档吧。目前市面上常见的办公套件选择主要包括以下几种:Microsoft Office、金山 WPS、Apple iWork、开源解决方案、其他。

Microsoft Office[2]

Microsoft Office

大名鼎鼎的微软 Office,办公套件一把手,旗下的 Word、Excel 和 PowerPoint 三件套已经成为了事实上的行业标准,其余所有的办公套件都需要兼容其格式。除了三件套以外,Outlook 是一款不错的邮件客户端,OneNote 是数字笔记本的翘楚,OneDrive 为广大用户带来了云盘的新选择。随着 Office 365 的推出,其平易近人的价格和得天独厚的多平台同步优势,使其受欢迎程度只增不减。

macOS 下同样有对应版本的 Microsoft Office,并且随着版本的更迭,Windows 和 macOS 下制作出来的 Office 文档的兼容性在逐步提高,旧版本中可能存在的一些排版问题,在新版本中或许已经不复存在了。当然排版问题在不同版本之间的 Office 之间就已经存在了的,如果对于格式有一定要求的话,建议采用 PDF 格式在不同平台之间传输。

金山 WPS[3]

金山 WPS

同样大名鼎鼎的金山 WPS~~(虽然更多的是因为它的广告)~~,据闻在微软进入中国市场之前垄断了中国的办公套件,并且拥有雷总的技术加持,金山 WPS 在今天依然受到不少人的青睐。可以说,除了 Microsoft Office 本身以外,金山 WPS 是对微软文档格式兼容得最好的一个办公套件,基本上在文本内容和格式上都不会有太大的差异,并且得益于会员制度,金山为用户提供了大量的模板,使得软件的易用程度又上升了一个台阶。

macOS 版本的 WPS 在 2019 年推出了正式版并上架 Mac App Store,在未登录金山账号的情况下对文档只读,并且会经常提示「登录账号以使用更多功能」,登录账号及加入会员计划后能够解锁更多功能。

iWork[4]

iWork

为了能够在自家平台上吸引更多的用户,与竞争(微)对手(软)形成对峙的局面,Apple 开发了自家的 iWork 套件,旗下的 Pages、Numbers 和 Keynote 分别对标 Microsoft Office 的 Word、Excel 和 PowerPoint。在 2013 年 9 月 11 日 iPhone 5s、5c 发布会上,苹果表示 iWork 套件将针对新购买苹果设备的用户开放免费下载,这对于用户来说不失为一个天大的好消息。对于不少关注 Apple 的人来说,Keynote 是其中名气最大的一款软件,毕竟这是 Apple 开发布会的御用幻灯片制作软件,黑白渐变的主题也成为了苹果的一个标志。随着版本的更迭,iWork 的使用体验在逐步上升,对于一些并不过于复杂的任务已经是足够使用的了。

iWork 套件拥有在线版本,可以在 iCloud 网页版中对文档进行查看、编辑操作,就算没有 Mac 也能够在浏览器中体验到 iWork 套件的魅力啦。

开源解决方案[5]

LibreOffice

如果想要使用正版办公套件,又苦于经济成本或平台限制,抑或者希望能够自己对软件的功能作出代码级别的修改,那么开源方案是一个不错的选择。在众多的开源办公套件中,LibreOffice 是笔者认为做得比较不错的,这也是内置在 Ubuntu 系统中的一个默认办公套件。在对标 Microsoft Office 上,LibreOffice 拥有 Writer、Calc、Impress,对于一般的文档编辑任务还是能够胜任的,但是在文件格式的兼容上面还有进步的空间。并且,LibreOffice 还加入了 Draw、Base 和 Math 三个新的功能,能够完成绘图、数据库管理和公式编辑的工作,对于有这方面简单需求的用户来说还是一个不错的选择。

其他

当然,除了前面介绍的办公套件以外,还可以自行寻找满足需求的软件,构成属于自己的工作流。由于可能涉及到比较多小众的软件,在这里就不多介绍了,读者可以自行在各大搜索引擎中进行搜索相关领域的软件。

iWork 在多设备上的联动

由于有 Apple 的加持,无论是在移动端还是在桌面端,iWork 能够有一致的优秀表现,并且与 iCloud 高度集成,方便用户实现对文档的共享和跨设备编辑。下面笔者将介绍自己是如何在日常的学习和生活中使用 iWork 套件满足自己对文档制作的需求的。

Pages

Pages 对标 Microsoft Office Word,是 iWork 办公套件中的富文本编辑软件。

和大多数的文字处理软件一样,Pages 是一款「所见即所得」的软件,用户在文档中对文字内容、格式作出的任何改变都会直接影响到文档的最终效果,例如对文字的加粗、下划线等。Pages 在操作上与 Word 相差不多,用户可以方便地在右边的格式面板中对文档的格式、布局等作出修改,并在主编辑面板进行文字的编辑。

Pages 界面

除了文字以外,在 Pages 中还能插入各种各样的对象,例如图片、视频、形状、表格等,使文档不仅仅是文字的堆砌,增加文档的可读性。除了传统的文字输入外,Pages 还支持插入文本框,改变文本的线性排列结构。得益于这一点,Pages 甚至能够完成海报的制作,结合内置的模板,能够帮助用户快速完成文档的制作。

对于不依赖于模板的文档编辑任务,Pages 能够非常方便、快速地制作出一份符合要求的文档。在我的研究生学习生活中,曾经用 Pages 完成了「线性系统理论」课程的所有书面作业,由于这门课的作业对格式并没有太多的要求,因此使用 Pages 能够帮助我快速完成格式排版,包括列表、图片插入、公式编辑等。使用「段落格式」功能能够快速设置文本的段落层次,使文章的结构一目了然,同时有利于文章目录的制作,在这一点上我认为 Pages 的易用性和用户友好度是高于 Word 的。

在写作业的过程中,不可避免地会遇到需要插入公式的场合。在 Word 中有内置的公式编辑器及第三方的 MathType 可以辅助公式编辑的任务,MathType 也有对应的 Mac 版本。然而,使用 Pages 内置的 LaTeX 公式编辑器同样能够编辑出非常漂亮的公式。选择「插入」->「方程」即可开始一条公式的编辑,Pages 使用 LaTeX 的语法对公式进行渲染,并且不需要额外安装 LaTeX 编译器,一切的渲染任务在 Pages 内部就能够完成。

Pages 公式编辑

在移动端上,Pages 与桌面端的差异不大,但为了适配移动端的屏幕大小,不少的功能都被隐藏在了二级菜单中,并且是以图标的形式展现出来的,不熟悉图标的话可能需要多尝试几次才能够知道图标所对应的功能。总体来说,在移动端更适合完成简单的文本编辑和浏览的任务,对于文字格式和布局的调整工作还是建议在桌面端上进行,比较方便。

Numbers

Numbers 对标 Microsoft Office Excel,是 iWork 办公套件中的数据处理软件。

相比于 Excel,Numbers 在使用上更加符合普通用户的习惯,常用的功能都已经有相应的实现,只需要。与 Pages 相同,Numbers 同样支持在表格文档中插入其他类型的对象,如文本框、形状、媒体文件等,使表格能够包含更多的信息,这一点在 Excel 中实现元没有 Numbers 简单易用。

尤其是在公式编辑的时候,在单元格中输入 = 号就能开始公式的编辑,并且在右边的面板中会显示内置的常用公式及对应的说明、注释与示例,帮助用户根据自己的需求选择合适的公式。当然,你也能够编写自己的公式来适应实际的工作需求。在单元格的引用方面,Numbers 同样支持工作表及工作表的数据引用,方便用户对数据作进一步的处理。

Numbers 公式编辑界面

为了充分发挥 iCloud 的同步优势并且懒得多装一个 App,我选择使用 Numbers 对每月的开支情况进行记录。在 Numbers 的内置模板中有一个「个人预算」的模板,能够对支出的种类、金额和时间进行记录,并用图表的方式统计各个类别的支出情况,并且可以设置预算,从而对自己的支出情况进行管理和控制。在这个记账本中就利用了工作表间的数据引用,「预算」工作表引用了「交易」工作表的数据,将每个类别的支出金额进行合计,并在「预算」工作表中绘制饼图反映各类别的支出占比,提醒自己可以在哪些方面减少自己的开支。

Numbers 记账本

对于「记账」这一应用场景,我习惯每当自己花钱的时候,就在手机上记录下这一笔交易,然后在电脑上查看当天的消费 情况,并对自己接下来的开支有一个初步的规划。虽然暂时还没能通过记账发现自己不合理的消费情况,但是养成记账的习惯起码能够知道「自己的钱有没有花在刀刃上」,帮助自己逐渐养成理财的意识和习惯。

相比于 Excel 来说,Numbers 确实还存在很多可以提升的地方,对于专业的数据处理任务来说 Numbers 还是难以胜任的,但是对于普通的任务来说,Numbers 已经游刃有余了。

Keynote

Keynote 对标 Microsoft Office PowerPoint,是 iWork 办公套件中的演示文稿软件。

幻灯片的制作可不是简单的「文字 + 图片」的机械组合,而是需要将各个元素有机地结合在一起,设计出具有美感的幻灯片。Keynote 希望用户更关注幻灯片元素之间的美妙组合,而不是把时间浪费在「寻找某个功能的按钮」上。Keynote 强大的对齐工具能够帮助用户快速对文本、图片等内容进行对齐,而在这一点上 PowerPoint 的表现就差一点了。除此之外,Keynote 的模板比 PowerPoint 更加人性化,如果只是进行一个简单的工作汇报,使用 Keynote 不仅省时省力,出来的效果还好,何乐而不为呢?

Keynote 以其出色的动画效果而闻名,其中最强大的莫过于「神奇移动」这一动画效果,它能够平滑地移动前后两张幻灯片中的同一元素,对于文字,还可以选择「按字符」进行过渡的效果。Microsoft Office PowerPoint 直到 2016 的版本才出现了类似的动画效果,并且使用起来远没有 Keynote 简单,这应该是 iWork 办公套件中打赢的唯一一场对决吧。

Keynote 动画效果选择

在移动端上,当 iPhone 和 Mac 处在同一局域网连接下时,可以将 iPhone 当作翻页笔来使用,控制幻灯片的播放,以后再也不用担心演示现场没有翻页笔的问题啦。除了翻页以外,在 iPhone 上还能够显示激光、笔画等工具,比翻页笔的功能强大的多,在演示的时候能够让用户更加专注于演讲本身而不是幻灯片。毕竟,听众是来听你演讲的,不是来看你做的幻灯片的。

办公套件之外的

工具的使用最终取决于任务的需要。实际上,除了办公套件以外,还有很多优秀的软件能够满足我们对于文档编辑的要求,将这些软件组合起来,何尝不是一个属于自己的「办公套件」呢?

文本文档工具

对于文本文档编辑的任务来说,主要的需求是文字的编辑与排版。Word 和 Pages 这类「所见即所得」的富文本编辑器固然能够满足大多数人对文本编辑的需求,但,Markdown 和 LaTeX 是其中两个比较流行的文本文档工具。

Markdown

Markdown 是一种轻量级的标记语言,与传统的富文本编辑方式不同,Markdown 是纯文本的编辑方式,通过特定的符号来表示文字的格式,如删除线加粗斜体等。本文的撰写就是基于 Markdown 完成的。相比于 Word、Pages 等采用的富文本编辑方式,Markdown 能够使写作者更关注于文章的内容而不是格式的修改,从而提高生产力。

由于 Markdown 文档的编辑是以纯文本方式进行的,因此使用任何一个文本编辑器都能够完成 Markdown 文档的撰写,但是如果想要看到 Markdown 渲染后的效果,就需要一款专门的 Markdown 编辑器了。笔者使用的。目前有不少的笔记软件,如印象笔记,已经支持 Markdown 文档的编辑和渲染了,如果不想多安装一个软件的话,可以考虑使用这类软件完成 Markdown 的编写。

LaTeX

LaTeX 是最好的文字排版软件,没有之一,它以类似于编译代码的方式来实现对文本格式的渲染。与 Markdown 相同,LaTeX 文档实际上也是一个纯文本文件,通过特定的字符和关键字来对格式进行规定,需要专门的 LaTeX 编译器对源文件进行编译、渲染才能够看到最终的效果。

LaTeX 对论文、期刊等对格式有着比较严格的要求的文章撰写中有着得天独厚的优势,但是对于普通用户来说,富文本编辑器和 Markdown 已经足够应付绝大多数的工作了,LaTeX 更加适合专业写作的人员(写文章也逃不了 debug 是什么感受)。

数据处理工具

对于数据处理的任务来说,主要的需求是对已有的数据进行整理、处理和可视化等。实际上,除了已有的办公套件以外,用户并没有太多的选择,因为 Excel 已经成为了行业标准,而 Numbers 与 Excel 之间还是存在着非常大的差距的,所以对于专业的数据处理工作来说,Excel 更加靠谱一点。

当然,对于程序员以及硬核用户来说,可以使用更加高阶的方法对数据进行处理,如 Python、Julia、Matlab等。Excel 能够做到的是「用最简单的方式为用户提供最强大的功能」,而如果通过编程的方式对数据进行处理,能够限制你的只有你的想象力了。近年来,Python 在大数据处理领域的热度成指数上升,得益于大量第三方库,使用 Python 或许能够以更加优雅的方式实现对数据的处理和可视化,相比于 Excel 来说,Python 的可玩性只增不减。

演示文稿工具

对于演示文稿来说,主要的需求是生动地展示文档内容,通过动画实现文档内容的突出显示、前后切换等。除了 Keynote 和 PowerPoint 以外,Prezi 也是一个不错的选择,它采用非线性的方式对演示文稿进行展示,在动画切换的效果上比较有特点。由于笔者没有使用过这款软件,感兴趣的读者可以自行感受一下这款软件的魅力所在。

结束语

本文介绍了如何在 Apple 设备上使用 iWork 办公套件满足日常的文档编辑需求。相比于 Microsoft Office 来说,iWork 在功能及兼容性上都存在一定的限制,无法像 Microsoft Office 那样成为「事实上的行业标准」。但是,大而全所付出的代价是运行速度较慢、需要一定的经济成本,对于一些普通的文档编辑任务来说,使用 iWork 已经能够满足要求了,如果需要在不同平台上进行兼容,导出为 PDF 才是最好的选择。

那么,你准备好打开 iWork 完成你下一个编辑任务了吗😊



本文为原创文章,转载请注明。


  1. 在这里,「文档」的概念并不局限于文本文档,还包括表格、演示文稿等内容。 ↩︎

  2. 图片截取自 https://products.office.com/zh-cn/explore-office-for-home ↩︎

  3. 图片截取自 https://www.wps.cn ↩︎

  4. 图片截取自 https://www.apple.com/cn/iwork/ ↩︎

  5. 图片截取自 https://zh-cn.libreoffice.org ↩︎

Apple File System Guide 学习笔记

13 September 2019 at 18:05

Introduction

由于 iOS 独有的沙箱机制,每一个 iOS app 都有其独立的、与系统和其他 app 隔离开的文件系统,通常来说,开发者只能够对自己 app 的文件系统进行操作,而不能随意访问和操作其他的文件系统。

在iOS中,每个应用各自有着相对独立、完整的文件系统,称为应用程序沙盒,简称沙盒。

任何应用程序的文件访问范围都不允许超出自身沙盒的范围,即无权访问其他应用程序沙盒中的文件。但可以通过获取用户权限来使其他沙盒中的文件发送到当前沙盒中。

文件目录结构与macOS的文件目录结构相类似,通常是通过目录的名称来区分其作用。

为了对文件系统有一个更加深入的了解,本篇笔记基于 Apple 官方文档的学习,总结了 File System 的使用方法。

File System Basics

下面简要介绍一下文件系统的基本知识。

常用文件目录

  1. Documents: 文档目录,用于存放用户生成的内容、直接与用户打交道的内容,即需要永久保存下来的内容。该目录会通过iTunes和iCloud备份。

  2. Documens/Inbox: 文档收件箱目录,用于存放用户允许的从外部应用中获取的文件。可以新建或删除该目录下的文件,但不允许编辑。该目录会通过iTunes和iCloud备份。

  3. Library: 资源库目录,用于存放非用户生成的内容,通常是程序或系统需要用到的内部文件。可以生成自己的目录。除了Caches以外的目录会通过iTunes和iCloud备份。

  4. tmp: 临时目录,用于存放临时文件,应用程序退出后该目录下的文件会被清空。该目录不会通过iTunes和iCloud备份。

  5. 其他文件目录:

    1. iCloud文件目录,涉及CloudKit的使用
    2. 查看模拟器应用沙盒:~/Library/Developer/CoreSimulator/Devices/(check device number)/data/Containers/Data/Application/(check app number)

图解:

Tips for file system

  1. 用户数据存放在Documents/中,允许用户新建、删除、编辑。

  2. 应用程序需要的支持文件存放在Library/Application Support/中,对用户屏蔽,包含各种保证应用程序正常运行的文件。

  3. 临时数据存放在tmp/中,这些文件不需要做持久化处理,使用完毕后应当删除,iOS系统会不定期清空该目录。

  4. 缓存文件存放在Library/Caches/中,生命周期比临时文件长,但不如支持文件长,缓存文件用于提高性能及用户体验,避免资源重复加载。iOS系统同样会清空该目录,因此应用程序应能够在需要时重新生成或下载这些缓存文件并保存在同一路径下。

Accessing Files and Directories

下面简要介绍如何访问当前沙箱下的文件及目录。

选择合适的方式来访问文件

对于不同类型的文件来说,选择一个合适的方式来访问它们,可以更好地发挥高层 API 的作用,使得文件访问变得更加简单。Apple 对于几种常见的、基本的文件类型提供了标准的访问方法,包括:

  1. 资源文件,包括Nib(Storyboard)、图片、音频、本地化资源、字符串文件等。这些与代码无关的资源文件通常用来显示本地化内容。一般是使用Bundle.main.path(for resource:of type:)来获取文件路径。
  2. 文本文件,包括txt等。文本文件是无结构的ASCII或Unicode字符,通常使用String构造器进行获取。
  3. 结构化数据文件:包括XML、plist等。结构化文件通常是在字符串的基础上,使用特定字符集合的数据。XML的解析有特殊的方法。
  4. 归档文件
  5. Package
  6. Bundle
  7. 代码文件
    在标准类型的文件无法满足需求的情况下,可以自定义文件后缀名,以字节流的方式对文件进行读写,能够对文件操作有更加自由的控制。

指定文件或目录的路径

通常来说,指定文件或目录路径的方式是使用URL,URL 的创建可以基于 String,即把路径用 String 写出来后,作为 URL init 的参数。有三种方式的URL表示路径:

  1. 基于路径的URL:file://localhost/Users/andy/Documents/MyFile.txt
  2. 文件引用URL:file:///id=6571367.2773272/
  3. 基于字符串的URL:/Users/andy/Documents/MyFile.txt
    使用 URL 获取路径的另一个好处是能够方便地与 FileManager 结合使用,从而使文件管理变得更加简单有序。关于 FileManager 的使用后面会有进一步的文档学习介绍。

在文件系统中对文件进行定位

在访问文件或目录之前,还需要对文件进行定位,常用的方式包括:

  1. 自己找。
  2. 询问用户,可以通过 Open and Save panel 与用户进行交互。
  3. 在标准的系统目录下寻找,这又可以分为:
    1. 在 App Bundle 中寻找:调用Bundle.main.url(for resource:with extension:)获得文件在 App Bundle 中的 URL。
    2. 在标准目录中寻找:可以使用FileManager.default.urls(for directory:in domain:)来访问标准目录下的文件 URL,其中第一个参数可以搜索的范围包括 Application Support、Documents、Library 等标准沙箱路径。
  4. 使用书签(bookmarks),bookmark 是 URL 的一个属性,是一个定位文件的持久化方法。

管理文件和目录

对于文件和目录的管理,无非是创建、复制、移动、删除、隐藏文件等。

创建

新建目录:使用 FileManager 的 createDirectoryAtURL 或 createDirectoryAtPath 方法在给定路径新建目录。
新建文件:使用 FileManager 的 createFileAtPath:contents:attributes 方法在给定路径新建文件。如果需要将 Data 或 String 的内容写入文件,可以使用 writeToURL:atomically 方法。

复制和移动

使用 FileManager 的 copyItemAtURL:toURL:error: 或 copyItemAtPath:toPath:error: 方法复制文件或目录;使用 FileManager 的 moveItemAtURL:toURL:error: 或 moveItemAtPath:toPath:error: 方法移动文件或目录。

文件备份实际上是将文件复制到一个不让用户访问到的位置。

**注意:**文件的复制和移动操作可能需要较长的时间,并且复制和移动的操作是同步执行的,因此推荐将文件复制和移动操作放到异步线程中执行,避免阻塞主线程。

删除

使用 FileManager 的 removeItemAtURL:error: 或 removeItemAtPath:error: 永久删除文件或目录。

隐藏文件

在 macOS 中,隐藏文件是文件名以.开头的文件,默认不会显示出来,但是用户仍然有办法访问到这些文件。对于 iOS,通常来说没有必要设置隐藏文件。

FileManager 文档解读

Apple 推荐的文件管理方式是使用 FileManager 进行文件路径的获取、访问、修改等操作。下面结合 FileManager 的官方文档进行学习,并对一些比较重要和常用的方法与属性进行记录。

概要

FileManager 是 Foundation 框架中的类,帮助开发者更加方便地与文件系统进行交互,其继承于 NSObject。通过 FileManager,开发者可以对文件和目录进行定位、创建、复制、移动等操作,也可以获取到文件和目录的信息并修改它们的一些属性。
定位文件可以使用 URL 或 String,更推荐 URL 是因为它可以用一种更加稳健的方式表示路径,并且 URL 提供了书签(bookmark)进一步简化文件的访问。
在使用 FileManager 对文件进行操作时,具体的操作可以由遵循 FileManagerDelegate 委托来定义,通过委托模式进行实现解耦和自定义。注意在这种情况下,在执行文件操作的时候需要创建一个新的 FileManager 实例,设置其代理,然后用这个新的实例对操作进行初始化(即避免使用 share 的 FileManager 实例)
在 iOS 5.0 和 macOS 10.7 以后版本的系统中,还可以直接使用 FileManager 对 iCloud 中的文件进行管理,发挥 iCloud 强大的同步特性。
对于多线程,share 的 FileManager 实例的方法可以安全地在多个线程中调用,即耗时操作可以放在异步线程中异步执行。
对于 macOS app,FileManager 能够,这里记录的主要是在 iOS 上的使用方法,更多的使用请查阅官方文档

创建 FileManager 实例

有两种方法:

  1. init(authorization),初始化方法,参数为 NSWorkspace.Authorization 类型的实例,用户给予的访问文件的权限。
  2. class var `default`:FileManager 单例,app 内共享的 FileManager

访问用户目录

实际上需要访问的是用户目录和临时目录:

  • func NSHomeDirectory() -> String:当前用户的主目录,全局函数
  • func NSUserName() -> String, func NSFullUserName() -> String:当前用户的用户名。这两个方法是全局方法,不是 FileManager 的类方法。
  • var temporaryDirectory: URL:当前用户的临时目录。

定位系统目录

  • func url(for: FileManager.SearchPathDirectory, in: FileManager.SearchPathDomainMask, appropriateFor: URL?, create: Bool) -> URL
  • func urls(for: FileManager.SearchPathDirectory, in: FileManager.SearchPathDomainMask) -> [URL]
  • func NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory, FileManager.SearchPathDomainMask, Bool) -> [String]
    上述三个方法都能够返回给定路径枚举的路径。

列举目录内容

  • func contentsOfDirectory(atPath:) -> [String];:搜索给定的路径,并给出路径下包含的内容的路径

创建、删除、替换、移动、复制

  • func createFile(atPath:contents:attributes:) -> Bool:在给定路径下、使用给定的属性键值对,利用给定的Data创建文件。
  • func createDirectory(atPath:withIntermediateDirectories:attributes:), func createDirectory(at:withIntermediateDirectories:attributes:):在给定路径下创建目录。
  • func removeItem(at:), func removeItem(atPath:):删除给定路径的文件或目录。
  • func replaceItemAt(:withItemAt:backupItemName:options:): 将某个路径下的项目替换成另一路径下的项目,允许创建备份,并且可以保证没有数据丢失。
  • func copyItem(at:to:), func copyItem(atPath:toPath:)::将某一路径下的项目复制到另一路径。
  • func moveItem(at:to:), func moveItem(atPath:toPath:):将某一路径下的项目移动到另一路径。
    以上就是在普通的开发任务中比较常用的文件操作方法。

对于需要登录的 app,如果不想采用 Core Data 来进行用户数据的存储,可以考虑将用户的信息以文件的形式保存在本地。当然,如果这么做还需要考虑到文件的加密等安全措施。


Example code:

// This code shows how to combine FileManager 
// and NSSearchPathForDirectoriesInDomains to deal with file system.
// create a file manager instance that manages the files in sandbox.
let manager = FileManager.default
// specify the location of directory, using a string-related enum.
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
// grasp the first element of the array, which is the path string.
let testDir = path[0]
print(testDir)
// add more details to system path.
let filePath = testDir + "/folder1"
do {
    // call the responding methods to create directories, files, or
    // access them.
    try manager.createDirectory(atPath: filePath, withIntermediateDirectories: false, attributes: nil)
    print("succeed")
} catch {
    // deal with errors in the above operation.
    print("error")
}

❌
❌