MVVM —您做错了

应用程序体系结构是移动开发中的热门话题,并且有一个原因–每个应用程序都需要某种逻辑形式的结构化代码以保持可靠,可扩展和可维护。 iOS应用程序没有什么不同。 最受欢迎的体系结构之一是Model-View-ViewModel,其中视图控制器和视图属于“视图”部分,而视图模型是负责应用程序业务逻辑的单元。 我本人一直在使用此体系结构,还曾见过其他人的多种实现,但是它们似乎都不令人满意,它们显然错过了一些东西。 这些根组件都不适合执行导航或创建控制器。 在控制器负责这些任务的情况下以MVVM方式执行操作感觉很错误,但是我们必须这样做吗? 在MVVM领域中,用于处理路由的一种可能的解决方案是,视图模型公开一个接口,该接口告诉视图控制器何时何地应该路由到何处。 但是,此解决方案远非理想–它使视图控制器意识到其在应用程序中的位置,从而降低了我们以后重用它的能力。 解决此问题的更好方法是引入一个附加组件,这是经典MVVM所缺少的。 有两个常用的对象–路由器和协调器。 两者都是有效的解决方案,它们使单元测试的某些部分非常简单,但是,有一个关键的区别–路由器从视图控制器的单个实例管理路由,而协调器则负责整个流程。 哪一个更好? 与往常一样-没有适合所有应用程序的灵丹妙药解决方案。 如果您的应用程序有很多独立的屏幕,可以在不同的上下文中显示-您可能应该使用路由器,如果它的屏幕可以分为几个控制器-长流程,则协调器可能是更好的解决方案。 我现在正在处理的应用程序属于第一类,因此我一直在使用路由器。 让我解释一下为什么它们比没有它们的路由有这么大的改进。 请记住,以下大多数属性都是与协调员共享的。 路由器的界面不需要了解UIKit,它所使用的控制器很可能只是暴露了push , present和dismiss等基本方法的协议,因此,路由器易于测试,并且可以在不考虑平台的情况下使用或设备。 路由器的导航界面是唯一的界面。 如果使用View Controller执行路由,则可能要处理甚至可能不感兴趣的大量方法–同时,路由器的接口仍然很小,非常简单并且可以完全测试。 尽管您不一定需要测试路由器,但是这种简单性可以使视图模型测试更加简洁,因为对路由器的调用通常是复杂的视图模型逻辑的结果。 为了充分利用路由器,我们需要将它们注入到我们的视图模型中。 我们通过为每个路由器使用协议来实现这一点,并且可以解锁更多令人惊奇的特性: 几乎在每个屏幕上都会执行一些与UI相关的常见操作,例如活动指示器的显示和错误/成功消息的显示-这些可以放置在所有路由器的某些根协议中,例如RouterType,因此我们可以避免很多操作不必要的代码重复。 通用路由器功能可以使用默认实现封装在协议中并组成。 例如,我们可能希望我们的路由器能够向SafariController提供一些URL –这不是所有路由器都需要做的,但是我们可能在一些地方使用它。 我们要做的就是创建一个具有默认实现的协议,并且可以与其他协议(例如ImagePickerRoutable或DocumentBrowserRoutable)进一步组合。 路由器的使用还有一个很大的属性-它使提取与导航相关的通用逻辑变得非常容易。 假设您有很多警报,操作表或弹出窗口,需要用户执行某些操作,然后执行一些任务并关闭。 如果随后应执行某些UI动作(例如活动指示器或其他控制器的表示),则视图模型通常会将其通知给视图控制器。 现在,如果需要在多个应用程序中处理相同的动作,我们可以轻松地将逻辑提取到单个可重用的单元中。 但是如何处理那些与导航相关的动作呢? 如果由控制器处理它们,那么所有的人都需要这样做-这可能是大量的代码重复,而且浪费时间。 对于路由器,这个问题根本不存在-我们可以将其与一些常见的处理程序一起重用。 我还要在这里提一些与视图模型相关的实践: 视图模型不应该是数据源,而应该公开单元配置所需的数据-这对于测试复杂的tableViews和collectionViews特别有用。 您还应该将数据源创建为单独的对象-将来可以轻松重用它们,并且立即进行操作不会有任何危害。 用于填充特定视图(例如UserTableViewCell)的数据应包装为单个结构,例如UserCellConfiguration。 这种结构只是实际数据与其转换之间的薄薄一层,用于填充所有标签,imageViews等。 它使实际模型和视图之间的区别更加容易。 使用依赖注入–可以在单元测试中使用模拟对象,使其易于编写。 视图模型不应导入UIKit-这不是至关重要的事情,但是如果您牢记这一点,它将有助于您保持UI与逻辑层之间的分离。 除了介绍路由器之外,我还在应用程序中使用了另一个组件– ControllersBuilder。 ControllersBuilder只是一个简单的结构,能够在整个应用程序中创建所有控制器。 每个构建方法都遵循相同的方案: 初始化路由器并自行注入,因此此类路由器可以请求创建另一个控制器 初始化视图模型,注入所需的依赖关系,初始数据和路由器 初始化视图控制器并注入视图模型 将视图控制器分配给路由器的弱属性 这里要注意的重要事情–最好让构建器方法返回普通的UIViewControllers,而不是返回特定子类的实例。 […]

Material Design Component pada iOS应用程序:按钮

Pada tulisan kali ini kita akan mencoba mengimplementasi prinsip-prinsip dari Google物料设计 Pada aplikasi iOS。 物料设计 sejak awal merupakan 设计语言 yang独立dalam arti tidak terikat pada satu platform saja。 Walaupun Mungkin untuk saat ini Adaptasi 材质设计 paling sering kita jumpai pada aplikasi-aplikasi Android dan produk-produk Google tentunya。 Google sendiri telah menyediakan cukup banyak 资源 untuk memudahkan 设计师和 […]

介绍货架!

今天,iOS上的每个人都将能够首次更新该应用程序并体验制作Shelfies! 什么是书架? Shelfie是一种显示和播放您的收藏的新方法。 您可以突出显示您喜欢的贴纸,卡片和3D图形,对其进行装饰,并添加迷人的覆盖物以完成外观! 您还可以在Feed中看到其他人的架子! 因此,走到那里,发挥创造力,看看别人在做什么,然后用拳头赞美自己喜欢的作品! 在2018年第三季度在Android上寻找Shelfies。 但是Shelfies并不是最新应用程序版本中的唯一更改: 交易:您可以在Feed的顶部而不是屏幕底部的标签栏上找到交易。 交易本身是相同的,只是在Feed中找到了新家。 我们希望您所有的社交活动都生活在同一个地方,以便与您的朋友联系,并了解他们的工作变得更简单,更简化。 展示柜:您可以继续在Android上进行展示,但不能在iOS上进行展示。 我们知道有些人会错过Showcases,但我们有信心,如果您尝试一下Shelfies,就会发现一种全新的很棒的方式来表达自己,并与您的朋友和社区分享您的创作。 而且,重要的是:在星期五,我们将要求所有iOS用户升级到最新版本的Quidd,所以请不要等待升级并开始享受Shelfies! 我们也想花一点时间感谢我们所有的Beta测试人员在过去几周中为测试Shelfies所做的时间和反馈。 您的见解对于我们调整Shelfies来为您带来最好的体验非常重要。 既然Shelfies已发布,我们将继续吸收每个人的反馈意见,并使Shelfies和Quidd成为更好的体验! 请将任何新的Shelfies反馈发送至Shelfies@myquidd.com。 立即前往App Store或Google Play商店下载并升级到Quidd的最新版本!

宣布可可足类产生

抬起头,我们已经搬家了! 如果您想继续了解Square的最新技术内容,请访问我们的新家https://developer.squareup.com/blog 在Square,我们将CocoaPods用作iOS开发工作流程的核心部分,除了在测试套件中使用的库之外,仅在Square Point of Sale应用程序中就有大量库。 这些库中有许多都生活在我们的主要iOS monorepo中,并且随着应用的增长,构建整个库所需的时间也越来越长。 CocoaPods允许我们将所有库配置保留在易于阅读的podspec文件中,与Xcode项目相比,在pull请求中查看它要容易得多。 我们的项目配置很少在Xcode本身中进行管理,这确保了文件系统与Xcode中所见内容之间的一致性。 但是,将所有这些模块集成到单个项目中存在一些缺点。 尽管我们已经投入了很多精力来改善安装依赖项的整体性能,但是运行单个pod install命令可能需要一到两分钟,这在快速迭代单个库时会加起来。 使用CocoaPods还会生成一个非常大的Pods项目,这可能导致Xcode的响应速度变慢,并且需要很长时间才能建立索引。 随着这种爆炸性的增长,显然我们需要开发人员更好的工作方式。 我们希望能够隔离地处理模块,仅构建正在积极处理的模块并仅运行随附的测试套件。 输入pod gen。 我们构建了一个CocoaPods插件,该插件使iOS开发人员可以轻松地独立处理各个库,尤其是在monorepo的情况下。 运行此插件将生成一个存根应用程序项目,该项目将集成并链接指定的库及其依赖项。 我们完整的Pods项目大约需要95秒才能生成,并且是35.6万行。 如果我们在U​​I组件库上运行pod gen,则将花费5秒钟并生成9000行项目文件。 这使得在这些基础库上的迭代变得如此快,并且工具可以轻松地处理较小的规模。 关于gen的最酷的事情是它会自动从现有Podfile和Podfile.lock中引入依赖项限制,因此所有配置都将在生成的库工作区中进行镜像。 这不仅在用于独立开发的monorepo上下文中有用。 当与可从podspec中描述测试的功能结合使用时,它在独立的存储库中也可以很好地工作。 这使独立库可以避免检入和维护自己的Xcode项目,因为CocoaPods可以对其进行管理。 这意味着您不再需要处理PR中的Xcode冲突,因为.xcodeproj只是生成的用于开发的工件。 podspec本身成为描述您的库的真理源,您可以使用简单的pod gen命令从中生成工作区。 可以简单地将测试放在文件系统中的相应文件夹中,并将其作为CI的一部分运行。 例如,我们设置了这个演示仓库,我们需要签入的唯一配置是podspecs。 我们已经在内部使用这种宝石几个月了,很高兴与世界分享它!

使用SnapKit可视化斐波纳契序列

处理约束是用户界面编程中最普通,最挑剔和不可避免的方面之一。 作为新开发人员,我绝对需要以编程方式设置和处理约束的经验。 用代码全部写出来提高了我的效率,提高了我对如何最合理地安排视图和子视图的理解。 话虽这么说,但是如果您仅依靠UIKit功能来进行约束,那么就会有局限性,并且不乏烦恼。 我的一位讲师向我介绍了一种非常用户友好的CocoaPod,名为SnapKit,它可以简化并简化过程。 SnapKit通过消除对约束规则和视图本身进行单独操作的需求,使您可以更动态地设置和更新约束。 它在语法上也更加简洁。 由于这些原因及其他原因,SnapKit有助于避免常见错误,例如在应用程序生命周期中的任何给定时刻将过多或过少的约束应用于视图。 我决定制作斐波那契序列的可视化动画作为起点,以测试SnapKit的实用性和便利性。 资深的数学书呆子会同意:斐波那契数列非常有趣,因为它在整个自然和人类文化中表现出许多模式。 一个示例是金色矩形,其侧面的比率为〜1.618。 在铌酸钴的磁共振中,从量子水平到帕台农神庙设计的巨大规模,这一比例是显而易见的。 这是我想生成的图像: 首先,我编写了一些函数来生成斐波那契数,并创建具有相应尺寸的方形UIView。 我为每个正方形应用了随机的背景色,并将初始alpha值设置为零,以便以后实现淡入动画。 我希望约束正方形将是棘手的部分。 我首先编写伪代码来定义它们的排列算法: 将第一个正方形固定在超级视图的中心。 将第二个正方形的顶部和左侧边缘分别与第一个正方形的顶部和右侧边缘对齐。 将第三个正方形的右边缘和上边缘分别与第二个正方形的右边缘和下边缘对齐。 将第四个正方形的底部和右侧边缘分别与第三个正方形的底部和左侧边缘对齐。 将第五个正方形的左边缘和下边缘分别与第四个正方形的左边缘和上边缘对齐。 对系列中的其余正方形重复前四个步骤。 使用SnapKit,将这些步骤转换为Swift实际上非常容易。 您可以使用switch语句实现重复,该语句为索引模4的每个可能值指定一组不同的约束: 现在,该让它看起来很酷了! 我使用了关键帧动画来使正方形逐渐淡入。 使用.CalculationMode Paced会使每个正方形的渐变以渐进的方式发生,而不是从透明变为不透明。 这是最终产品: 我想添加一个滚动视图,以便您可以查看任意数量的斐波那契正方形的迭代模式的完整范围。 这将增进我对SnapKit的理解,特别是因为滚动视图是在约束方面要实现的更具挑战性的UIView类型之一。

斯威夫特的自我

今天我在谈论Swift中的self关键字。 让我们先检查一些代码。 这是关于属性初始化的错误使用,但是开始我们的主题是个好方法。 这是运行后的日志: 有人可能会问为什么self在这里是一个函数,它不应该是当前类的实例吗? 好,让我们添加一些东西。 和添加lazy关键字后的日志: 看到, self成为我们现在期望的当前类的实例。 那么, lazy这个魔术词做什么呢? 也就是说,关键字lazy确保默认闭包内的self被初始化并引用当前实例。 但是,当没有lazy声明时, self指的是什么? 我们知道许多语言都有关键字self ,例如Ruby和Python。 还有许多其他语言具有相同的模式,但是在JavaScript,C#和Java中使用了不同的关键字,例如this 。 而且,JavaScript中的this始终是指诸如global东西,但是迅捷的self并不总是具有意义。 让我们进一步了解我们在一开始所声明的内容。 看来我们可以得出结论了。 开始时的self是指NSObjectProtocol中的self 。 让我们做一个小实验: 我们声明一个名为SomethingNotInheritedFromNSObject的类,该类未从NSObject继承。 并且self在初始关闭时变得不确定。 这就是Swift中的自我。

为iOS创建自定义键盘

每个开发人员都希望在创建产品时尽最大努力。 通常,它需要超越自然设计的新颖感。 幸运的是,应用程序扩展允许开发人员在常规应用程序的边界之外添加自定义功能。 此扩展名的类型很少,其中一种是自定义键盘。 从iOS 8开始,开发人员可以使用此功能扩展其移动应用程序。 系统键盘很棒,很舒适,而且功能齐全,但是在某些情况下,第三方键盘可以通过滑动或预测用户意图等功能来加快文本输入速度(这是一种什么样的法术?)。 引入它时,我的第一个想法是创建一个简单的密码管理器。 在小型非物理键盘上输入复杂的密码从未如此简单。 从理论上讲,我只需要构建自己的自定义键盘并从此过上幸福的生活。 但是,后来我了解到某些并发症,例如… 自定义键盘限制 这种局限性可以用一句话来概括:不允许自定义键盘访问标准应用程序通常可以使用的许多功能。 首先,关于我的密码管理器,Apple担心用户隐私。 这就是为什么我们在与安全字段进行交互时不能使用自定义键盘的原因,因此无法创建带有魔术按钮的键盘来为我们完成所有这些密码键入操作。 此外,您不能在普通键盘框架上绘制任何元素。 真可惜,但是它与Apple的人机界面设计规则很好地结合在一起,就是这样。 除此之外:我们无法通过“自定义键盘”访问麦克风和摄像头,这使得听写输入变为不可能。 正如您所期望的那样,该列表还在不断增加。 您可以在《人机界面指南》中阅读有关限制和良好实践的更多信息 ……和必备 必须提供一个按钮以切换到其他键盘或返回普通键盘。 在系统键盘中,是那个神秘的地球仪图标按钮。 为了不干扰用户的期望,习惯和根深蒂固的行为,我建议保持这种状态。 我们需要做的就是将带有所有事件的addTarget添加到我们的键盘UIInputViewController到方法advanceToNextInputMode()中(用户可以点击此按钮以快速切换到下一个按钮,或者点击更长的时间以查看可用键盘的列表)。 要使用Internet连接,用户必须允许完全访问自定义键盘。 它允许键盘与包含的应用程序共享数据。 因此,该应用程序可能成为键盘的管理器应用程序。 因此,它可用于同步用户词典或调整并保留用户首选项。 但是,键盘必须在没有完全访问权限的情况下始终可用,因此也需要没有互联网连接。 实施技巧 只是一些代码片段 我不想逐步介绍您,因为我确定您知道如何创建xcode项目,添加视图等。要开始,您需要添加一个新目标(文件->新建->目标->自定义键盘扩展名)到现有项目。 您将获得InputViewController子类,它是键盘的起点-您可以在此处添加控件。 让我们跳到很酷的东西。 例子在Obj-c中,但是基本上它们没有任何特定于语言的怪癖,如果您使用Swift编写,翻译它们会非常容易。 我还建议阅读Krzysztof Pelczar文章中的更多实施技巧。 处理回车键类型和键盘类型 如果要创建可用作主键盘的应用程序,则需要准备不同的文本输入方案。 为了方便用户,您可以在用户输入电子邮件地址时添加“ @”按钮而不是其他按钮,或者在编辑仅允许数字的字段时仅显示数字键盘。 您可以通过在InputViewController中调用来获取当前的键盘上下文(UIKeyboardType): 我不建议使用可视界面构建器来放置按钮,因为完成该过程将花费更长的时间,并且从长远来看将变得很难维护。 通常,在我们的项目中,我们使用具有布局约束(可视格式语言-FTW!)的代码创建视图。 但是这次我决定计算每个键盘按钮的帧将是更好的解决方案。 它只会被计算一次,不需要在相邻视图之间保持约束,并且可以非常快速地调整大小。 一段代码只是为了展示它是如何完成的: 处理长按按键重复 当用户点击删除按钮时,他希望它将删除文本,直到他抬起手指。 这就是它在本机键盘上的工作方式,这也是根深蒂固的用户行为之一。 但是有一个缺陷。 据我所知,没有手势识别器会在每个给定的时间间隔被调用。 但是我们可以使用UILongPressGestureRecognizer和NSTimer做一个简单的技巧。 结果,当用户将手指放在删除按钮上时,我们每150毫秒接收一次事件。 […]

谈论我的泛型-第1部分(快速3)

“ 通用编程是一种计算机编程风格,其中,算法根据稍后要指定的类型编写,然后在需要以特定类型作为参数提供时将其实例化 。” — Wikipedia 使用泛型编写代码是一种编写函数和数据类型而无需指定需要使用哪种确切类型的方法。 顾名思义,泛型类型不是特定的。 通过使用泛型,我们可以编写非特定的代码,因此,我们可以以更清洁,错误更少的复杂方式抽象代码。 在第一部分的这篇文章中,我将讨论数组,字典和可选变量是泛型的示例。 第二部分将讨论编写通用数据类和结构,编写通用函数和约束通用类型。 第三部分将讨论“关联类型”和“通用where子句”。 泛型数组 数组是泛型类型的示例。 数组是容纳任何类型的事物的容器。 在Swift中,我们受益于类型推论,我们可以在不显式声明其类型的情况下创建数组。 由于我们不必指定任何数组的类型(因为可以推断出该数组的类型),因此该数组作为数据结构是通用的。 让月份= [“一月”,“二月”,“三月”] 上面,我有几个月的时间。 由于我给的数组类型参数是字符串,因此本月的实例被赋予了STRING的具体类型 。 换句话说,声明数组时应定义的类型。 数组的通用语法 尽管上述月份的数组未正式将类型声明为STRING,但是有一种通用的语法声明数组,如下所示: var springMonths:Array = [] 请注意,单词Array后跟尖括号中的类型。 我们可以在这些尖括号之间分配“通用类型”的名称。 对于泛型,规范是使用作为我们引用的任何泛型类型的占位符。 在数组上使用通用方法 数组是通用的,带有通用方法,例如.append() springMonths.append(“ March”) springMonths.append(“ April”) springMonths.append(“五月”) 您会在上面注意到,我在springMonths数组中附加了月份“ March”,“ April”和“ June”。 如果我接下来编写以下代码行,您会怎么办: springMonths.append(50) 如果您猜编译器会给我错误: 那你是对的! 由于我们声明了Array的类型为STRING,因此任何不是字符串的东西都会使编译器不满意。 泛型词典 字典也是泛型的,因为我不必直接指定每个键的类型或每个值的类型。 例如,在下面,我没有具体说明字典类型: let monthDaysDictionary = [“一月”:31,“二月”:28,“三月”,31,“四月”:30,“五月”:31,“六月”:30,“七月”:31,“八月”: 31,“ […]

将您带入地图:iOS逐步介绍Google Maps

该博客旨在逐步详细地介绍如何在iOS应用上设置Google Maps。 对于此博客,任务是在The Flatiron School放置一个Google Maps标记,以便每个人都知道我在哪里学习。 第1步-安装Cocoapods Google Maps SDK可作为Cocoapods窗格使用。 当您听到术语Cocoapods pod和SDK时,有些人可能会像这样this。 不用担心,那很好。 不久之前,我的脸也一样。 让我们首先来看一个简单的SDK。 SDK是指软件开发人员套件。 Cocoapods是管理Xcode项目库依赖关系的工具。 换句话说,Cocoapods是使我们的生活更容易开发软件的工具。 如果尚未安装Cocoapods,则只需转到终端(按Command + Space并编写Terminal)。 在那里,输入: 须藤宝石安装cocoapods 等待安装完成,然后输入“ pod”。 如果看到此图片,恭喜! 您已经安装了Cocoapods。 如果您对此还有其他疑问,请访问https://cocoapods.org/ 第2步-创建Podfile 首先在您的Xcode中创建一个项目。 为了撰写本博客,我们将创建一个名为“ Google Maps”的单一视图项目。 然后,打开终端并转到包含您的项目的目录。 在那里,输入“ pod init”。 这将创建一个名为“ Podfile”的pod文件。 下一步是编辑该文件以添加Google Maps依赖项。 我们将使用文本编辑器打开文件来完成此操作。 我使用Atom(https://atom.io/)。 如果您不这样做,我强烈建议您开始使用它。 如果您有Atom,只需在终端中编写“ atom Podfile”,pod文件将在Atom中为我们的版本打开。 在文件中添加“ pod’GoogleMaps’”并保存(Ctrl + S)。 您的文件应如下所示: 最后,我们准备在项目中安装Podfile。 为此,我们只需键入“ pod […]

Swift:类型推断与类型分配

快速地,类似的赋值语句具有两种不同的行为,在一个地方它的行为就像类型推断,而在另一个地方它的行为就像类型赋值。 swift中类型推断和类型赋值之间存在细微差别。在类型推断中,它直接复制变量的类型,就像在let语句中将赋值的赋值中那样,这意味着将其解包为可选 输入推断示例: 现在名称的类型为Optional 意味着名称完全推断了str变量的类型,该变量为Optional String 类型分配示例 结论 :两者都是赋值语句,但是第一个(name = str)是类型为infer的简单赋值,第二个(如果让plicateName = str {})是类型赋值。