エウレカのPairs全球事业部にてiOSエンジニアとしてインターンをしている田口です。 3 / 1〜3 / 2に行われたSwiftの技术カンファレンスtry! Swift Tokyo2018に参加してきました。 私个人として今回が初参加となりましたが,Swift言语やアプリ开発について多くの知见が得られて楽しいカンファレンスでした。 今回はその尝试! Swift Tokyo2018をレポートします! 尝试! Swiftは国际的なSwiftのカンファレンスであり世界中から20人以上の讲演者と300〜700人の参加者が参加します。今回は800人规模の会场でしたが人で溢れかえっていました。 ! Swif Tokyoで过去最高の参加人数だそうです。 に表内容はiOSアプリ开発に限らずmacOS,watchOS,tvOSやServer Side Swiftなど様々です。特に今回のイベントではiOSアプリとServer Side Swiftについてのセッションが多い印象でした。 これまでにニューヨーク,インド,东京で开催され今年で3年目になります。各セッション后には登坛者と1 on 1で质问できる时间が设けられており気軽に质问ができます。 まず会场に到着し企业ブースはこのような感じです。 多くのスポンサー企业様がブースを设け,アンケートやグッズ配布などを行っています。 1日目にDeNAさんのブースで行っていたアンケートの结果はこちらです。 参加みなさんンジニアの开発环境についてアンケートを取っておられました。みなさん积极的に回答していて,开発环境について热く语っておられました。 続いてメルカリさんのアンケート途中结果はこちらです。 こちらではCocoaPodsとCarthageやプロダクトのSwiftrateについてお话しました。 同时イン会场はこの様になっており,同时通訳を受訳を登坛者のプレゼンを闻くことができます。 そして2日目の最后には聚会后! 多くのパフォーマーの方がいらっしゃり参加者を楽しませてくれました! お酒の力を借りて色々な方とお话することができました! Swift的事件驱动网络 残表者はApple社のNorman Maurerさんで発表内容はswiftでの低レイヤーのネットワーク处理に关するものでした。残念ながらスライドはシェアされていませんが,セッションにが告げられ会场が沸きました。 私たちは「Pairsがあったからこの人と出会い,结婚することができた」という人を増やし,プロダクトを通じて谁かの人生をより可能にあふれたものにすることを目指しています。 一绪に创り上げていけるエンジニア募集です 株式会社エウレカ的新技术をプロダクトに取り入れ,事业スピードを加速させたい人Wanted! 希望通过では,働くモチベーションや一に働くメスに働くメンにつーについて知ることができます。 www.wantedly.com
还可以学习叶子-一种可轻松生成视图的模板语言! 您可以在github上找到本教程的结果 本教程是如何设置Vapor 3项目的自然后续。 您可以先阅读该教程然后再回来,也可以跳过并继续阅读😊 指数 1.创建一个新项目 2.生成Xcode项目 3.配置您的项目以使用Leaf 4.创建您的第一个模板/视图 5.实施服务视图的路线 6.奖金:将数据传递到视图 7.从这里去哪里 1.创建一个新项目 蒸气工具箱非常好,让您可以使用任何git-repo作为模板轻松创建新项目。 由于我们将基于上述教程的结果,因此我们将使用它作为模板来创建一个新项目: 蒸气新项目名称–template = vaporberlin / my-first-route 注意:您可以通过在终端中输入vapor –help来查看蒸气工具箱的全部功能。 我们的命令将使用来自vaporberlin的 my-first-route作为模板创建一个名为projectName的项目(感谢vapor toolbox🤛🏻)。 2.生成Xcode项目 在生成Xcode项目之前,我们必须添加Leaf作为依赖项,并在Package.swift中更改包名称: // swift-tools-version:4.0 导入PackageDescriptionlet包=包( 名称:“ projectName”,//已更改 依赖项:[ .package(URL:“ https://github.com/vapor/vapor.git”,来自:“ 3.0.0”), .package(URL:“ https://github.com/vapor/leaf.git”,来自:“ 3.0.0”)//添加 ], 目标:[ .target(name:“ App”,依赖项:[“ Vapor”, “ Leaf” ]), .target(name:“ Run”,依赖项:[“ App”]), .testTarget(name:“ AppTests”,依赖项:[“ App”]), ] […]
如果您要开发的应用程序需要进行大量的数学计算,则可能希望能够在错误扩散到各处之前捕获错误,例如被零除和整数类型溢出。 浮点错误包括被零除(产生±infinity )和未定义( NaN )表达式,例如sin(Double.infinity) 。 这些无声地失败了。 整数类型的运算符错误仅限于溢出,这会使您的代码大声崩溃。 Swift提供了方便的整数算术运算符,允许溢出; &+ , &− , &*以及更多有用的方法,例如addWithOverflow(_,_) 。 我利用这些方法来定义抛出运算符&& + , &&- , && * , && / , &&%和&&? 。 用法如下。 / * *示例。 * / //产生15.27543444817377 func x()throws-> Double { 返回尝试(12.5 && + 2.3) && /(&&?sin(3.1)&& + &&? atan2(4,3)) } //引发ArithmeticOperationError(.infinity,.division) func y()throws-> Double { 让y […]
是时候挖掘核心数据了。 如果您错过了上一个第4部分:UIKit,请检查一下。 现在开始吧!!! 📀 问:持久性存储协调器可以有多个持久性存储吗? 持久性存储协调器将持久性对象存储和托管对象模型相关联 ,并向托管对象上下文提供外观,以使一组持久性存储显示为单个聚合存储。 它具有对托管对象模型的引用,该对象模型描述了商店中或其管理的商店中的实体。 在许多应用程序中,您只有一个商店,但是在复杂的应用程序中,可能有多个商店,每个商店可能包含不同的实体。 问:为什么我们需要多个持久性存储? 应用程序具有固定数据集,该数据集已包含在捆绑软件中 应用程序处理的某些数据是我们不希望保留在磁盘上的敏感信息 对不同实体有不同的存储要求 您可能已通过将文件包中的文件复制到可写位置并将其用作整个数据存储库的方式来满足第一个要求。 您可能已经通过手动删除对象来处理第二个问题。 在两种情况下,单独的持久性存储都是更好的解决方案。 每个持久性存储都有自己的特征-可以是只读的,可以二进制或SQLite或内存形式存储(在OS X上,也可以使用XML后备存储),也可以是您自己的NSIncrementalStore实现。 可以将模型的不同部分存储在不同的持久性存储中,以利用这种灵活性。 问:如何添加多个永久存储? 借助托管对象模型中的配置,我们可以实现这一目标。 我们可以创建多个配置并将其分配给不同的持久性存储。 我们希望每个商店一个配置 ,并且每个实体应仅添加到一个配置 (除了默认配置)。 实体可以具有多种配置,但是在这种情况下,您必须手动将每个对象分配给存储。 通过为每个商店创建一个配置并将每个实体分配给一个配置,我们使核心数据框架能够将实体定向到不同的商店,而无需任何进一步的交互。 问:我们可以在不同的持久存储实体之间建立关系吗? 您必须注意不要创建从一个持久性存储中的实例到另一个持久性存储中的实例的关系,因为Core Data不支持这种关系。 如果需要在不同商店中的实体之间创建关系,则通常使用获取的属性。 问:一个持久性存储协调器可以有多少个托管对象模型? 每个模型我们只能有一个持久性存储协调器。 问:持久性存储的类型? 问:持久存储安全性有哪些限制? Apple Docs说:Core Data 不保证不受信任来源(与内部生成的存储相对) 的持久存储的安全性,并且无法检测文件是否已被恶意修改 。 SQLite存储提供的安全性比XML和二进制存储好一些,但是不应将其固有地视为安全的 。 还应注意,元数据中存储的数据可能会独立于存储数据而被篡改。 为确保数据安全,请使用加密磁盘映像之类的技术 。 问:核心数据中的并发类型是什么? 两种并发模式NSMainQueueConcurrencyType和NSPrivateQueueConcurrencyType 。 NSMainQueueConcurrencyType特别用于您的应用程序界面,并且只能在应用程序的主队列上使用。 NSPrivateQueueConcurrencyType配置在初始化时创建自己的队列,并且只能在该队列上使用。 因为该队列是私有的,并且在NSManagedObjectContext实例的内部,所以只能通过performBlock:和performBlockAndWait:方法对其进行访问。 当您使用NSPersistentContainer ,viewContext属性配置为NSMainQueueConcurrencyType上下文,而与performBackgroundTask:和newBackgroundContext关联的上下文配置为NSPrivateQueueConcurrencyType 。 […]
您是否曾经尝试过让应用在横向和纵向上都看起来不错? 分开查看风景和肖像是否令人沮丧? 制作同时支持iPhone和iPad的屏幕布局会让您发疯吗? 如果是,我将带给您一些好的做法,以克服构建支持从方向到不同屏幕尺寸的所有内容的UI的所有障碍。 Autolayout不仅使您可以轻松地在应用中支持不同的屏幕尺寸,而且还使国际化变得微不足道。 您不再需要为每种希望支持的语言制作新的笔尖或情节提要,其中包括从右到左的语言,例如希伯来语或阿拉伯语。 在此自动版式教程中,您将了解: 支持纵向和横向的应用程序 iPhone和iPad的支持应用程序 学习如何使用约束来布局视图 使用尺寸类在不同坐标下显示视图元素 使用堆栈视图可以简化布局复杂的UI的工作 我们的示例应用 图1 —在横向模式下查看(iPhone X) 图2 —以纵向模式查看(iPhone X) 图1和图2显示了我们将在本教程中构建的示例应用程序。 该应用程序支持横向和纵向方向,并且在构建时牢记iPhone和iPad的屏幕尺寸。 自动布局的基础 固定菜单 它位于编辑器的右下角,用于向不同的视图元素(如标签,按钮,文本字段)添加约束。 这些约束不过是不同视图边界之间的关系。 约束可以固定,也可以在运行时更改以达到动画目的。 图3 — Pin菜单 对齐菜单 它位于图钉菜单的左侧,可用于水平和垂直对齐中心的视图。 图4 —对齐菜单 文件大纲 它位于编辑器的左侧,可用于查看视图的层次结构,还可以查看特定视图控制器内部添加的所有约束。 图5 —文档大纲 属性检查器 这用于自定义视图属性,例如背景颜色和字体。 属性检查器 摆出我们的观点 首先,我们将一个视图添加到视图控制器并为其添加约束。 在对象库中搜索UIView并将其拖动到视图控制器中。 添加您喜欢的任何颜色并添加约束,以使视图固定在屏幕的左上角。 选择视图并打开图钉菜单并使用以下约束: 图7 —向视图添加约束 图7显示视图的高度和宽度等于75,并且顶部锚点和左侧锚点设置为8,这基本上意味着无论是iPhone或iPad,还是横向或纵向设备,视图始终具有距离视图控制器左上角的距离为8。 添加这些约束后,我们的视图控制器就是这样。 图8 —示例应用程序 添加视图和为其添加约束似乎很容易,对吗? 但是您应该永远记住,编程并不是要一次又一次地重复相同的步骤。 让我们假设我们以刚才的方式构建UI。 我们的视图控制器中总共有20个矩形,对于每个视图,我们都必须添加约束,同时要记住每个与特定视图边界接触的视图之间的关系。 […]
Earnest最近发布了首个iOS应用。 下面,我分享了我们如何设计的一些细节。 笔尖和情节提要似乎无法相处。 想象一下一个iOS应用程序,该应用程序使用情节提要来布置视图控制器。 在某个时候,您想要创建一个名为MyView的UIView子类,用笔尖实现其布局,并使其出现在情节提要的视图控制器中。 创建MyView之后,将UIView对象拖到视图控制器中,然后将UIView的类更改为MyView。 “做完!” 你想自己。 您在运行应用程序时期望看到自己的工作成果,但是相反,您只会看到空白。 问题在于情节提要板默认情况下会忽略笔尖。 因此,您需要采取一些其他配置步骤。 您将在堆栈溢出详细说明方法上找到一些好评如潮的响应,详细说明了将笔尖配置为在情节提要中出现的最差方法:在情节提要中创建一个容器,将插座手动重新连接到新实例,或创建另一个UIView包含子视图。 我发现这些太多了。 每次使用自定义笔尖时,我都必须重复这些步骤之一,如果错过了某个步骤怎么办? 将UIView子类与程序化布局一起使用时 ,只有三个步骤。 我进行布局,将UIView对象拖到我的视图控制器上,然后设置我的自定义子类。 那行得通。 我希望从笔尖查看该行为: 我不要 : 每个笔尖和/或视图控制器的样板代码。 额外的意见浮动。 用其他东西包裹我的笔尖。 我要 : 在情节提要板上添加的所有约束都可以使用。 通过情节提要添加的所有子视图都将出现。 通过情节提要设置的任何UIView属性都将应用。 查看Swift ESTNib UIView扩展。 要使用,只需将扩展名添加到您的项目中,并为您的笔尖命名与您的子类相同的名称(即MyView.swift和MyView.nib )。 它是如何工作的? 如果存在与当前类同名的nib文件,该扩展将覆盖awakeAfterUsingCoder 。 它使用笔尖创建视图的新实例,将属性和约束从原始笔尖转移到新实例,并返回新实例以供视图控制器显示。 特别感谢XXNibBridge的创建者Sunnyxx,该目标具有相似的目标。 两个项目之间的主要区别包括 ESTNib传输NSLayoutConstraints的属性。 ESTNib不需要您的视图就可以遵守协议。 ESTNib通过覆盖工作,而XXNibBridge使用毛毛雨。 ESTNib支持添加情节提要的子视图及其约束。 未来的改进 ESTNib也应该能够从UIView子类反映定制的IBInspectable故事板属性。 检查我们 认真今天启动了我们的iOS应用程序! 对于我们目前的客户来说,这是通过Earnest管理您的贷款并推荐朋友的好方法。 对于其他所有人,您可以开始使用负担得起的学生贷款再融资或个人贷款。 在这里免费下载!
通过利用Swift的语言功能来创建和维护健壮的代码库。 因为您不想在承受提供功能的压力时发现并修复错误。 减少消防时间,增加创新时间。 不变性和可变性 健壮的代码可以很好地处理所有极端情况。 通过支持不变性,我们可以强制排除尽可能多的情况。 因此,使用let关键字声明的变量总是更有利的。 由于变量无法突变,因此我们消除了所有其他情况,从而使运行时的行为更易于推论。 枚举 枚举是我们可以确保对所有案件进行详尽说明的另一种方法。 当使用带有Swift枚举的切换条件时,编译器会强制处理所有可能的情况。 即使不注意,程序员也很难错过任何东西。 依靠快速的编译器将释放更多的认知资源来解决更大的问题。 选装件 不要使用隐式展开。 使用了! 产生nil值的运算符会导致运行时错误。 可选参数的目的是使零大小写对开发人员来说很明显,并将其作为预期或意外的情况处理。 主体类似于错误处理。 我们不应忽略可能的错误,也不应忽略可能的值不可用。 面向协议的编程 组合总是优先于继承。 每当发现需要让多个类型共享相同的行为时,请将它们抽象为协议,然后使用扩展名为该协议定义默认行为。 在下面的示例中,通常将Polygon作为基类,并使用Square和Triangle子类Polygon。 子类化确实可以工作,但是测试很尴尬,因为为继承的方法编写测试用例似乎很愚蠢。 但是,有可能方法被意外覆盖。 对于实现协议的纯快速对象,无法覆盖默认扩展名。 因此,在上述情况下仅测试Polygon扩展就足够了。 此外,通过子类化,sides属性将必须从超类继承。 这意味着该属性必须是可变的,以便子类更改它们。 因此,我们将无法排除我们不想考虑的情况。 而且,子类一次只能继承一个类。 如果您需要对许多不同的抽象建模,则很难将它们分开。 这对协议来说不是问题,因为类型可以实现多个不同的协议。 这些也在这里讨论:https://developer.apple.com/videos/play/wwdc2015/408/ 类型和协议 AnyObject或AnyClass的用法应为红色标记。 Swift的强类型系统是它的最佳功能之一,我们不应该绕过它。 知道函数期望和返回什么类型,将使人们更容易推断其行为。 实际上,我们可以进一步缩小范围,仅定义所需的协议。 考虑将物品从愿望清单移动到购物车的功能。 该协议从专利上明确了该功能的意图和作用。 在上面的示例中,我们知道购物车上仅执行添加操作,而在心愿单中执行删除操作。 这些对象的状态也保留在磁盘上。 而且,这允许我们传入实现上述协议的任何类型,从而减少耦合。 委托模式 在许多情况下,委托模式不是最佳模式。 一个很好的例子来说明这一点,它是iOS8之前的版本中的可可自己的UIAlertView和新的UIAlertController。 使用旧的UIAlertView,您必须像这样实现委托: 当多个UIAlertViews共享同一委托时,事情变得繁琐。 您必须找出哪个UIAlertView触发了委托。 另外,这还会引入代码碎片,其中显示警报视图的代码和处理解雇的代码位于代码库的不同部分。 使用UIAlertController,我们可以传入闭包来处理后续事件。 每个闭包实例将仅与UIAlert的一个实例相关联,没有歧义。 将职能视为头等公民。 […]
调用函数和评估脚本 所有这一切真正酷的是,您拥有支持WebKit(iOS和MacOS的Safari)的JavaScript引擎。 该增加一点时间了。 我喜欢为iOS开发,但我也花了很多时间为Node.js和网络开发。 我经常使用JavaScript库来减少必须编写的代码量,因为那里有比我更聪明的开发人员,他们已经进行了一些繁重的工作并创建了一些非常有用的东西。 因此,下一步是了解如何在代码中包含和使用现有的JavaScript库。 对于我们的示例,我将使用Moment.js来简化一些日期时间操作。 顺便说一句,我在所有实验中都使用了Playground。 如果您想继续,请使用iOS目标创建一个新的Playground文件。 从那里,您需要Moment.js库的JavaScript代码,该库是我使用Bower在本地安装的。 接下来,我将moment dir复制到Playground的Resources文件夹中。 如果您对此有疑问,可以参考我的其他一篇有关使用Playgrounds的文章。 接下来,我们需要找到moment.js文件的路径,将文件内容复制到String中,然后将其注入到JSContext中 。 请参阅下文,了解如何实现此目的。 这里重要的一点是,如果您调用JSContext的validateScript ()方法,则您正在执行的代码(在我们的示例中)将整个moment.js库添加为该特定JSContext的全局对象。 以后添加的任何脚本或JavaScript对象都可以使用moment.js的所有功能。 好吧,至少听起来不错。 事实是,这取决于您注入JSContext的脚本的内容。 为了使Moment.js库正常工作,我们需要通过另一个评估脚本调用来调用它的构造函数和格式函数。 完成此操作后,我们可以使用JSContext的objectForKeyedSubscript方法获得对moment.js库的引用,该方法将为我们提供初始化的moment.js对象。 有关如何调用方法或使用参数调用构造函数的一些示例,请参见上面的示例,这些示例在幕后使JavaScript发生了正确的事情。 一切都很强大。 扩展示例 继续前进,我想提供一个更深入的示例,让您考虑在自己的代码中实际使用它的可能性。 我们将创建一个用于显示联系人的小应用程序。 每次执行时,我们仅使用JavaScript库Faker代替真正的联系人,为我们提供新的联系人详细信息。 我们的示例将使我们能够在Swift中为联系人对象建模,并在Swift中创建可以在JavaScript中执行的函数。 我们还需要从具有“伪造”联系方式的JavaScript方面获取数据。 最后,我们将使用WKWebView实例在Playground的视图中显示联系人。 第一步是创建一个联系人对象和一个JSExports协议,该协议概述了我们想要向JavaScript公开的内容。 之后,我们需要使用JSContext的setObject方法来使我们的联系人对象可以在JavaScript中访问。 下一步是创建可以在JavaScript中执行的Swift函数。 我们需要使用@convention(block)语法将Swift闭包转换为一个块,该块将成为具有相同参数和返回值的JavaScript函数。 我们再次调用JSContext的setObject方法将方法传递给JavaScript。 但是,这一次,我们需要使用unsafeBitCast方法将Swift闭包转换为AnyObject类型,以使JavaScript能够正确处理。 我们的下一系列操作旨在向您展示如何从JavaScript调用和使用contact对象以及我们的createContact函数。 您将创建一个contact.js脚本并将其添加到Playground的Resources文件夹中。 我们的脚本将具有一个使用Faker.js库创建虚假联系人的函数。 目的是调用该方法以将新联系人添加到Playground中的视图中,并返回创建的联系人以进行进一步检查。 上面的代码为我们要向JavaScript公开的另一个对象创建了JSExports协议和类。 我们还将这个新类添加到我们的JSContext中,以便可以通过JavaScript对其进行访问。 另外,我们创建Swift方法将新的联系人对象添加到我们在Playground中可以看到的视图中。 接下来,我们需要将Faker.js库添加到我们的JavaScript环境中。 我们遵循用于添加Moment.js的相同模式。 我们还将我们的contact.js文件添加到我们的环境中,该文件将createFakeContact()方法添加到我们的全局范围中。 然后,我们在本机端获取对createFakeContact()方法的引用并执行它。 我意识到JavaScript与Swift中发生了很多来回的变化,但坦率地说,这是关键。 我希望您看到您可以轻松地来回传递对象并从任一侧执行方法。 我们需要做的最后几件事是添加page.html文件,该文件由WKWebView在我们的ContactDrawable类中加载。 为了显示我们的联系人,我创建了一个UIView并将其分配给XCPlaygroundPage.currentPage.liveView 。 执行Playground将显示一个简单列表,其中包含随机创建的“伪造”联系方式。 […]
在本文中,我们将讨论内存泄漏,并将学习如何使用单元测试来检测它们。 偷看一下: 这是用SpecLeaks编写的测试。 重要:我将解释什么是内存泄漏,讨论保留周期以及您可能已经知道的其他事情。 如果您只想阅读有关单元测试泄漏的信息,请跳至最后一部分。 内存泄漏 确实,这是我们作为开发人员面临的最常见的问题之一。 我们会逐个功能地编写代码,并且随着应用的增长,我们会引入漏洞。 内存泄漏是内存中永远被占用且永远不会再使用的一部分。 这是占用空间并导致问题的垃圾。 在某个时刻已分配但从未释放过并且不再被您的应用程序引用的内存。 由于没有对其的引用,因此现在无法释放它,并且该内存无法再次使用。 苹果文件 从初级到高级开发人员,我们都会在某个时候造成漏洞。 我们有多经验都没关系。 拥有一个干净,无崩溃的应用程序,消除它们是至关重要的。 为什么? 因为它们很危险。 泄漏很危险 它们不仅增加了应用程序的内存占用 ,而且还引入了有害的副作用和崩溃。 为什么内存占用量会增加? 这是对象未释放的直接结果。 这些对象实际上是垃圾。 随着创建这些对象的动作的重复,占用的内存将增加。 垃圾太多了! 这可能会导致出现内存警告情况,最终,该应用程序将崩溃。 解释不必要的副作用需要更多细节。 想象一下在init内创建通知时开始监听的对象。 它对此做出反应,将内容保存到数据库,播放视频或将事件发布到分析引擎。 由于需要平衡对象,因此我们在释放对象时在deinit内停止监听通知。 如果此类物体泄漏,会发生什么? 它永远不会死,也永远不会停止收听通知。 每次发布通知时,对象都会对此做出反应。 如果用户重复创建有问题的对象的操作,则将存在多个实例。 所有这些实例都响应该通知并相互介入。 在这种情况下, 崩溃可能是最好的事情。 多个泄漏的对象对应用程序通知做出反应,更改了数据库,UI,破坏了应用程序的整个状态。 您可以在The Pragmatic Programmer中的“死程序不说谎”中了解有关此类问题的重要性。 泄漏无疑会导致不良的用户体验和不良的App Store评分。 泄漏来自哪里? 例如,泄漏可能来自第三方SDK或框架。 甚至来自Apple创建的类,例如CALayer或UILabel 。 在这些情况下,除了等待更新或放弃SDK之外,我们无能为力。 但是我们很有可能在 我们的代码。 泄漏的 首要 原因是 保持周期 […]
•19/5/30:已为Swift 5更新了此内容,更正了一个错误。 这比其他任何东西都更适合我自己,因为UIPageViewControllers具有一些我经常要提醒自己的怪癖。 如果有人遇到这个问题,希望对您有所帮助。 首先,本教程将不使用情节提要。 这仍然适用于情节提要,但是如果您正在寻找基于情节提要的解决方案,则应牢记这一点。 因此,创建一个名为ViewController的新ViewController并添加一个名为UIPageViewController类型的名为pageController的可选变量。 私人var pageController:UIPageViewController? 接下来,我们需要viewcontroller扩展UIPageViewControllerDataSource和UIPageViewControllerDelegate 。 使用extension执行此操作,并添加所需的协议方法UIViewController()现在返回UIViewController() 。 扩展ViewController:UIPageViewControllerDataSource,UIPageViewControllerDelegate { func pageViewController(_ pageViewController:UIPageViewController,viewControllerBefore viewController:UIViewController)-> UIViewController吗? { 返回UIViewController() } func pageViewController(_ pageViewController:UIPageViewController,viewControllerAfter viewController:UIViewController)-> UIViewController吗? { 返回UIViewController() } 现在,在您的viewDidLoad或其他方法中,设置您的pageController 。 私人功能setupPageController(){ self.pageController = UIPageViewController(transitionStyle:.scroll,navigationOrientation:.horizontal,选项:nil) self.pageController?.dataSource =自我 self.pageController?.delegate =自我 self.pageController?.view.backgroundColor = .clear self.pageController?.view.frame = CGRect(x:0,y:0,width:self.view.frame.width,height:self.view.frame.height) self.addChild(self.pageController!) self.view.addSubview(self.pageController!.view) self.pageController?.didMove(toParent:self) } 这就是一大堆代码,它是做什么的? 首先,我们以滚动样式创建pageController ,并使其侧向滑动。 然后,将viewController设置为数据源,以便它知道从哪里获取尚未添加的所有viewControllers […]