Tag: swift

查看Swift中的装饰

本文适用于开发人员,iOS爱好者以及在确保移动应用程序中的视觉连贯性方面遇到挑战的任何人。 让我解释。 有时,您的移动应用程序中不同类型的元素具有相同的外观,对吗? 他们是连贯的。 通常,实现此目标是有代价的:您最终编写了许多行重复的代码,希望这样做不会使所有内容的可维护性降低,并且在需要时也很难进行更改。 一种可能的解决方案: 常量 。 正确分组并正确使用常量可以解决不一致问题,但是可维护性和代码简单性不会显着提高。 一定有另一种方式…… 在尝试找到一个简单,优雅的解决方案时,我想到了一个接口,该接口定义了一个入口点,用于在整个应用程序中自定义UIKit元素。 这个概念称为ViewDecorator 。 模式的核心是通过以下协议定义的,描述了装饰器对象的行为: 通过将外观包含在这些分隔符对象中,可维护性和可读性得到提高,重复代码行减少了。 想象一下,在更名的情况下修改组件的样式有多么容易。 😄 访问UIView子类的属性 由于协议采用特定的UIView类型,因此在尝试自定义特定的UIView子类属性时可能会遇到问题。 一种可能的解决方案可能是将视图实例转换为decorate(_ view: UIView)内的预期类型,但这会搞乱单一职责原则,并使其在不同种类的实例上的行为有所不同。 相关类型进行救援! 让我们看看装饰器对象如何变化: 很整洁吧? 我很高兴听到您对这个解决方案以及我可能没有想到的有趣用例的看法。 您也可以转到我的博客以获取更多文章。

构建Swift API端点

我们通常在常量文件中维护服务常量,从而针对开发,登台和生产等环境更改端点。 不幸的是,当将构建推入存储或进行临时发行时,此过程容易出现人为错误。 为避免这种情况,我们可以根据环境构建baseURL,如下所示。 然后,您可以在扩展中添加与应用程序相关的功能,如下所示: 在行动中使用环境: 我们可以使用构建设置中的“调试”设置来维护相应的环境。 为此,我们将使用预处理程序宏#if DEBUG并将环境设置为“开发”。我们可以在#else下将环境更改为“暂存”或“生产”。 可以如下所示管理端点。 我们甚至可以将属于特定功能的多个端点归为一组。 例如,与用户相关的API可以分组在“ struct User”下,并且可以作为“ structName.method”进行访问。 用法: 我们可以看到路径如何在Xcode中直观地流动: 通过单击以下链接,可以查看样本ServiceConstants.swift文件: https://github.com/appitventures/IOS-Blog/blob/master/ServiceConstants.swift 有关如何从项目目标和方案维护环境的更多详细信息,请参阅此博客: 设置Xcode目标:如何使用不同的API环境创建多个iOS构建

免费持续集成开源Swift软件包

苹果已经发布了自己的软件包管理器,称为“ Swift Package Manager”,以共享和分发Swift软件包。 最好在我们的开源Swift软件包中添加免费的持续集成,以确保代码更改不会破坏任何内容。 幸运的是,TravisCI为您在Github上的所有开源项目提供免费服务。 在这篇简短的文章中,我们将了解如何为Github上的演示包Greeter添加TravisCI支持。 启用TravisCI 如果您已经拥有GitHub帐户和Swift Package,这将很方便,然后我们可以使用Github凭据注册TravisCI服务。 TravisCI将指导您有关为项目启用CI服务的过程。 这是三步过程,如下图所示 基本上,这很简单,我们需要同步github帐户,该帐户将显示我们的所有存储库。 那么您需要在项目的根目录中添加.travis.yml文件。 并使用git push推送构建 添加TravisCI配置 让我们将.travis.yml添加到上面的Greeter示例包中,内容如下。 操作系统: -osx 语言:通用 sudo:必填 dist:可信赖 osx_image:xcode8 脚本: -快速构建 -快速测试 通知: 电子邮件: on_success:从不 on_failure:更改 Travis具有不同的构建环境,我们必须选择安装了所有必要软件组件的最新xcode 8.3映像。 这意味着我们已经在Travis服务器上快速安装了,然后我们需要运行脚本来构建我们的程序包并测试我们的程序包。 我们已经在上面的.travis.yml文件中完成了此操作。 现在,我们增加了对TravisCI的支持,我们需要在T​​ravis上启用项目,并且它将在每次提交或新的Pull请求之后“构建”和“测试”我们的项目。 我们的Greeter示例的Travis项目示例可以在这里找到 有太多选项可用于自定义.travis.yml文件。 您可以在此处阅读有关配置选项的更多信息 如此简单,希望您也将TravisCI服务添加到您的Swift软件包中。 像XCBlog的 XCTEQ 发布的帖子一样 ? 您可能还喜欢我们的一些服务,例如访客博客或Mobile DevOps(CI / CD)或测试自动化。 在 Github 上 搜索我们的 服务 ,开源项目, 或者在 […]

迅速的财产观察员

didSet 使我可以 通过局部常量 访问 旧值 : 在此示例中,我提供了名称oldNumber ,可以在方括号内使用该名称来访问旧属性值// 5000 当我将属性的值从5000更改为6000时,didSet {}中的代码将被执行//“旧值5000更改为6000” willSet 使我可以 通过局部常量 访问 新值 : willSet发生的事情是,通过它我可以与旧值// 500同时访问新值 更改值后,将执行willSet {}中的代码//将旧值1000更改为500

为什么我比Interface Builder更喜欢代码

本文 最初发布在我的博客上 。 像今天许多iOS(和macOS)开发人员一样,我通过遵循Apple自己的教程开始为Apple设备进行开发。 我仍然记得,在2011年并且来自Python和C ++的背景下,在助手编辑器上打开.xib文件和相应的类,然后从一个文件拖到另一个文件以创建IBOutlets和IBActions ,是多么神奇。 我想,“太好了”。 摆脱样板代码而只关注我的应用程序应该做的事情确实令人耳目一新。 没多久,我开始看到依靠这种矛盾的缺点。 特别是当我从从事小型教育项目转为开始与其他开发人员进行大型项目时。 合并地狱 .xib和情节提要板都不是人类可读的。 您应该使用Xcode为您生成的那些文件的漂亮图形表示。 这意味着,如果您和您的同事碰巧在同一个文件上进行工作,那么其中一个将不得不在以后解决合并冲突。 很多时候,合并将涉及随机选择更改,然后在Xcode上打开文件并弄清楚需要做什么以确保文件处于所需状态。 最后但并非最不重要的一点:Xcode只需打开它们即可更改Interface Builder文件。 有时,稍加修改就会导致文件的大量更改(这是因为文件的XML节点在内存中表示为一个集合 ,而集合不能保证顺序)。 其他时候,Xcode只是为了好玩而更改.xib的语法。 紧密耦合,几乎没有编译时间安全 您是否曾经运行过您的应用程序,并确信它会按预期运行,只是在您打开某个视图控制器后看到它崩溃了? 在检查了崩溃的确切原因之后,发现这是未连接的IBOutlet吗? 是的,我们都去过那里。 此外,Interface Builder文件更难重用,甚至更难重构。 几乎不可能单看这样的文件并对文件定义的所有内容都一清二楚。 当涉及到Interface Builder的问题时,我几乎看到了所有事情。 从可能突然使Xcode崩溃的文件仅从打开某个文件到可以使用不同xib实例化的视图控制器开始,并且当使用这些xib中的一个实例化实例时,可能导致崩溃,因为等待它,因为IBOutlet没有该xib上的对应元素,并且由于Xcode生成IBOutlets作为隐式解包的可选变量,因此任何尝试访问该变量的尝试都会导致应用崩溃。 拖拉东西不是我的强项 有一些小事情使通过“界面生成器”添加约束的任务变得异常艰巨。 就像必须移动到下一个布局约束字段一样,以便当我单击“ 添加约束” ,Xcode打开单个Storyboard所花费的时间,或者正在编辑某些内容并使Xcode崩溃时,Xcode不会忽略我的约束。 (然后必须重新打开该项目,并找出最后保存的更改是什么)。 当用代码完成UI工作时,更容易准确地查明发生了什么或需要更改一年前编写的代码中要实现该新功能的内容。 特别是,如果代码是出于良好实践的考虑而编写的,那么所有内容都是明确的,我将确切地知道在哪里查找。 这使我想起了我使用Python多年的乐趣:显式比隐式好。 但是编写自动布局代码是如此冗长而乏味…… 有一些框架可以消除编写自动布局代码的痛苦。 我选择的框架是制图学。 制图使编写自动布局代码极其简单和直观。 只需从项目的自述文件中查看以下示例: constrain(view) { view in view.width == 100 view.height == 100 […]

使用Swift探索视觉格式语言

几周前,我开始介绍如何在Objective-C和Swift中使用自动布局的想法,以确保我的应用在所有iOS设备上都能正确显示。 您可以在Interface Builder(IB)中创建约束或以编程方式构建约束。 具有视觉设计的背景,我立即着迷于IB,因为它使我想起了许多设计程序。 实际情况似乎是,构建程序约束的时间往往会被可能与您的项目一起工作的其他人重用和可读性更高。 因此,我很想知道是否可以通过某种方式在视觉上构造程序约束。 使用视觉格式语言输入约束。 视觉格式语言 视觉格式语言允许您使用视觉语法字符串构建程序约束。 想法是文本在视觉上与布局匹配。 这是摘录的Apple文档: 完整的布局线 H:|-[查找]-[findNext]-[findField(> = 20)]-| 让我们分解一下: H :(水平)//水平方向 | (管道)// superview -(破折号)//标准间距(通常为8点) [](方括号)//对象名称(uilabel,unbutton,uiview等) ()(括号)//对象的大小 因此,如果我们要说一句话,我们会说findButton是距超级视图的开头(左边缘)的填充点(空白)为8点,而距findNextButton的开头的填充点为8点。 findNextButton是距findField的开头的8个填充点。 最后,findField的大小至少为20点,并且距超级视图的尾部(右侧)有8个填充点。 上面的代码特别有趣的是,它同时处理(设置)对findButton,findNextButton和findField的所有水平约束。 这里发生了很多魔术,所以我将尽力向您介绍如何在模拟项目中实现此功能的基本示例。 在一个快速的项目中,我们需要向您的超级视图添加一些对象,我们也可以附加约束。 我还喜欢为您的对象添加背景色,以便在测试约束时可以看到它占用的空间: 覆盖func viewDidLoad(){ super.viewDidLoad() //创建四个视图对象以表示我们的超级视图中的元素 让topBar = UIView() 让middleFrameTop = UIView() 让middleFrameBottom = UIView() 让bottomBar = UIView() //设置这些子视图的背景颜色 topBar.backgroundColor = UIColor.grayColor() middleFrameTop.backgroundColor = UIColor.orangeColor() middleFrameBottom.backgroundColor […]

使用NSKeyedArchiver持久化数据(Swift 3)

Apple提供了两种在应用程序启动之间保留数据的方法:Core Data和NSKeyedArchiver。 NSKeyedArchiver编码(保存)和解码(检索)您要持久保存的所有与NSCoding兼容的类。 尽管NSKeyedArchiver不如Core Data健壮(它速度较慢且手动),但它可以完成所需的持久化数据工作,并且不如Core Data复杂。 NSCoding是一种协议,它需要两种方法- 必需的init(编码器解码器:NSCoder)和encode(使用编码器:NSCoder) 。 如果我创建一个符合NSObject和NSCoder的类,则可以将该类序列化(从其当前数据结构转换为可以存储为字节的格式),并反序列化(从字节提取为数据结构)为可以保存到用户磁盘。 为了演示,我将创建一个购物清单应用程序。 步骤1:在情节提要中创建一些UI。 对于此应用程序,我使用了标签栏控制器。 第一个选项卡Add New Item包含一个标签,一个textField和一个按钮。 第二个选项卡Shopping Shopping只是一个tableView,显示了第一个选项卡中我添加的项目。 步骤2:创建我的模型对象和一个单例对象作为我的共享DataStore。 我的对象将是ShoppingItems,现在,每个ShoppingItem都有一个属性:name。 我创建了一个名为ShoppingList的类,并采用了NSObject和NSCoding协议,这将使该类与NSKeyedArchiver兼容。 我创建了1)我的ShoppingList的name属性,2)我的ShoppingList的初始化程序,以及3)创建了一个结构,该结构包含一个静态名称属性来保存我的NSCoding键(为安全起见)。 NSCoder需要两种方法,解码所需的init和编码器进行编码。 encode(使用coder:NSCoder)方法将保存(编码)我创建的Key.name的name属性。 所需的init(coder encoder:NSCoder)方法将检索我保存的名称对象并将其转换为字符串。 我还为我的name属性创建了一个getter / setter,以确保使用加载的newValue更新它。 我还使用单例作为“数据存储”,以便在我的shoppingList数组中拥有一个位置。 步骤3:为我的文本框和按钮创建一个插座和操作。 这些是必需的,以便在单击按钮后我可以在textField中检索输入的文本,并将该文本另存为新的shoppingItem。 第4步:创建一个代表filePath的变量,该变量将指定我们应在何处保存此数据。 在具有textField和按钮的viewController中,创建一个filePath属性。 此filePath属性应创建一个FileManager (如手机上的文件柜文件和文件夹)。 它还应该在我们documentDirectory中 FileManager的url数组中检索一个url。 在这里,我检索了第一个filePath。 filePath属性应该返回该URL,并附加您指定的路径,我使用了“数据”。 var filePath:字符串{ // 1-管理员可让您检查应用程序中文件和文件夹的内容; 创建一个目录到我们要保存的目录 让经理= FileManager.default // 2-这从我们的documentDirectory返回一个url数组,我们采用第一个路径 让url = manager.urls(for:.documentDirectory,in:.userDomainMask).first print(“这是documentDirectory \(url)中的url路径”) // […]

Swift 3中的Singleton

什么是单例? Singleton是一种设计模式,在该模式下,一个对象只能在您的应用程序中实例化一次。 该对象只有一个副本,并且由任何其他对象全局共享和使用。 创建单例的唯一目的是允许您访问它的共享属性和方法,并使它可以全局访问。 当您只想创建一个对象的实例时,此设计模式很有用。 让我们看一些我们熟悉但完全是单例的示例: sharedURLSession defaultFileManager standardUserDefaults 实现单例 单例类{ 静态让sharedInstance = Singleton() } 这一行代码是实现单例的全部内容。 我们将static放进去是因为它是一种type属性,它的功能是仅创建一个对象的实例,并防止其方法被覆盖。 使用let将保证sharedInstance的值不会改变。 更好的方法 最终课程Singleton { 静态让sharedInstance = Singleton() 私人init(){} } 我们第一次如何实现单例存在一些问题。 首先,我们需要确保不能使用final关键字修改单例或子类。 其次,我们需要防止其他对象通过使用private init创建自己的singleton类实例。 如果我们不使用私有初始化器,则singleton类将创建它自己的默认公共初始化器。 需要注意的重要一点是, static属性和方法默认情况下是延迟初始化的,这意味着直到调用它之前都不会实例化它,因此提供了一些优化。

将Swift编译时安全变成用户安全

发生了什么? 从技术上讲,今天没有任何事情可以阻止您作为开发人员在未经用户确认的情况下无意或无意进行不可逆的更改(例如删除用户数据)。 当然,我们尝试尽可能地减轻这种风险,在各处编写UIAlertController代码(或者为此目的甚至制作基于可爱的便捷闭包的函数),但是我们编写的API并没有采取任何措施来防止这种情况的发生。 例如,假设我们有一个类Images ,它直接注入到我们的视图控制器中 最终课程图片{ 私人var图片:[UIImage] func图片(位于索引处:Int)-> UIImage { 返回图片[索引] } func add(_ image:UIImage){ images.append(image) } func delete(imageAt index:Int){ images.remove(at:index) } } 当用户点击“删除”按钮时,我们可以轻松地编写以下代码: @objc func didPressDeleteButton(sender:UIButton){ images.delete(imageAt:currentImageIndex) } 没有编译时错误(很明显),没有警告,什么都没有-因此,我们只是删除了一部分用户数据而没有任何确认。 这是不好的。 常规的解决方案是什么? 大多数时候,开发人员会尝试在视图层解决此问题,也就是在didPressDeleteButton UIAlertController那里创建并显示didPressDeleteButton 。 当然,这通常可以解决问题,但是它有明显的缺点: 开发人员可以忘记在某些地方编写此“安全措施”代码。 尽管如此,开发人员仍然可以不小心删除图像,而不是在视图层,而是在业务逻辑中的某个位置删除图像,而无需三思而后行。 那么什么是“正确”的解决方案? 我们真正想要的是我们的Images类,根本不允许未经用户确认而执行删除操作。 为了帮助我们实现该功能,让我们创建一个UserConfirmationRequired结构: struct UserConfirmationRequired { 私人让performDestructiveAction:()->() init(destructiveAction:@escaping()->()){ self.performDestructiveAction =破坏性动作 } func performWithUserConfirmation(alertTitle:字符串,alertMessage:字符串,alertDestructiveActionTitle:字符串,完成:@转义(Bool)->()){ //检索视图控制器以显示警报 保护让窗口= UIApplication.shared.delegate?.window else […]

快照XCUI测试

本教程向您展示如何将流行的测试库iOSSnapshotTestCase与XCUITests一起使用。 如果您已经知道如何创建项目并安装一些Pod,请随时跳过。 设置项目 然后在项目的根目录中,运行pod init 。 这将在项目的根目录中创建一个Podfile。 打开它,然后将pod ‘iOSSnapshotTestCase’添加到Test和UITest目标,如下所示: 然后运行pod install 。 这会将所需的文件添加到这些目标。 接下来,您将要编辑方案以添加必要的环境变量。 您可以从iOSSnapshotTestCase存储库中复制粘贴的前两个,除路径的最后一部分外,第三个将相同。 FB_REFERENCE_IMAGE_DIR是该工具将存储参考图像的位置。 该工具将在IMAGE_DIFF_DIR中存储差异以检查故障。 FAILED_UI_TEST_DIR是我们的自定义目录,我们将在其中存储XCUITest故障的快照 添加一个XCUITest 现在我们需要测试。 在Main.storyboard添加带有一些文本的UILabel 。 还要添加一个UIButton ,以一种不同的感觉与新的UIViewController结合。 例如: 在预先生成的SnapshotUITestingUITests ,将生成的代码替换为: 很好 如果将recordMode切换为false(或删除该行),则下一次运行会将屏幕截图与刚拍摄的截图进行比较。 在项目目录中找到存储的图像应该没有问题。 解决状态栏问题 我将为您省去发现的旅程,只告诉您有问题。 以这种方式做事需要整个模拟器的屏幕截图。 不用担心 我们可以解决这个问题。 将新文件添加到UITest目标,并将其UIImageExtensions 。 添加以下代码。 结论: 尚未使用太多,但看起来确实很有希望。 让我知道您的想法,如果您最终使用它! 您可以在这里找到完成的项目:https://github.com/joesus/SnapshotUITesting