Tag: swift

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,而不是返回特定子类的实例。 […]

斯威夫特的自我

今天我在谈论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 {})是类型赋值。

使用MVVM和RxSwift构建您的第一个应用程序

简介: 在本文中,我们将分四个部分逐步构建您的第一个Set应用程序。 第一部分将向您介绍MVVM应用程序体系结构。 第二部分将演示SetSDK设置过程。 第三部分将应用MVVM概念来为您的应用程序创建支架。 最后,在第四部分中,我们将使用SetSDK全天实时显示出发地和预计的目的地。 可以从Set Gitlab存储库下载该项目的源代码。 Set SDK允许任何开发人员通过简单的pod install将机器学习和用户行为预测集成到他们自己的应用程序中。 SDK的第一个版本侧重于用户位置,回答了以下问题:他们现在在哪里,下一步要去哪里? 我想提供一个简单的演示应用程序,以演示如何将SDK集成到您自己的项目中以及如何使用SDK的某些功能。 在我讲的时候,我将使用Apple的MVC(模型视图控制器)体系结构,MVVM(模型视图视图模型)和一种启用MVVM的技术(称为RxSwift)来替代它。 第一部分:MVVM概述 关于MVC设计模式为何变得令人头疼的信息很多,尤其是在更复杂的应用程序中。 MVC对视图控制器负有太多责任。 关注点很少。 在下图中,您将看到视图和模型主要只是存在而无需做任何事情,而视图控制器则负责更新和接收来自模型和视图的更新以及所有业务逻辑,数据库访问,联网等。 在复杂的应用程序中,视图控制器可能会增长为成千上万行代码,并且很难维护。 MVVM是对MVC的简单添加,它简化了视图控制器的作用: 对于您应用中的每个屏幕或视图控制器,我们都会引入一个附加文件,即视图模型。 视图模型负责与模型数据进行交互,并将业务逻辑和显示格式应用于该数据。 视图模型还可以利用数据库或网络抽象。 格式化的数据通常通过简单的属性,RxSwift Observable或类似的技术(稍后会详细介绍)提供给应用程序的视图层。 视图控制器的新角色是通常使用RxSwift之类的工具简单地将视图模型公开的数据“绑定”到视图。 视图控制器还处理诸如按钮轻击和手势之类的用户交互,将这些事件通知视图模型。 这样,视图和视图模型紧密耦合,可以视为单个功能单元。 MVVM可以使关注点更加清晰。 它从视图层中删除了业务逻辑和数据格式,并创建了易于通过单元测试进行测试的视图模型。 可以以“无头”方式(没有任何视图)测试许多应用程序,而可以使用更合适的工具(例如Xcode UI测试)来测试视图。 这样就导致了具有更少错误的应用程序,更易于维护。 第二部分 启动SetSDK应用 要启动SetSDK,您将需要应用程序的客户端凭据。 我们目前正在使用一个自助服务开发人员门户网站,您可以在其中创建这些凭据,但是现在发送电子邮件至sander@set.gl,您将通过电子邮件接收凭据。 SetSDK使用Cocoapods分发。 如果您不熟悉它,请访问他们的网站,其中有许多很好的入门指南。 使用File > New > Project… > Single View Application创建一个新的Xcode项目。 我叫我的MVVMSetSDK。 创建项目后,将其关闭并在命令行上导航到项目的根目录。 通过运行pod init初始化Cocoapods。 打开新创建的Podfile进行编辑,并向其中添加SetSDK依赖项: 并提供当我们要求使用他们的位置和运动数据时显示给用户的Info.plist字符串。 […]

Swift中的枚举与实际示例

介绍 枚举(Enum)为一组相关值定义一种通用类型,并使您能够在代码中以类型安全的方式使用这些值。 假设我们有一个名为dayType的方法,该方法接受一周中的任何一天,然后返回Weekend为Saturday and Sunday , Weekday为Monday, Tuesday, Wednessday, Thursday and Friday并返回This is not a valid date因为它无法识别已通过的内容进去。 该函数将按方面工作,但是当您实际要传递friday ,即使我们实际上要键入的是friday您误输入了friday时会发生什么。 上面的开关转到默认值。 This is not a valid date 。 这就是类型安全的enum带入表中。 它通过为一组相关值提供通用类型来帮助我们避免此类风险,如下所示: 通过定义用于保存日期的枚举,我们已经能够消除String的使用。 我们不需要在switch语句中的day枚举的每个声明之前使用Day追加,因为我们已经将day分配为Day类型。 因此,可以将dayType函数简化为以下形式: 我们必须了解,枚举最适合于声明具有有限可能状态的类型,例如方向(北,南,西,东),运动(上,下,左,右)等。 枚举值 我们可以为每个enum案例分配值。 如果enum本身确实与某物有关,则这很有用 我们可以使用rawValue关键字访问分配给枚举的值。 要访问上面的Week枚举,我们可以使用Week.Monday.rawValue ,它将返回Weekday 。 枚举关联值 关联值是将附加信息附加到enum案例的一种绝佳方法。 假设您正在编写交易引擎,则有两种不同的可能的交易类型,即买入和卖出,它们将具有特定的库存和金额。 我们可以使用如下的关联枚举值来表示 枚举方法 我们还可以在如下enum定义方法 注意上面的代码片段,对于enum每种情况,func描述都将返回“ This is a apple device”。 为了避免这种情况,我们可以在以下描述方法中使用switch语句 很高兴与您分享这篇文章。 如果您喜欢本文,请通过鼓掌👏表示支持。 […]

在24小时内学习Swift —注1

我今天正式开始学习iOS开发的Swift。 几天前,我和妻子阿里斯(Aris)在Barnes and Noble Book Store中选择了一本书。 乍看之下,我大致看了这本书,发现这本书更侧重于Swift编码的基础知识。 在这段时间里,除了BJ Miller撰写的《 24小时学习Swift》这本书外,我别无选择,但是Aris告诉我,我迟早要学习基础知识。 最后,我同意她的看法,发展是学习新语言的较快方法,但是语法,基本知识是基础,我应该在真正建立基础之前对其进行学习。 在第一个小时,我了解了Swift及其父Objective-C的故事。 而且我已经熟悉本机IDE XCode。 而且,我还会在Playground和REPL上玩耍,这是XCode提供的两个功能,用于测试整个项目的部分代码。 使用它们的主要好处是用于任何逻辑,运算等的测试,您无需再次创建独立的项目。 Playground和REPL之间的区别是REPL在终端中运行,只是以“ xcrun swift”开头,而Playground是XCode中的一种文件类型。 另外,我知道Swift中最基本的数据类型之间的区别。 Var —变量,Let —常数。 将int转换为String,使用“ String(a)”,合并两个字符串,仅使用“ +”和一些基本的字符串转换方法,“ lowercaseString”和“ uppercaseString”。 无论如何,这是学习Swift的一个很好的开始。 还有很长的路要走,但是我有信心。

TIL:如何在SpriteKit游戏中使用SKCameraNode

在过去的好时光中,过去常常需要花费大量的数学才能产生一种幻觉,即玩家正在向更大的“世界”移动。 我最近一直在玩SpriteKit,今天我了解了使用SKCameraNode实际创建该效果有多么容易。 什么是SKCameraNode 苹果的文档很好地解释了这一点。 SKCameraNode对象用于指定场景中可以从中渲染场景的位置。 换句话说,它可以用于在游戏世界中滚动玩家的视图。 如何使用它 俗话说,一张图片值得一千个字。 通过一个示例,了解SKCameraNode工作方式将更加容易。 我们将建立一个游戏场景,让玩家能够通过点击屏幕来浏览世界。 世界观将始终以主角为中心。 为了给事情增添趣味,我们还将添加一个得分计数器叠加层。 让我们首先使用SpriteKit创建一个新的iOS游戏。 默认项目模板包括带有一些示例行为的GameScene 。 由于我们专注于相机行为,因此我们将从头开始创建自己的SpriteKit Scene 。 让我们在场景编辑器中放置一些Color Sprites : 我将其中一个尺寸缩小(50×50),并给它命名一个名称player ,以后我们可以用它来从代码中引用该对象。 这将是精灵对象,它将响应触摸事件并在世界范围内移动。 我们的“相机”(播放器的视图)将以该对象为中心。 通过足够的拖放操作,让我们打开游戏场景swift文件并开始编写一些代码。 声明类型为SKCameraNode的可选变量: var cam: SKCameraNode? 这将是我们对相机的参考。 场景进入视野后,我们将初始化变量: 覆盖func didMove(以查看:SKView){ super.didMove(查看) cam = SKCameraNode() self.camera =凸轮 self.addChild(cam!) } 这里要注意的重要一点是,相机节点具有自己的框架,该框架占据屏幕的大小。 设置相机效果的位置会转换为设置可见屏幕的中心。 为了演示这一点,让我们首先尝试让我们的“玩家”在屏幕上移动以响应触摸事件。 为此,我们将在场景首次进入视野时获得对玩家对象的引用,并在触摸屏幕后使其移动: var cam:SKCameraNode? var播放器:SKSpriteNode? 覆盖func didMove(以查看:SKView){ super.didMove(查看) cam = SKCameraNode() […]