在深入研究代码之前,这里是对该模式的快速介绍。 MVVM代表模型,视图,视图模型,这是一种特定的体系结构,其中视图模型位于视图和模型之间,提供模仿UI组件的接口。 通过“绑定”值(将逻辑数据链接到UI)来建立此连接。 在iOS应用中,这种MVVM方法中的View元素通常是UIViewController本身。 这种方法的好处是代码变得更加模块化,易于维护和测试。 现在让我们进入代码。 模型 与api模型匹配,这是我的两个结构开始。 struct CurrencyRate { let currencyIso : String let rate : Double } struct Converter { let base : String let date : String let rates : [CurrencyRate] } 我还创建了一个特定的服务来创建一个api请求以及我的Converter实现的解析器协议。 您可以在该文章末尾的Github存储库中找到这些源代码。 这并不是真正与MVVM相关的,因此我将直接进入我们的ViewModel和绑定系统。 视图模型 首先,为了能够将ViewModel的值绑定到View,我们需要具有可观察模式的元素。 在iOS中,我们可以使用KVO模式添加和删除观察者,但我认为我们可以使用“ didSet”观察者做得更好。 让我们记住,我们要随着时间的推移执行特定的代码,而该代码已更改。 我们还可以假设我们的对象可以有多个等待更新的观察者。 为此,我创建了一个类,其中包含一个观察者及其更新时要执行的代码的字典(此处为闭包)。 在这里看起来像什么。 typealias CompletionHandler = (() -> Void) class DynamicValue […]
欢迎来到我的征服ReactiveSwift系列文章的第4部分。 在上一篇文章中,我们学习了如何创建和观察信号。 在本文中,我们将介绍SignalProducer的概念,它是Source类别下的重要原语。 定义 顾名思义, SignalProducer是产生Signal的东西。 基本上, SignalProducer封装了延迟且可重复的工作,这些工作在启动时会生成Signal 。 好吧,它有什么用? 请记住,在上一篇文章中,我们研究了以下问题陈述。 每隔五秒钟打印一次已过时间的消息。 我们创建了一个信号,该信号在接下来的50秒内每5秒发出一个整数。 然后,我们观察这些整数并打印经过的时间。 让我们假设,现在我们希望这从按钮轻按开始。 但是,作为观察者,我们只能观察信号,而不能使其开始或停止。 对于这种情况, SignalProducer非常适合。 因此,让我们开始吧! 我们将把整数发射代码封装在SignalProducer中。 //创建SignalProducer 让signalProducer:SignalProducer = SignalProducer {(观察者,生命周期)在 对于i in 0 .. <10 { DispatchQueue.main.asyncAfter(截止日期:.now()+ 5.0 * Double(i)){ viewer.send(值:i) 如果i == 9 {//标记第9次迭代完成 reader.sendCompleted() } } } } 在这里, SignalProducer用一个闭包初始化,该闭包在调用SignalProducer的 start方法时执行。 此闭包接受类型为Signal.Observer的观察者以及类型为Lifetime的生存期。 观察者用于发送值。 如果停止观察,则一生将为我们提供取消正在进行的工作的机会。 现在我们准备好了一个SignalProducer 。 让我们开始观察它。 //创建观察者 […]
在开发周期中,我花了大量时间进行网络调试。 在android中,我们有Stetho。 我可以在没有Android Studio的Chrome网络检查器上查看所有请求和响应。 在iOS开发中,我使用控制台日志或Charles,这需要在特定的macOS上进行一些设置(或许可)。 我已经在iOS开发上找到了Stetho等效的设备,但是它没有简单的解决方案。 我写AlamofireSafariLogger是因为我想更轻松地进行网络呼叫调试。 最终结果是,我记录了从Alamofire到Safari Web检查器的所有请求和响应。 与Stetho不同,我不是在“网络”选项卡中而是在“控制台”选项卡中登录。 最终结果如下所示。 设定 pod’AlamofireSafariLogger’ 在您的appDelegate中, func application(_ application:UIApplication,didFinishLaunchingWithOptions launchOptions:[UIApplicationLaunchOptionsKey:Any]?)->布尔{ //添加此startLogging()行 AlamofireSafariLogger.shared.startLogging() // … } 而已。 现在,您需要在Safari中启用开发菜单。 在Safari Perference>高级>选中“在菜单栏中显示开发菜单” 如果您要使用设备进行调试,请打开“设置”> Safari>“高级”>启用“ Web检查器”。 当您的应用程序运行时,您会在开发人员标签下找到“ about:blank”。 单击它,您现在可以使用Web检查器调试alamofire网络通话! 当前设计中的控制台日志级别: 标题:日志 身体:警告 错误:错误
UIKit的速度与iOS 7的发布速度无关。 电子地图的布局可能与iPhone的外观和外观有所不同。 在iOS平台上的最高性能,以下三种格式:故事板,Xib或ViewCode。 讨论高级质量问题的通告。 瓜尔·戈斯塔里亚·德·梅尔哈尔因不同的理由而存在,并以优胜劣汰的形式存在,并在ViewCode上被法院起诉。 使用Xcode可以使您的故事重新开始,使用Storyboard进行聊天,也可以使用其他人来进行聊天。 1.移除参考故事板的Info.plist 2.删除Main.storyboardcriéautoamente criado Ao tentar rodar o projeto agora vai tomar uma bela tela preta,poisnãohánenhum ViewController sendo inicializado🙁 Para consertar isso,vamos botar或ViewController.swift参数 Abra或AppDelegate.swift或方法_didFinishLaunchingWithOptions_ como abaixo: 集市,实用程序或ViewTable参数自定义UITableViewCell自定义视图,并自动布局视图。 Mais uma vantagem通过Storyboard通过String nas propriedades做ViewCodeéo fato dacélulanãoter o identificador。 服装的基本单元或协议的参数标识符和细胞高度 。 复制程序的简单格式适用于使用ViewController的类。 Essapráticadificulta os erros de escrita nas字符串pos utilizamos semper tipos definidos […]
增强现实(AR)允许用户体验现实世界中的虚拟对象。 苹果发布ARKit之后,为iOS构建AR应用程序变得很容易。 让我们考虑您要从源头到目的地旅行。 如果您可以看到一个在导航时显示方向的AR视图,而不是依靠2D应用程序,那将是多么令人兴奋。 可以使用带有ARKit的Google Maps SDK轻松构建此类应用程序,其中Google Direction Api在两个位置之间提供所需的方向信息。然后,这些方向信息用于在Google Maps和AR环境中绘制路线。 让我们看一下用于构建AR导航应用程序的框架。 2.1 Google Maps SDK Google Maps SDK会自动处理对Google Maps服务器的访问,地图显示以及对用户手势(例如单击和拖动)的响应。 它允许在地图上添加标记,折线,地面叠加层和信息窗口。 为了提出Google API请求,必须具有有效的API密钥。 2.2 ARKit ARKit框架允许开发高细节的增强现实体验。 在ARKit的帮助下开发的应用程序使用Visual Inertial Odometry(VIO)将虚拟对象放到了现实世界中。 在这里,ARKit框架用于开发实时导航应用程序,该应用程序通过将虚拟对象放置在增强现实场景中来显示从源到目的地的路径。 现在让我们跳入构建此应用程序必须实现的两个关键方面。 3.1。 在Google地图上的源和目标之间绘制路线。 Google Maps SDK支持在源位置和目标位置之间的地图上绘制概图折线。 源始终是用户的当前位置,该位置不可编辑。 用户只需提供其坐标即可选择任何目的地。 Google Directions Api会获取位置之间的路线。 它有助于根据区域搜索路线,指定不同的交通方式(驾驶,步行或骑自行车),不同的交通模式等。 确保您具有用于使用方向api的有效api密钥。 Google Direction URL: https://maps.googleapis.com/maps/api/directions/json?origin=\(source.latitude),\(source.longitude)&destination=\(destination.latitude),\(destination.longitude)&mode= \(travelMode)&key = \(ApiKey) 以下示例以JSON格式请求从旧机场路到Indiranagar的步行路线: https://maps.googleapis.com/maps/api/directions/json?origin=12.96023760508684,77.6436832043738&destination=12.9718915,77.6411545&mode=”walking”&key=ApiKey 获得的JSON响应包含以下信息: status :此字段指示响应是否包含有效数据。 如果Api无法获取结果,则此字段提供有助于跟踪的调试信息。 […]
以下是WWDC 2017(会议201)的大部分“可可接触新功能”会议的快速回顾。 这都是现场输入的内容,因此,如果某些内容不正确,请随时纠正我。 生产率 拖放 通过创建UIDragInteraction(UIInteraction的子类)并将其附加到您希望具有Draggable的视图(并分配其委托),来启用Drag。 委托人提供拖动项目的数据,允许自定义“提升”动画并自定义draggin预览。 通过创建UIDropInteraction并以类似方式设置其委托来启用Drop。 委托允许在拖动移动时更新UI,接收拖放上的常规数据并自定义拖放动画。 开箱即用地支持许多UIKit元素-TableView,CollectionView,TextView,TextField和WebView。 文件管理 引入了新的文件资源管理器,该文件资源管理器允许在我们的应用程序中进行演示。 UIDoumentBrowserViewController是我们可以在应用程序中显示的ViewController。 它是高度可定制的,并允许访问设备内容,iCloud内容和外部云服务。 协调文件访问至关重要,因为我们不能指望我们的应用程序是唯一可以触摸某些文件/资产的应用程序。 可以通过使用NSFileCoordinator / UIDocument来完成。 用户界面优化 大标题导航栏可以具有内置的搜索栏,但是当您向上滚动内容时,搜索栏将随着大标题折叠为常规小标题而折叠,就像我们习惯于iOS 10。 在大多数情况下,这几乎可以自动完成,因为系统可以通过将UINavigationBar.prefersLargeTitle设置为true来自动处理它。 您还可以使用UINavigationItem.largeTitleDisplayMode来围绕导航级别以更精细的方式控制大标题显示模式。 通过设置UINavigationBar.searchBarController启用统一搜索栏。 通常,导航控制器/堆栈中的顶部控制器将是唯一使用“大型”样式标题的控制器,而内部控制器应使用标准的小变化。 导航栏中有一个内置的Pull-to-refresh,看起来很棒。 我们不能一直指望导航栏的静态高度,因为它可能会更改不同内容类型的高度。 在大多数情况下,这将由各种UINavigation类自动处理。 UIView上有一个新的safeAreaInsets.top属性,我们可以准确而可靠地使用它来通过导航栏知道“模糊”区域的大小。 还有一个底线值。 ( safeAreaInsets.bottom ) 您也可以使用safeAreaLayoutGuide 。 对于手动布局,您可以手动阅读safeAreaInsets并订阅安全区域大小的更改。 现在, UINavigationController不再像使用安全插入一样控制插入值,因此在Scroll View上设置手动插入应该不再像以前那样费时了。 UIScrollView具有称为contentInsetAdjustmentBehavior和contentInsetAdjustmentBehavior新属性,可用于手动定义插图在UIScrollView上的行为。 UITableViewCell现在支持使用新的UIContextualAction类在左侧和右侧进行内置的滑动操作。 看起来与UITableRowAction工作原理类似,但是更加精致,因为您可以单独使用UISwipeActionsConfiguration对这些UIContextualAction进行分组。 UITableView上有一个新的属性,称为“ separatorInsetReference ,可让您设置插图的值是绝对值还是“增量”,例如,在引用中,插图将是以前的插图。 斯威夫特4和基金会 归档Swift本机类型 新的Codable协议允许更轻松地“存档”和“取消存档”,并且默认情况下允许他们参与NSCoding。 关键路径 新的KeyPath类型具有用于文字语法的选项(例如\Object.path.subPath ),因此您可以动态创建一个密钥路径,然后使用collection[keyPath: someKeyPath] 。 使用object.obserer(keyPath) { […]
当我决定分析我的代码生成时,我在最新,最出色的项目上工作效率很高。 我的大多数方法都在6毫秒以下才能编译,但某些布局方法却需要20毫秒或20毫秒以上的事实,这让我感到非常恼火。 一旦删除了第三方布局框架,并构建了自己的优化解决方案,我就认为自己将恢复生产效率。 然后,我注意到我的图形被构建为PNG,并占用了不必要的空间。 那让我很生气。 一旦将肿的图形移动到矢量上,然后将这些矢量移动到代码上,并且一旦制定了自定义二进制矢量资产格式,以使我的图形不会减慢编译时间,那便是我决定终于可以恢复生产力。 在构建应用程序中的下一个屏幕时,每次在iPhone上运行该应用程序时,我都会不断看到相同的3秒进度条。 “复制领域框架”。 那让我很生气。 为什么浪费时间复制价值95 MB的框架? 因此,随着Realm进入斩波阶段,它将被Apple的数据库基础解决方案Core Data取代。 Core Data随附了一个漂亮的UI,该UI已直接集成到Xcode中,这使创建数据库模式变得容易。 不仅如此,您还可以添加映射模型,从而可以轻松地将架构迁移到新版本。 但是我很喜欢Realm的架构都是用代码定义的,所以我愚蠢地决定着手建立一个类似的解决方案。 有一些稀疏的文档可以在代码中定义模式,但是可以做到。 而我做到了。 没关系 然后我想添加迁移。 那是问题出现的时间。 我开始遇到各种有趣的错误。 您是否知道Google上没有“不支持的实体映射类型”的搜索结果? 我很遗憾。 我搜寻了和我一样处境的人们的互联网。 没有太多可找到的。 不仅如此,当我确实找到那些决定以这种方式使用Core Data的人的参考文献时,有很多人表达了自己的困惑。 出于好奇,用代码而不是用数据模型编辑器构建模型的背后原因是什么? […]如果是由于视力障碍[…] –困惑的CocoaBuilder.com用户[链接] 最终,我确实弄清楚了迁移。 部分归功于使用称为Hopper的有用调试工具检查了错误原因。 现在,我可以恢复生产力了。 因此,正如标题所述,不要以编程方式创建Core Data模型。 但是,如果您要采取任何方式,我的源代码可能会有所帮助。
如上图所示,从服务器解析JSON响应需要进行两次转换: 从原始HTTP响应转换为数据的JSON表示形式。 从JSON表示形式到模型。 在第一步和第二步之间,我们将使用数据的JSON表示形式。 这种JSON表示形式使我们能够对服务器响应进行增量转换。 通过在模型之前解析为中间JSON表示,我们的代码变得更加可组合。 JSON的表示方式对于理解本文中的功能编程概念并不是必不可少的。 但是,为了完整起见,让我们现在对其进行定义。 如果阅读JSONSerialization的文档,您会注意到有效JSON的规则之一是: 所有对象都是NSString , NSNumber , NSArray , NSDictionary或NSNull 。 由于案例数量有限,因此使用枚举似乎是一个好情况。 定义了JSONObject类型后,我们现在知道第一次转换之前,第一次与第二次转换之间以及第二次转换之后的数据类型。 它们是Data -> JSONObject > Model (我们稍后会定义模型。) 功能方法 有一个核心概念将通过此练习来推动我们的思考过程。 我们将过程中的每个步骤都视为独立于函数外部变量的转换 。 让我们看一下如何将这种思考过程应用于上面概述的两个转换。 转换1:将数据转换为JSON对象 我们将从定义一个新类型Deserialize开始,该类型将Data转换为JSONObject 。 在函数式编程中,类型由其方法签名定义。 要使用此类型,我们编写了一个函数,该函数返回新的Deserialize类型。 我想强调关于JSON()函数的几件事。 该函数返回一个闭包。 以面向对象的思维方式看待这个问题可能看起来很奇怪,但这是标准的函数式编程。 请注意,从JSON()返回的闭包完全独立于该函数外部可能存在的任何状态。 这与我们非常依赖状态的面向对象编程形成了鲜明的对比。 转换2:将JSON对象转换为模型 正如我们在第一个转换中所做的那样,让我们定义一个新类型,该类型接受一个JSONObject作为参数并返回类型T的模型: typealias Decode =(JSONObject?)->(T?) 注意:我们使 Decode 函数通用,因此我们可以解码各种模型。 由于我们尚未定义任何模型,因此这是我们开始从JSON创建模型所需的所有样板代码! 下一步是定义我们的模型以及可以解析JSON的函数。 全部放在一起 让我们导出一个简单的示例,以显示与该代码交互的外观。 对于此示例,我们将期望服务器向我们发送有关用户的信息。 我们将定义User模型,并编写一个函数decodeUser() ,以执行从JSONObject到User的转换。 […]
面向对象编程的标志之一是继承。 但是,协议并不完全像传统继承那样,传统的继承是一个类从另一类继承而来,但是有了协议,任何给定的类,结构或枚举都可以“符合”协议。 符合协议的类或结构的实例将能够执行协议中概述的任务或指令。 根据Apple的定义,协议就像蓝图,其中包含方法,属性或定义任务所需功能的任何其他要求。 一旦创建了协议,就可以被结构,类或枚举“采用”。 在任何给定的时间,类或结构可以采用多种协议,如以下示例所示: 协议将要求类,结构或枚举的instance属性或type属性具有名称和类型。 该协议不会区分是存储属性还是计算属性。 但是,该协议将定义其仅是可获取的,还是可获取和可设置的。 如果该属性既是可获取的又是可设置的,那么它将不能作为常量存储属性或只读计算属性。 如果该属性是可获取的,那么它可以是任何类型。 这是一个示例gettable实例属性: 如前所述,协议也可以包含实例或类型方法。 这些方法的编写方式与定义任何其他实例或类型方法的方式相同。 方法也可以像属性一样具有静态前缀,在由类实现时也可以使用class或static关键字。 有时,您可能需要允许其所属实例或该实例的任何属性被修改的方法。 协议也可以具有初始化程序,它们的编写方式与类或结构初始化程序完全相同,但是它们不包含花括号或初始化程序主体。 初始化程序分为便利类或指定的初始化程序,但是当由符合该协议的类进一步定义时,必须使用“ required”修饰符 最后,类型如Int,Double或String的协议。
取决于上下文和任务,由手写笔或手指在移动设备上进行的手势比与按钮和菜单进行标准交互更有效和方便。 然而,手势和相关命令的记忆引起一些困难。 另外,由于有限的屏幕空间和屏幕上已经存在的可单击/可移动UI元素,将手势交互应用于应用程序的操作受到限制。 为了解决这些问题,让我介绍一下iOSGesturizer库。 简而言之,该库允许在3D触摸设备上运行的任何iOS应用上基于单笔划手势的交互,而不会干扰现有的UI元素。 有关更多详细信息,让我们仔细看一下该示例。 这是他们的教程中使用的简单的Apple的FoodTracker项目。 通过CocoaPods将iOSGesturizer导入此项目后,从该项目中打开两个文件。 在AppDelegate.swift文件中,初始化窗口:UIWindow变量作为GesturizerWindow类的实例: var window:UIWindow? = GesturizerWindow() 现在打开文件MealTableViewController.swift并添加以下内容: 覆盖func viewDidAppear(_动画:布尔){ super.viewDidAppear(动画) 让window = UIApplication.shared.keyWindow! 如! 手势窗口 让view = GesturizerView() view.gestureHandler = { 让警报= UIAlertView() alert.message = view.names [索引] alert.addButton(withTitle:“确定”) alert.show() } window.setGestureView(视图:视图) } 通过gestureHandler,我们定义了手势侦听器方法。 在这种情况下,手势名称(剪切,复制或粘贴)会在警报消息中弹出。 现在,运行项目: 如您所见,该应用程序可识别三种类型的手势。 要显示可能的手势(例如左侧),您需要用力触摸并等待一秒钟,然后用手指进行所需的动作。 学习后,您无需等待即可执行手势(右侧示例)。 从体系结构的角度来看,这整个过程看起来像这样: GesturizerWindow首先处理所有屏幕触摸,并确定是否将事件传递给GesturizerView或下面的视图。 值得一提的是,只有GesturizerView才需要在“训练模式”下显示手势,而在GesturizerWindow中则需要进行按压力和手势识别的处理。 对我们来说幸运的是,GesturizerWindow不会在应用程序中造成延迟,因为手势是使用快速简单的算法1 $ -Recognizer识别的,不需要训练数据也无需学习。 如果您认为这个想法很有趣,那么我正在等待请求。 PS我计划添加一般自定义手势和参数的功能。