2018年4月13日 这是用户的常见要求……“我有iPad和iPhone,如何将我的收藏夹保存在一个设备上,让它们出现在另一台设备上?” 如果用户必须创建一个帐户才能使用您的应用,则可以将此功能内置到您的后端中。 但是,有时候,我们不想强迫用户创建帐户来使用我们的应用程序。 在这种情况下,有一个简单的解决方案,只需几行代码即可跨设备同步用户数据。 这是iCloud键值存储。 它就像本地词典一样工作,对于每个iCloud用户而言都是唯一的,并且会自动在该用户所属的每个设备之间进行同步。 如果您使用userDefaults存储用户数据,那么您已经到了一半。 iCloud键值存储的工作方式非常相似,其附加好处是数据可以在应用删除后继续存在。 假设有两个设备的用户下载了您的应用,他们将生日输入到一个设备中并保存了。 您要将这些数据存储到iCloud键值存储中,以便当他们在另一台设备上打开您的应用程序时,生日将已经自动存在。 这是做什么的… 在Xcode中选择您的项目目标,然后打开“功能”部分。 激活iCloud并确保选中“键值存储”。 首先,创建一个keyValueStore。 您可以根据需要创建任意数量的字典,每个字典实际上都是不同的字典。 我通常创建一个全局实例,然后可以从任何类中调用它。 var keyStore = NSUbiquitousKeyValueStore() 将数据添加到您的keyStore keyStore.set(“03/04/1989”, forKey: “userBirthdate”) 检索应用程序新实例上的数据 if let storedUserBirthdate = keyStore.string(forKey: “userBirthdate”) { //Use retrieved birthdate } else { //Ask user for birthdate } 到目前为止,keyStore的行为与UserDefaults完全相同,即数据将仅存储在本地。 为了允许所有iCloud用户的设备访问数据,您需要通过以下方式进行同步: keyStore.synchronize() 真的就是这么简单。 要记住的一件事是同步不一定立即发生。 正如Apple Docs所说:同步发生在“适当的稍后时间”。 iCloud键值存储旨在同步不需要持续和超快速更新的数据。 用户设置和偏好设置之类的东西。 最初于 2018 […]
Swift 4.1已有几天推出,并且具有一个有趣的功能: 条件一致性 。 这是用于实现面向协议的编程以及API设计的新工具。 这篇文章是对这种崭新技术的快速介绍,并且肯定会在接下来的几周内进行深入探讨。 和往常一样,您可以在我的个人博客上找到原始帖子。 借助Swift,您可能会对实现扩展的所有不同方式感到困惑。 随着语言的成熟,出现了新的扩展可能性,开发人员必须选择合适的语言。 基本上,扩展允许扩展类型的行为而不必将其子类化: 进行实用程序功能或实现有效的工厂模式很有用。 对专用于泛型类型的基本扩展进行了改进: 例如,将sum()函数应用于Bool数组可能没有意义。 这种扩展允许开发人员将新功能引入现有类型,但使它们可以安全地同时使用。 当您设计供其他开发人员使用的API时,这非常有用。 一旦满足良好要求,他们就可以一次“免费”地受益于某些代码。 扩展不仅可以将行为添加到现有类型,还可以修改类型本身以使其符合协议。 这在存在异质性的地方带来了一致性。 如果不使String , Ints和Optionals符合相同的协议,就不可能将它们混合在一起。 但是,尽管Array的每个元素都是“ Resettable ”,但我们仍然必须对其进行解析(使用map )并在每个元素上调用“ reset() ”以实现全局操作。 有条件的一致性将大大帮助希望重置整个Array的开发人员无缝地执行此操作。 到目前为止,我们还没有谈到面向协议的编程。 该范例具有向协议添加默认行为的能力。 符合此协议的类型也将有益于此默认行为(或覆盖它)。 这是一种特殊的扩展名,并不是本文的重点。 因此,在这里我们不要引入过多的噪音,而只关注扩展具体类型的能力。 条件一致性是Swift 4.1随附的全新功能。 它是条件扩展和一致性扩展之间的混合。 如此,它继承了它们的两个关键原则: 带来了新功能,但使它们可以安全同时使用 在存在异质性的地方带来一致性 实际上,其背后的主要思想之一是:如果可以将行为应用于超集的每个元素,那么我们可以考虑将该行为也应用于超集本身: 由于Array的每个元素都是Resettable ,因此整个Array也是。 例如,这正是苹果公司对Equatable和Hashable所做的事情。 使用Swift 4.1时,等于数组的元素数组是等于数组的。 在我们的示例中,最大的功能是,我们可以在根Array中嵌入一个Resettables数组,并且仅使用一条语句“ resettableArray.reset() ”,即可重置整个数据结构。 这是处理递归的一种非常优雅的方法。 如我们所见,就API设计而言,与基本的“ 一致性扩展 ”相比,使用“ 条件一致性 ”具有很大的优势。 API设计人员将在他的框架中内部化他认为很聪明的代码(此处为reset()函数),以免费提供给符合相应要求的开发人员。 […]
软件体系结构是关于做出基本的结构选择,一旦实施,更改成本很高。 每个软件开发项目都经历多个阶段:概念,设计,开发,测试等等。 作为一名初级开发人员,对我而言显而易见的是,在开始编写任何代码之前,至关重要的是,全面规划应用程序的体系结构基础,以确保您的应用程序从一开始就具有模块化,稳定和可扩展的特性。 架构设计有助于在项目中尽早发现潜在问题,以便有机会在施工开始之前进行更改。 有效的架构模式可提高代码的可读性,因为它们需要考虑直到以后才变得可见的问题。 什么是建筑设计模式? 设计模式是针对软件设计中常见问题的可重用解决方案。 它们是旨在帮助我们编写易于理解,易于测试和易于重用的代码的模板。 设计模式有助于创建松耦合的代码,以便以后可以在代码中轻松更改或替换其组件。 使用设计模式构建应用程序确实可以为您带来回报。 开发人员非常了解我们的应用程序会发生变化:您可能想添加新功能,可能需要修复一些错误,您的雇主可能会要求您在视图控制器上快速更改配色方案,或者您可能需要更新您的代码以与新版本的iOS软件或新设备保持兼容。 架构设计模式是使您的应用程序能够应对这些更改的准则。 现在我们已经回答了“ 为什么? 让我们来看看“ 如何? ” iOS设计模式: iOS开发最常见的设计模式是: MVC , MPV , MVVN和Viper 。 我将仅介绍MVC和MVVN,因为这些是我目前最感兴趣的设计模式。 在此博客中,我将重点关注MVC,接下来是关于MVVN的后续博客。 什么是MVC? 模型视图控制器(MVC)由Trygve Reenskaug于1979年发明。它是iOS开发中最常见的面向对象设计模式。 MVC可以非常清楚地分离应用程序的数据-逻辑,视图和控制器。 MVC根据对象的一般角色对它们进行分类,并鼓励根据每个角色对代码进行清晰的分离。 每个对象都属于以下组之一: 该模型负责数据和逻辑:诸如持久性,模型对象,解析器和网络代码之类的内容都存放在这里。 视图负责负责模型的可视化表示的对象以及用户可以与之交互的控件; 想任何以“ UI”前缀开头的东西。 Controller充当应用程序的视图对象及其模型对象之间的中介。 控制器编排查看事件和模型更改。 现在,我们了解了MVC如何管理这三个阵营之间的通信,我们的工作是根据每个对象所扮演的角色来明确区分代码。 遵守MVC设计模式需要不断关注对象的位置,以避免潜在的陷阱。 以下是一些需要牢记的准则: 控制器可以与之对话并了解有关该模型的所有信息。 控制器的工作是从模型中获取所需信息以显示给用户。 控制器还可以通过我们在控制器中创建的插座直接与视图对话。 视图只是控制器的奴才 -保持视图简单! 模特和模特 永远都不要说话。 MVC的缺点: 设计模式没有万灵药。 在MVC中,视图和控制器紧密耦合。 iOS视图控制器类通常将同时包含UI逻辑和数据逻辑,这将使一个逻辑的修改影响另一个逻辑-创建MVC的一种“ M / […]
与Objective-C相比, Swift增加了语言功能,使开发人员的代码更安全 , 更快 , 更可靠 , 更可预测 。 1创建一个Swift文件来管理您的样式 设置外观属性的最简洁方法: 为您创建的每个标签或按钮设置颜色和字体 ,只需创建一个扩展某些类的Swift文件。 您可以在两分钟内更改应用程序的样式。 🎨 使用静态的计算变量。 Swift 3允许我们使用没有任何初始化程序的变量。 // Colors.swift 扩展UIColor { 静态var buttonColor:UIColor { 返回UIColor(红色:0.50,绿色:0.50,蓝色:0.50,alpha:1) } 静态var bodyColor:UIColor { 返回UIColor(红色:0.30,绿色:0.0,蓝色:0.30,alpha:1) } 静态var backgroundColor:UIColor { 返回UIColor(红色:0.00,绿色:0.7,蓝色:0.40,alpha:1) } } // FontStyles.swift 扩展UIFont { 静态var bodyFont:UIFont { 返回UIFont(名称:“ Helvetica”,大小:14) } 静态var titleFont:UIFont { 返回UIFont(名称:“ Helvetica-Bold”,大小:18) } } […]
我们如何在Stuart为我们的本机iOS应用程序运行自动化的UI测试。 每个厨师都有自己的食谱。 苹果派可以用多种样式制作,每种都可以用自己的方式美味……但是,有一个我们最喜欢的特殊食谱。 让我向您展示质量保证团队为在我们的iOS应用上执行UI测试而构建的Stuart测试框架配方。 我将通过苹果 , 烤锡和我们选择的面粉与您交谈。 苹果 我要谈的第一个要素是测试框架,即我们的“苹果”。 有几个选项,例如Appium,EarlGrey,Calabash,但我们选择了XCTest。 但为什么? 这是苹果公司支持的框架(我们知道他们正在努力使它年复一年地变得更好); 它将完全集成在应用程序代码中。 这为我们的iOS开发人员添加/修复测试打开了一扇门,并且是一种鼓励他们(当然,是质量检查小组!)为同一功能请求中的每个功能或错误修正添加测试的方式。 最后但并非最不重要的一点是,我们可以在UI测试和单元测试之间共享模拟,这使得编写新测试非常顺利。 烤锡 选择合适的苹果后,我们要使馅饼变大。 当我切成薄片时,我个人讨厌它,它会破裂,因此Stuart Apple蛋糕将不得不抵制所做的更改并使外观保持惊人。 在开始做任何事情之前,我们必须先喘口气,然后想想在我们的项目中遵循哪种架构。 我们希望在使代码可重用且易于维护的同时保持简单性。 为了满足这些需求,页面对象模式将用于对应用程序的屏幕进行建模。 我们还将使用机器人模式,以将与应用程序的所有交互封装在一个地方。 测试将使用屏幕与应用程序进行交互,屏幕将使用机器人执行操作(即查找元素,获取文本,点击,滑动…)。 此外,我们创建了一个不同的应用程序目标,以使应用程序与测试脱钩。 这使我们可以在应用程序目标(和其他一些文件)中创建模拟,以定义这些模拟的默认值。 例如,我们有一个模拟客户端(在具有应用程序目标的文件中定义),将具有firstName John和lastName Doe。 这些变量在两个目标都共享的文件中定义,并且仅包含静态数据,因此即使我们要在测试目标中执行断言(甚至使用它构建模拟),我们也可以引用该数据。 面粉 馅饼几乎已经准备好放入烤箱了,但是我们错过了最后一个(也许是最重要的)配料:面粉。 普通面粉可以胜任,但优质面粉可以发挥作用。 我说“面粉”是什么意思? 我们不想通过我们的测试打入网络! 这些测试的目的是检查我们的应用程序的UI在特定的交互后是否以特定的方式运行。 如果由于API返回有效值但我们期望有所不同而导致测试失败,该怎么办? 如果网络请求花费太长时间怎么办? 测试可能会失败。 我们不希望每次由于网络请求而导致测试失败或(或者更糟)习惯于失败的测试时都收到烦人的通知。 我们对可以使我们实现该目标的内容进行了一些研究。 我们发现有趣的选项是OHHTTPStubs,Embassy / Ambassador,MockServer。 每个人都有优点和缺点,但是……我们的最终决定不是所有这些! 主要原因是我们不想在项目中添加外部依赖关系和/或维护大量API响应。 因此,我们使用了不同的方法。 让我们一步一步走。 我们要确保我们的APIClient不会影响网络。 让我们在APIClient中添加几行代码来完成工作并初始化我们的URLSession: 但是……我们如何知道测试是否为UITest以及MockUrlSession是什么 ? 请耐心等待我的朋友,细节来了! 为了让该应用知道我们是否正在运行测试,让我们使用Apple提供给我们的一个很棒的功能:launchArgument和launchEnvironment。 在每次测试执行之前,我们将启动一个参数,指示我们正在运行测试。 […]
介绍: 要成为iOS开发人员,我们首先需要明确的概念和一些主题。今天,我列出了这些主题和有关这些主题的简短描述。希望它将帮助您简化旅程。 Xcode: 首先,我们需要一个IDE来开发Apps。Apple为MacOS,iOS和所有其他Apple Flat开发平台构建了这个出色的IDE。因此,在我们开始开发之前,我们需要知道如何使用此IDE。这是有关Xcode的有用文章: 面向初学者的Xcode教程(已针对Xcode 8.2更新) 本教程面向初学者,了解如何使用Xcode 8构建应用程序。 这个Xcode教程还将贯穿所有…… codewithchris.com 语言-Swift或Objective C: 苹果有用于开发iOS Apps Objective C和Swift的语言。 我个人更喜欢Swift,但是您对目标C有基本的了解。 斯威夫特: 一些主题我们需要有清晰的概念。 这些是 关闭 协议 泛型 类和结构 延期 财产观察员 这是使用Swift语言的Apple官方文档: Swift.org Swift是一种通用的编程语言,使用安全性,性能和软件的现代方法构建而成…… swift.org UIDesign: 要构建应用程序,首先必须设计应用程序用户界面,例如Storyboard,Xib或Programmatically。 因此,为此,我们需要了解Xcode提供的组件或自定义组件的实现及其功能。我们需要通过故事板上的自动布局来固定这些组件的位置,或者通过编程来添加约束。 有三种方法可以在应用程序屏幕上添加组件以设计用户界面。 通过 故事板 Xib文件 以编程方式 我们可以通过 自动版面 添加约束 UIComponent: 苹果为开发人员提供了一些非常重要的组件,每个按钮,按钮,标签,文本字段等应用程序都需要它。 在这里,我列出了每个应用程序中需要的这些组件。 纽扣 标签 文本字段和文本视图 滑杆 tableView collectionView 视图 应用程序生命周期: iOS应用程序具有生命周期。iOS应用程序有5种状态:未运行,不活动,活动,后台和已暂停。 应用程序如何以及何时经历这五个状态称为应用程序生命周期。 […]
上周,我谈到了我如何发现Swift是服务器端开发的绝佳选择。 本周,我将讨论平台的选择。 顶篷在这里可用。 首先,我想重申我的承诺:只要我的Patreon目标得以实现,就可以将Canopy的客户端和服务器端开源。 我想成为一名全职的开源开发人员,Canopy的货币化是我想要到达那里的一种方式,但是如果我不需要,我宁愿将自己的工作交给社区。 需要的顶篷: 具有SSL功能的Web服务器 与苹果的推送通知服务进行通信的APNs引擎 数据库 通用密码功能 我考虑的选项是: 完善 汽 Kitura 重新发明轮子 我选择了完美。 让我们谈一谈。 重新发明轮子 网络服务器并不复杂。 但是边缘情况是 。 我并不反对重新发明轮子,通常这很有趣,而且在此过程中您经常学到很多东西。 为我自己动手的最好理由是,当出现问题时,您会知道代码,因此更容易解决问题。 如果黑匣子修复出现问题,您自己可能会很难。 不过,我从未真正考虑过Canopy的这种选择:我想构建一个强大而可靠的解决方案; 制作自己的Web服务器肯定会把我咬死,而且肯定会在生产过程中最糟糕的时间发生。 因此,我的决定将归结为: 堆栈的成熟度 博客中的大量材料,平台文档以及现有stackoverflow答案的相对有用性 成熟的堆栈是希望已经稳定并且相对没有错误的堆栈。 堆栈在GitHub上具有良好的开发人员影响力,这是我遇到问题时可以希望得到帮助的堆栈。 关于堆栈的大量博客文章等表明其他人已经进行了测试,该堆栈可能在现实世界中使用。 大量的问题和至关重要的答案 ,StackOverflow表示我会在开发过程中以及生产中遇到问题时找到帮助。 完善 Perfect是从Java移植到Swift的成熟解决方案,已经存在了很多年。 我在网上发现了很多信息 汽 蒸气是纯Swift解决方案。 这直接关系到更轻松的构建系统寿命。 任何对依赖图有经验的人都熟悉错误的位是“本机”扩展这一事实。 该工具必须与C库或非该语言的其他库集成的地方。 正如我上周所说,相对于例如,这在Swift中是相当轻松的。 红宝石。 我仍然无法在Mojave上构建一堆gem,因为它们需要libxml或其他任何东西,并且Mojave移到了各种C标头所在的位置。 但是蒸气是相当新的并且处于助焊剂状态。 但是我喜欢Vapor是一个真正充满活力的开源项目,积极而热情地维护着。 它还具有一个美丽且非常周到的API。 Kitura Kitura得到了IBM强大力量的支持,他们自己使用它,人们为此而付出了很多报酬。 自Swift发行以来,IBM一直以非常鼓舞人心的方式致力于Swift。 但是我并不热衷于可用的API,而Kitura大约和Vapor一样成熟。 为什么我选择完美 所有解决方案的性能都非常好。 因此,这并不是主要问题(尽管Perfect赢得了链接比较)。 […]
当我们开始构建iOS应用程序时,我们将面临与视图布局及其内容有关的几个问题。 通常,这些问题是在缺少有关视图实际更新时间的知识时发生的。 了解视图更新的方式和时间要求我们必须了解iOS应用程序的主运行循环 ,以及它与UIView提供的某些方法之间的关系。 iOS应用主运行循环 iOS应用程序的主运行循环处理所有用户输入事件,并触发应用程序中的相应响应。 与应用程序的任何用户交互都将添加到事件队列中 。 应用程序对象将事件从队列中移出并将它们分派到应用程序中的相应对象。 它实质上是通过解释来自用户的输入事件并为应用程序的核心对象中的输入调用相应的处理程序来执行运行循环。 这些处理程序调用由应用程序开发人员编写的代码。 这些方法调用返回后,控制权返回主运行循环, 更新周期开始。 更新周期负责布局和重绘视图。 UIViewController布局周期 ViewDidLoad() 该类已加载,但单个元素(子视图)不可操作,并且约束尚未完全解决。 调用此方法后,将创建视图控制器的视图,并确保所有插座均已就位。 此方法在视图控制器的生命周期中仅被调用一次,因此您可以将它用于只需要发生一次的事情。 ViewWillAppear () 该视图将出现,但到目前为止尚未将其添加到视图层次结构中。 在视图出现之前立即调用,非常适合于隐藏/显示字段或您希望在视图可见之前每次进行的任何操作。 因为您可能会在视图之间来回移动,所以每次视图将出现在屏幕上时都会调用此方法。 ViewDidAppear() 该视图已添加到视图层次结构中。 解决了所有限制(您可以在屏幕上看到结果)。 ViewDidLayoutSubviews() 每当更新,旋转或更改视图或更改边界时,都会调用viewDidLayoutSubviews 。 需要注意的一点是,仅在对视图应用了所有自动 布局或自动调整大小的计算之后,才调用此方法。 简而言之,每次视图大小更改并且重新计算视图布局时,都会调用viewDidLayoutSubviews 。 ViewWillDisappear() 与viewWillAppear相似,此方法在视图从屏幕消失之前被调用。 与viewWillAppear一样,可以在视图控制器对象的生存期内多次调用此方法。 当用户离开屏幕导航时被调用。 ViewDidDisappear() 从屏幕中删除视图控制器后,将调用此方法。 通常,您可以重写此方法以停止视图控制器不在屏幕上时不应运行的任务。 例如,您可以停止收听通知,观察其他对象的属性或不再需要的网络呼叫。 Deinit() 像其他所有对象一样,在将视图控制器从内存中删除之前,它会被初始化。 通常,您可以重写deinit()来清除视图控制器已分配的资源,但这些资源不会被ARC释放。 您还可以停止以前的方法中未停止的任务,因为您希望将它们保留在后台。 注意 :如果视图控制器离开屏幕(在导航堆栈中按下),并不表示之后会释放它。 导航堆栈中所有以前的视图控制器都保留在内存中。 导航控制器仅在向上导航层次结构(弹出视图)时才释放视图控制器。 因此,您必须记住,不在屏幕上的视图控制器仍然可以正常工作并接收通知。 查看布局 UIView layoutSubviews方法处理视图及其所有子视图的重新定位和调整大小。 系统在需要重新计算视图框架时会调用此方法,因此,当您要设置框架并指定位置和大小时,应覆盖此方法。 […]
iMessage应用程序是Apple使用iOS设备推动更多对话的最新尝试。 仅需少量代码,您就可以创建扩展,以将自定义逻辑和交互添加到Apple的iMessage应用。 在本文中,我将向您展示如何创建一个简单的iMessage应用程序,该应用程序可让您在iMessage对话中与朋友评价图像。 消息框架 Messages Framework允许您扩展iMessages来创建两种类型的扩展名:贴纸包和iMessage应用程序。 Apple允许您将扩展程序创建为独立应用程序或现有应用程序的扩展程序。 贴纸包 贴纸包是两个扩展中较容易创建的,因为它们不需要代码。 贴纸包只是包含图像的资产目录,用户可以将其附加到消息提示框上。 您可以在此处了解有关图像要求(尺寸,格式等)的更多信息。 iMessage应用程序 iMessage应用程序使您可以与iMessage应用程序进行更深入的集成。 iMessage扩展可以创建自定义UI,以在iMessages中呈现给用户。 您还可以为用户创建动态标签,或将富媒体类型插入对话。 iMessage应用程序可以利用MSMessage和MSConversation对象来创建交互式内容,甚至可以更新对话中的消息。 在创建iMessage应用程序时,Messages框架为您提供了很多控制。 本文的其余部分将重点介绍如何创建iMessage应用程序以及您需要了解的主要类。 立即评价 我们的示例iMessage应用程序称为Ratethisnow 。 我们的应用程序可让您对1到5星的图像进行评分,并与朋友分享您对图像的其他评论。 为了简化此发布的示例,我们将示例限制为只有2个用户,以便我们可以使用iPhone Simulator。 我在演示中还使用了其他一些东西来简化事情。 当它们出现时,我们将进行覆盖。 应用程式设定 启动Xcode 8并创建一个新项目,在iOS选项卡下选择iMessage Application模板。 命名您的项目ratethisnow 。 如果您只是想继续,可以从Github获得我的示例项目。 让我们快速看一下项目浏览器。 iMessage应用程序的模板包含一个入口点( MessagesViewController.swift )和一个故事板( Maininterface.storyboard ),我们可以为其添加自定义视图。 单击MessagesViewController并查看内容。 我们的swift类继承自MSMessagesAppViewController,并且覆盖了其几个类。 MSMessagesAppViewController是iMessage扩展的主要类。 您需要重写子类中的方法才能与iMessage进行交互。 您可以在此处了解有关文档中可用方法的更多信息。 您还可以在“应用程序扩展编程指南”中获得应用程序扩展的更广泛概述。 模型 我们的应用程序允许用户创建商品的评价并在对话中分享。 创建一个新的名为RTNItem的 swift类作为我们的模型对象。 我们需要存储标题,图像和用户评分。 如前所述,我们将采取一些捷径来使本文易于管理。 因此,我们将仅允许两个评估者,因为iPhone Simulator仅包含两个可用于测试的用户。 您当然可以将其更改为适合将来用途的系列。 接下来,我们需要配置两个Collection视图单元:一个用于创建按钮,另一个用于标签类型。 […]
也许有时候我们想使用UIScrollView来显示列表内容,而不是使用UIPagesController或UICollectionView 。 并且在这种情况下使用“自动布局”可以使声明布局更加明确。 看看Github上的示例项目https://github.com/onmyway133/archives/tree/master/ScrollViewAutolayout 这个想法是使用UIView作为UIScrollView的contentView来确定其contentSize 首先,固定UIScrollView的边缘 其次,固定contentView的边缘 对于contentView内部的视图,将约束水平固定,以便将最左侧的视图固定到contentView的左侧,将最右侧的视图固定到contentView的右侧。 对于高度,我们将contentView的顶部和右侧边缘固定到其父级,即UIScrollView 参考文献 https://developer.apple.com/library/content/technotes/tn2154/_index.html http://samwize.com/2014/03/14/how-to-use-uiscrollview-with-autolayout/ https://spin.atomicobject.com/2014/03/05/uiscrollview-autolayout-ios/