Tag: swift

RxSwift和可变参数DisposeBag的秘密

使用RxSwift的最新功能之一来显着清理代码 我的公司已经将RxSwift用于所有新的iOS项目已有一段时间了,我们开始欣赏它的强大功能,灵活性和简洁性。 就是说,我们可以说RxSwift在一个区域不那么简洁。 实际上,这是完全多余的。 所以今天,让我们谈谈Disposables和DisposeBags。 一次性用品和DisposeBags? 如您所知,Disposables和DisposeBags是RxSwift对Swift的ARC内存管理的让步。 当您订阅或绑定到RxSwift Observable或从RxSwift Observable驱动时,该订阅将返回Disposable。 一次性使用基本上是对该订阅以及该订阅的整个Observable链的引用。 在处置该一次性物品之前,订阅链一直存在(除非订阅收到完成事件或错误事件,但这是另外一回事了)。 所以。 最重要的是,订阅会返回我们需要维护的一次性物品,以便正确控制订阅的生命周期。 为了方便起见,这些一次性物品通常插入已创建的DisposeBag中,并附加到UIViewController(或在某些情况下附加到View Model或其他对象)。 一个DisposeBag就是它所说的,一袋(或一组)一次性用品。 那对我们有什么帮助呢? 好了,当视图控制器被释放时,其变量(包括bag)将被释放。 释放disposeBag时,其deinit函数将对其包含的所有一次性用品调用dispose。 那些可抛弃型对象继而释放它们可能要参考的任何可观测对象的引用,也可能继而释放其对观测对象的引用,依此类推,等等,直至完成。 一切都已正确释放,没有泄漏,每个人都很高兴。 除了一个小问题。 冗余 因此,假设您使用的是RxSwift,而您使用的是MVVM(模型-视图-视图模型)体系结构。 在此特定屏幕上,我们的视图模型将公开一组可观察对象,而我们的视图控制器会将这些可观察对象绑定到一组标签。 很基本的东西。 代码看起来像这样。 一切都非常简单,但是也许您在上面的示例中注意到了一些多余的样板代码? 那就对了。 每个订阅(绑定)都返回一个一次性对象,必须将其添加到disposeBag中。 每个人返回它。 并且每个人都必须维护。 没有解决的办法。 还是在那里? 解? 可变参数的处理袋 现在检查以下代码。 干净得多! 但是所有一次性用品都发生了什么? 好吧,RxSwift 4.3添加了DisposeBag的insert函数的可变版本。 可变参数可以使用一个或多个参数。 在这种情况下,一个或多个一次性用品。 因为如果您看内部dispose(by:disposeBag),您会发现它只是一行代码:disposeBag.insert(self)。 因此,我们无需获取每个订阅的结果并将其功能性地链接到Dispose(by :),而是绕过中间人并将每个人直接添加到disposeBag中。 我想您会同意代码绝对更加简洁,并且通过消除一些样板代码,我们可以更好地表达该功能的意图。 请享用。 顺便说一句,如果您发现DisposeBag上的可变参数插入很有用,欢迎您。 这是我对RxSwift项目的次要贡献之一。 😉

Swift中的Optional的Optional

您是否知道可选内容本身可以包含一个可选内容,而该可选内容又包含另一个可选内容? 在这种情况下,我们需要多次拆开包装 当您尝试访问window时,通常会看到它 let window = UIApplication.shared.delegate?.window // UIWindow ?? 这是因为delegate可以为nil,其window也可以为nil。 window ??。backgroundColor = .yellow 原始故事https://github.com/onmyway133/blog/issues/58

处理视图控制器状态

视图控制器除其他功能外,还负责在用户输入以及与基础数据进行交互之后更改视图状态。 您可以将视图状态视为输入和输出对。 每当用户选择一行时,视图控制器就会启用特定按钮。 当API开始获取新数据时,视图控制器将显示活动指示器。 映射这些状态的一种方法是使用布尔值,也称为标志 。 您有一个标志来说明当前状态是否为错误。 另一个告诉您内容是否正在加载。 彼此之间还有另外一种情况。 这种方法存在潜在的问题,您将看到其中的一些问题。 本文还显示了一种更好的状态处理方式。 每个州是一个不同的标志 检查以下代码以处理公共视图控制器的状态: var isLoading = false var产品:[产品] = [] var错误:错误? 在这种情况下,您有三种可能的状态: 装货 内容加载 错误 考虑所有可能发生的相互作用。 这里是其中的一些: 视图控制器使用API​​来获取内容。 然后将isLoading设置为true 。 并且由于它现在处于加载状态,因此它显示了活动指示器。 它接收一些产品作为响应,并在表格视图中显示内容。 但是,如果响应是错误,则显示“重试”消息。 用户拉表视图以刷新数据。 在这种情况下,视图控制器将再次调用API。 这是一个容易出错的代码,因为每当发生这些交互之一时,您都必须更新所有标志。 而且,即使您忘记了一个,您也可能陷入不确定的状态并产生错误。 接口上的状态通常是互斥的。 这就是为什么用标志处理状态会带来歧义的原因。 映射状态的更好方法是使用枚举。 用枚举处理状态 Swift中的枚举是强大的值类型,它们不仅可以定义一组相关的事物,还可以为每种不同的情况指定关联的值。 检查多选题应用的以下枚举映射状态: 枚举状态{ 案例未回答 案例回答(选项) 案例确认(选项) } 这显示了一组明确定义的状态。 视图控制器没有很多标志,而是具有如下状态变量: 类MCQViewController:UIViewController { var state:State = […]

在iOS中查看模型验证

在上一篇文章中,我谈到了如何为iOS实现MVVM体系结构。 在这篇文章中,我将说明在将MVVM体系结构用于iOS应用程序时如何执行验证。 我们将在允许用户设置新密码的屏幕上工作。 下面的屏幕快照显示了屏幕的用户界面。 更改密码屏幕由“ ChangePasswordViewModel ”控制,该实现如下: 这是我们需要考虑的一些验证规则。 密码长度至少为8个字符 密码需要匹配 ChangePasswordTableViewController利用ChangePasswordViewModel为视图提供数据。 从文本字段到视图模型属性的方向绑定是在视图控制器内部设置的,如下所示: 每当您更改文本字段中的值时,视图模型就会自动更新,其中包含文本字段中的最新值。 现在,我们需要执行实际验证。 可以在保存操作内部的视图控制器中实现此操作,如下所示: 不要那样做! 您不希望视图控制器中包含验证逻辑。 这使得视图控制器的代码本来就不需要在这里膨胀。 解决此问题的一种方法是允许视图模型进行自我验证。 理想情况下,您将实现一个验证引擎类,其职责是验证视图模型。 验证引擎是自定义属性的偶像用例。 不幸的是,Swift当前不支持自定义属性。 每个坏规则将由以下实现的BrokenRule类表示: ViewModel协议定义协议,该协议由brokenRules集合和isValid布尔属性组成。 ChangePasswordViewModel符合ViewModel协议并实现validate函数,如下所示: 验证功能执行所有验证。 同样,最好有一个单独的验证引擎类来执行所有与验证有关的任务。 isValid属性将重置规则集合,然后调用私有验证功能。 最后,它取决于破碎规则列表是否包含任何破坏的规则而返回true或false。 现在,视图控制器可以使用以下代码来验证视图模型: 让我们测试一下。 运行该应用程序,然后为新密码输入以下值并确认密码,然后尝试保存/更新密码。 新密码=保留空白 确认密码=保留空白 视图模型将无效,您将在输出窗口上看到以下消息。 [Headlines.BrokenRule(propertyName:“ newPassword”,消息:“ Password应该至少包含8个字符”)) 新密码= password123 确认密码= password345 [Headlines.BrokenRule(属性名称:“ confirmPassword”,消息:“密码不匹配”))] 我确实希望在Swift的未来版本中可以实现自定义属性。 使用自定义属性,我们的视图模型将如下所示简单: 显然,您仍然需要为“ Required”属性创建实现,但是我相信它将从视图模型中删除很多不必要的代码。 所有验证逻辑将移至ValidationEngine类,该类将根据修饰后的属性动态验证视图模型,并返回中断规则列表。 编程愉快! 您可以在此处下载代码: azamsharp / HeadlinesMVVM 通过在GitHub上创建一个帐户为HeadlinesMVVM开发做出贡献。 […]

使用Alamofire和SwiftyJSON构建简单的API搜索

本文有助于设计带有SearchBar的UITableViewController并在我们每次开始键入时填充结果,然后在点击搜索结果时打开SafariWebView。 此外,本文还有助于了解如何将Alamofire与SwiftyJSON结合使用以访问 API并获取一些结果,对其进行解析并将结果显示在UITableViewController中。 在Xcode中创建一个新项目 2.转到终端并输入项目目录路径,然后键入pod init这会在我们的目录中创建podfile ,然后从项目目录中打开此podfile,然后将其放入其中 平台:ios,’9.0’target’WikiSearch’做 use_frameworks! pod’Alamofire’,’〜> 4.7′ pod’SwiftyJSON’,’〜> 4.0’结束 然后在终端中运行pod install ,这将帮助我们安装这些依赖项并创建.xcworkspace文件,关闭项目并打开* WikiSearch.xcworkspace *文件。 如果您不喜欢cocoapods,请在这里看看 3.在Main.storyboard添加带有NavigationController的UITableViewController 4.我将创建一个ImageView , TitleLabel和DescriptionLabel ,因此TableView单元中将有三个UIElement。 5.将一个新类CustomTableViewCell子类化UITableViewCell到项目中,并从表视图单元格中添加引用布局。 整个项目可以在这里下载 你可以通过LinkedIn与我联系 谢谢阅读… later稍后在日记中阅读此故事。 every‍💻每个星期天早晨,您都可以在收件箱中等待本周最受关注的Tech故事。 阅读技术新闻中的值得注意。

同步UIView动画

我最近正在构建一个子视图控制器框架,并且在实现动画时,偶然发现了iOS开发中一个相当常见的问题。 我正在尝试同步两个动画,使它变得不那么琐碎的事情不是直接应用于核心动画,而是使用带有动画关闭功能的常规UIView动画实现的。 更糟糕的是,由于该组件是作为框架开发的,因此解决方案必须没有牢固的联系。 幸运的是,我找到了一个很想与您分享的解决方案。 为了给您更多的上下文,我正在尝试解决的问题是框架布局算法。 我想为框架的用户提供对视图层次结构的完全控制,让他们可以选择在展开,折叠或仅隐藏UI组件时应用动画。 第一步只是在动画环境中运行算法。 我采用了最直接的方法,即将算法包装在UIView动画闭包中,并为动画提供从其当前状态开始并允许用户交互的选项。 后者的添加不会破坏用户的滚动体验。 到目前为止,一切都很好,但是当我开始测试不同的场景时,我很快意识到动画是不同步的。 如果动画持续时间与框架使用的持续时间不匹配,则它们将永远不匹配。 客户端代码如下所示: 如果仔细观察,您会发现框架和客户端应用程序的持续时间是不同步的,主要是因为用户想要的值不是框架默认值。 从框架的起源出发,我想使API尽可能简单,并且比您从未学习过的东西更直接。 我以为自己遇到了障碍,将不得不实施另一种方法来正确过渡到新状态。 经过一些调查并深入思考了UIView动画是什么,我提出了一个解决方案。 UIView动画是核心动画之上的一种便利抽象,这意味着可以通过向图层询问其动画键来解决这些问题。 对于我尝试解决的情况,我可以简单地查询要监视的视图,以了解其中是否有绑定动画。 这将决定布局算法是否将使用动画运行,或者结果是否应立即呈现。 因此,在了解正在播放哪些动画关键帧的情况下,我可以提取动画并检查其值。 对于帧动画,我最终得到一个` CABasicAnimation` 。 因为CABasicAnimation继承自CAAnimation ,而CAAnimation又符合CAMediaTiming ,所以我可以使用我想要的duration属性。 我把它们全部包装在CALayer的一个小扩展中,这不是美的事,但是它可以工作。 https://github.com/zenangst/Family/blob/master/Sources/iOS%2BtvOS/Extensions/CALayer%2BExtensions.swift 就我而言,我只关心第一个动画及其持续时间,因此最终将其作为框架中的实现: https://github.com/zenangst/Family/blob/master/Sources/iOS%2BtvOS/Classes/FamilyScrollView.swift#L250-L257 我敢打赌,大家可以将其应用到更高级的案例中,但是这种方法有助于解决我所面临的问题,希望您觉得这有用,这对我来说是大开眼界的。

Swift中面向协议的编程简介

当我几年前开始编码时,它全都与面向对象编程有关。 借助Swift,出现了一种新方法,使代码更易于重用和测试, 面向协议的编程 。 在介绍解决方案之前介绍问题,让我们假设我们拥有这个Vehicle类,并且Car继承了它的速度值。 class Vehicle { var speed : Double = 0.0 } class Vehicle { var speed : Double = 0.0 } class Car : Vehicle { override init() { super.init() self.speed = 90.0 } } class Car : Vehicle { override init() { super.init() self.speed = 90.0 } } 但是,在开车之前,我们使用了其他交通工具,例如马,也有特定的速度。 在没有继承的情况下,该新对象如何表现为其他对象? […]

简单选项弹出框

您遇到了多少次需求,需要显示一个弹出窗口,该弹出窗口显示可供选择的选项列表。 在某些情况下,我们还会显示刻度线,以确定已经选择了哪个选项。 假设您创建了一个视图控制器,其中显示了要排序的选项,升序和降序以供选择。 外观如下图所示。 现在过了几天,您还需要显示一个弹出窗口,该弹出窗口显示用于选择按选项排序的选项。 我经常看到开发人员编写新的viewcontroller来显示这些选项以供选择,如下图所示。 现在,我们无需使用协议,而是可以使用协议并创建可以显示任何类型选项的通用ViewController。 此弹出窗口还将根据选项的大小和文本来调整大小。 让我们在下一部分中进行此操作。 让我们首先记下我们的要求: 弹出框应显示选项对象的数组。 选项将是一个对象,该对象可以提供要为该选项显示的文本,该文本的字体。 我还应说明是否选择了该选项。 如果选择了一个选项,则需要显示刻度线。 选择选项后,应将回调与选项对象一起发送到委托对象。 显示选项的UIViewController子类应该是通用的。 我们应该能够显示任何一组选项。 弹出窗口应调整为可变的文本宽度。 现在我们有了我们的要求,让我们开始编码。 让我们首先照顾最简单的部分。 让我们首先将选项对象需求转换为协议。 我们还使用协议的扩展来计算它显示的文本的大小。 它使实现保持清洁。 现在我们已经有了RBOptionItem协议,让我们在UITableViewCell上创建一个扩展,以便可以由RBOptionItem对其进行配置。 容易吧? 接下来,我们创建名为RBOptionItemListViewController的视图控制器 。 它执行以下操作: 将tableview设置为其视图。 包含RBOptionsItem的二维数组,称为item。 它使用这些项目来填充表格视图中的单元格。 设置项目时计算preferredContentSize 。 它具有可向其通知选项项目选择事件的委托对象。 它还充当tableview的数据源和委托对象。 它将modalPresentationStyle设置为popover类型。 这就是RBOptionItemListViewController最初显示的方式。 这里的关键部分是计算首选内容的大小。 看一下calculateAndSetPreferredContentSize()方法。 它将二维数组项展平为一维。 然后,在展平的数组上,我们调用reduce以获得弹出框所需的最大宽度。 同样,我们通过展平数组来计算选项项的最大数量,最后计算总高度和宽度,并将其设置为viewcontroller的preferredContentSize。 现在,我们在RBOptionItemListViewController上编写扩展,以使其充当表视图的数据源和委托。 在这里,我们使用项目将选项内容填充到表格视图中。 委托也用于通知选择事件。 我们的简单选项popover现在可以使用了。 让我们尝试一下。 让我们创建一个选项类型,分别选择排序顺序和排序依据。 创建一个简单的UIViewController子类,我们可以在其中进行测试。 您可以使用下面的要点自行编写。 使用此ViewController实例后,您将看到我们的弹出窗口正在按预期工作。 它是通用的,可以呈现选项项。 此处提供了此简单弹出窗口和演示的源代码。 您可以继续改进自己的实现。 到此结束本主题。 […]

枚举的许多面孔

对我来说这已经是忙碌的几个星期了,很高兴终于回到我可以适应例行程序的地步。 从单身派对到一个可怕的寒冷一周,再到哈德逊山谷度过一个愉快的假期,我再一次发现自己在布鲁克林,在计算机前编写代码(与之相对的另一种纸张和纸本一支钢笔)。 所以我们到了! Swift有了对Objective-C的许多改进(对于那些久经考验的真正的Objective-C开发人员,我很容易受到侮辱),从面向协议的编程到功能更强大的编程方法,甚至是围绕泛型的更好的API。 Swift中的许多功能使其成为令人愉悦的开发体验。 我个人发现Swift中的枚举很棒。 它们一直是我最常用的语言功能之一,以至于我可能对它们有些执迷,这是一个错误。 但是,我将介绍一些我认为枚举可能非常强大的案例,特别是 澄清看似任意的值 状态的封装 错误包装 澄清看似随意的值 枚举最适合用来处理似乎晦涩难懂,难以记忆的价值并为其应用含义。 这主要是因为它们是可读值。 我们每天在语言中使用的单词具有很多含义,我们可以在有些令人困惑的值之上轻松地传递该含义。 让我们以CAGradientLayer为例。 CAGradientLayer具有两个属性,即startPoint和endPoint ,它们都是CGPoint.实例CGPoint. 苹果将​​这些文件记录如下 绘制到图层的坐标空间中时,渐变的起点和终点。 起点对应于第一个渐变停止点,终点对应于… 这是对这两个属性的不必要的冗长描述。 这两个点都代表渐变开始的x和y坐标,范围从0到endPoint和endPoint的默认值endPoint为[0.5, 0] endPoint [0.5, 0]和[0.5, 1] endPoint [0.5, 1] 。 很难想象这意味着什么,但这是从上到下的垂直渐变。 如果要构建水平渐变层,则需要执行以下操作。 func horizo​​ntalGradientLayer()-> CAGradientLayer { 让渐变= CAGradientLayer() gradient.startPoint = CGPoint(x:0,y:0.5) gradient.endPoint = CGPoint(x:1,y:0.5) 返回梯度 } 即使现在只输入一次,我也必须考虑一下startPoint和endPoint可以正确定义水平渐变的不同可能性。 这种想法容易出错。 使用枚举定义渐变方向可以改善我们对渐变实际外观的可视化。 枚举GradientDirection { 垂直案例 案例水平 […]

SwiftNIO入门

您可能在不久前听说过有关SwiftNIO的谣言(发音为Swift Neo,就像Matrix的家伙一样;-)。 当我第一次阅读它时,我感到非常兴奋,想查看一下它,看看它与Java中的Netty有何不同。 到目前为止,如果您熟悉Netty,则应该对SwiftNIO感到宾至如归。 如果不是,请继续阅读。 为什么选择服务器端Swift? 为什么不? 如果您已经是iOS开发人员并且喜欢编写Swift,为什么不尝试在Swift中构建后端? 与Java,C#,Ruby,Go和Node等其他语言相比,Swift几乎没有相同数量的软件包可供您在后端重用。 但是,所有这些社区都必须从某个地方开始。 如果您在后端查看诸如Go和Javascript之类的新语言,那么像您这样的人会对这种语言感兴趣并解决了有助于推动社区前进的问题。 为什么选择SwiftNIO? 已经有一些很棒的服务器端Swift框架,例如Vapor,Kitura和Perfect,您可以开始构建Web应用程序。 实际上,Vapor将SwiftNIO用于其网络层。 我想做的是向您介绍一些较低层次的基础知识,以更好地理解较高层次的抽象。 在本文中,我们将介绍SwiftNIO。 从他们的Github页面上获取: SwiftNIO是跨平台的异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。 它就像Netty,但是为Swift编写的。 SwiftNIO架构 首先,让我们简要介绍一下SwiftNIO的组件。 Channel是负责处理入站和出站事件的构造。 它连接到基础网络套接字。 Channel具有ChannelPipeline ,它是ChannelHandler对象的双链接列表。 ChannelHandlers通常包含您的应用程序逻辑。 当发生网络事件并按顺序处理时,将触发ChannelHandler方法。 本质上, ChannelPipeline是数据处理事件的管道。 入门 本教程适用于Mac,但是适用于Linux的步骤应该非常相似。 由于我们在服务器上,因此我们将使用Swift Package Manager(SPM)。 有两个部分: 创建一个TCP客户端 创建一个TCP服务器 第1部分:创建客户端 我们将运行一些命令来创建我们的项目结构并生成我们的Xcode项目。 打开您的终端并运行以下命令: $ mkdir TCPClient $ cd TCPClient $ swift软件包init –type可执行文件 $ swift包generate-xcodeproj $打开TCPClient.xcodeproj / 这是什么: 为您的项目创建一个名为TCPClient的新目录 […]