iOS开发月报#3|201809

新机发布,你中意XS Max还是XR?iOS Tips 在label中插入图片 let label = UILabel() let attribute = NSMutableAttributedString(string: "Title")let imgAttch = NSTextAttachment() imgAttch.image = image //设置图片大小 imgAttch.bounds = CGRect.init(x: 0, y: 0, width: 18, height: 15) let imageAttribute = NSAttributedString(attachment: imgAttch) //图片插入位置 attribute.insert(imageAttribute, at: 0) label.attributedText = attributeCollectionCell阴影+圆角的优雅处理方式 只针对类似App Store的整体圆角阴影的效果。 //设置父视图阴影效果 //CollectionView.swift self.layer.shadowOffset = offset self.layer.shadowColor = color.cgColor self.layer.shadowRadius = radius self.layer.shadowOpacity = opacity contentView.layer.cornerRadius = radius //如果切割圆角带imageView才需要添加下面 contentView.layer.masksToBounds = true处理tableView点击时label背景色消失问题 //第一种方式,UITableViewCell.swift override func setHighlighted(_ highlighted: Bool, animated: Bool) { let color = self.indexLabel.backgroundColor super.setHighlighted(highlighted, animated: animated) self.indexLabel.backgroundColor = color } //第二种方式 cell.textLabel.backgroundColor = UIColor.clear cell.textLabel.layer.backgroundColor = UIColor.red调整UILabel文字的内边距 自定义UILabel,然后重写drawText:方法 override func drawText(in rect: CGRect) { let insets = UIEdgeInsets.init(top: 20, left: 20, bottom: 20, right: 20) super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) }swift 浮点数取整 ceil(x)返回不小于x的最小整数值。 floor(x)返回不大于x的最大整数值。 round(x)返回x的四舍五入整数值。 let number1 = 12.456 // ceil(number1) = 13.0, floor(number1) = 12.0, round(number1) = 12.0 let number2 = 12.756 // ceil(number2) = 13.0, floor(number2) = 12.0, round(number2) = 13.0跳过非store下载的应用检查 xattr -d com.apple.quarantine app所在路径 加载大图导致内存暴涨 large_leaves_70mp.jpg图片是7033x10110(占用磁盘大小8.3MB),分辨率 = 7033 x 10110 x 4(ARGB),对应位图占用大小 = 分辨率 x 1024 x 1024 ( = 271MB),解压会把图片转成位图,也就意味着会占用271MB内存,所以解压过程内存会瞬间消耗很大,等转成NSData后位图的内存就会被回收掉,内存就降下来,这时候NSData占用的大小即是图片的实际大小,该过程中由于会转成位图,而位图的大小是比图片的实际的大小大很多的,内存暴增的点就在位图。位图的内存大小计算是根据图片的分辨率而来(分辨率(width x heigth) x 1024 x 1024 x 4 (ARGB)),所以一般来说图片分辨率越高转成的位图占用的内存空间越大。 新版iphone尺寸设备 逻辑分辨率 比例因子 对角线 分辨率iPhone XS Max 414×896 @3x 6.5inch 1242px × 2688pxiPhone XS 375×812 @3x 5.8inch 1125px × 2436pxiPhone XR 414×896 @2x 6.1inch 828px × 1792pxiPhone X 375×812 @3x 5.8inch 1125px × 2436pxiPhone 8 Plus 414×736 @3x 5.5inch 1242px × 2208pxiPhone 8 375×667 @3x 4.7inch 750px × 1334pxiPhone SE 320×568 @2x 4inch 640px × 1136pxiPhone 4 320×480 @2x 3.5inch 640px × 960px带透明度的渐变 使用CAGradientLayer进行渐变的时候,如果我们需要由一个颜色渐变至透明,当我们将透明色写成UIColor.clear或者其他通过RGBA设置的颜色,改变透明度为0时,会发现实际效果跟预期有出入,会带点黑色:这是因为clearColor会有一个透明度为0的黑色通道。所以应该这样设置透明色: UIColor(white: 1, alpha: 0).cgColor指定tableView,collectionView的header高度0 如果我们想隐藏headerView可能会直接在其高度的代理方法,做如下设置: func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0 }当实际上这并不会生效,会返回一个默认高度。也就是说这个高度只有设置成一个非0的正数才是有效的。 有些人会写成0.01,表面上看是解决问题了,但0.01的偏移会造成像素不对齐(Color Misaligned Images),加重CPU计算负荷。完美的解决方案是: func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return CGFloat.leastNonzeroMagnitude }CGFloat.leastNonzeroMagnitude表示CGFloat支持的最小正数值,不会引起像素偏移。 保持屏幕常亮 //保持屏幕常亮 UIApplication.shared.isIdleTimerDisabled = true //关闭屏幕常亮 UIApplication.shared.isIdleTimerDisabled = false注意:不要滥用屏幕常亮属性(苹果会因为这打回你的app),如果只在某些特殊场合需要屏幕常亮,应该在之后将该值恢复成默认值false。 Github MMKV MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今,在 iOS 微信上使用已有近 3 年,其性能和稳定性经过了时间的验证。近期也已移植到 Android 平台,一并开源。 替代UserDefaults的绝佳方案。 Lottie Lottie是一个面向Android和iOS的移动库,它能够解析由AE在bodymovin导出为json的效果动画,并在移动端渲染矢量动画! 一些复杂的UI可以完全交给设计师了,哈哈哈。 RSSHud RSSHub 是一个轻量、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源。 结合Reeder不要太爽哦。😆

Git基本操作回顾

作为git最常用的几个命令git status、git add、git commit,我们每天可能都会写个数十遍。但是越是这种我们熟悉的操作,越容易存在一些我们忽略的细节。这篇文章就是用来记录下这些细节,记录我们常用命令中不常用的操作。 在git中编辑过某些文件之后,由于自上次提交后你对它们做了修改,git 将它们标记为已修改文件。 我们逐步将这些修改过的文件放入暂存区,然后提交所有暂存了的修改,如此反复。所以使用 git 时文件的生命周期如下:回顾完就进入正题git status git status会有以下几种状态 $ Changes to be committed: $ (use "git reset HEAD <file>..." to unstage)表示已经在暂存区,等待添加到工作区。使用git reset命令可以将暂存区的内容移除。 $ Changes not staged for commit: $ (use "git add <file>..." to update what will be committed) $ (use "git checkout -- <file>..." to discard changes in working directory)有修改, 但是没有被添加到暂存区。使用git add命令可以将文件添加到暂存区,使用git checkout命令可以撤销文件修改。 $ Untracked files: $ (use "git add <file>..." to include in what will be committed)含有未跟踪文件, 即未纳入版本管理的文件。使用git add可以将文件放入暂存区。git add 添加文件到暂存区 git add file添加多个文件到暂存区,空格隔开 git add file1 file2使用通配符批量添加documentation目录下的所有txt后缀文件 git add documentation/*.txt添加当前目录下的所有git-开头的shell文件 git add git-*.sh将修改和以删除的文件添加到暂存区,不包括未被跟踪文件。 git add -u filegit add .和git add -A(即git add --all)区别 一.版本导致的差别: 1.x版本: (1).git add -A可以提交未跟踪、修改和删除文件。 (2).git add .可以提交未跟踪和修改文件,但是不处理删除文件。 2.x版本: 两者功能在提交类型方面是相同的。 二.所在目录不同导致的差异: (1).git add -A无论在哪个目录执行都会提交相应文件。 (2).git add .只能够提交当前目录或者它后代目录下相应文件。git commit 当我们执行git add命令将文件放到暂存区之后,还需要提交这些暂存到工作区(仓库区),从暂存区->工作区,的工作就是git commmit来做的。 # 提交暂存区到仓库区,message为提交信息 git commit -m [message] # 提交可以指定文件 git commit [file1] [file2] ... -m [message]常用的commit扩展命令 # 提交时显示所有diff信息 git commit -v # 使用一次新的commit,替代上一次提交,如果代码没有任何新变化,则用来改写上一次commit的提交信息 git commit --amend # 重做上一次commit,并包括指定文件的新变化 git commit --amend [file1] [file2]以上三条如果不带-m [message]将会在vim的编辑器中添加提交信息。 如果你感觉没有git add,git commit有点麻烦,想直接将修改到工作区,可以用另外一个命令。 # 会将上次commit之后的变化,直接添加到工作区 git commit -a -m [message]git rm rm file删除位置:相当于手动右击点删除,只删除了工作区的文件。 git status:Changes not staged for commit: 恢复:直接用git checkout -- file就可以。 git rm file它等价于rm file + git add file 删除位置:相当于不仅删除了文件,而且还添加到了暂存区。 git status:Changes to be committed:。 恢复:先git reset,去掉暂存区修改,然后再git checkout -- file,恢复文件。 git rm --cached file删除位置:从暂存区移除,不删除文件。 git status:Changes to be committed:,Untracked files: 恢复:git add file

使用git stash储存和恢复进度

当我们正在当前项目处理一些事情时,有一个需求插进来,使得我们要在别的分支做一些工作。切换分支之前当前任务是需要保存的,但我们并没有完成一个完整的任务,直接commit显得不合适,这时就可以使用git stash命令。stash是储藏的意思,该命令的作用也可以理解为先将当前的修改储藏起来,等我们在其他分支做完必要工作之后可以再回到储藏时的状态。 git stash大致可以分为储存和恢复这两步。储存 储藏当前进度有两条命令: git stash保存当前工作进度,会把暂存区和工作区的改动都保存起来,再次运行git status会发现当前工作区是干净的。 git stash save "commit message"是git stash的完整描述,可以为本次保存添加说明。 恢复 git stash list查看当前保存进度,进度保存可以有多个。 git stash apply恢复最近保存的进度,不会删除stash内容 git stash apply stash@{0}如果有多个stash,恢复某一个,按时间倒叙排列 git stash pop会恢复最新保存的工作进度,并将恢复的工作进度从存储的工作进度列表中清除。 git stash drop [stash_id]删除某一个存储的进度 git stash clear #删除所有储存进度删除所有存储进度

iOS开发月报#2|201808

数据库用完要close 当我们向下面这样执行完一次数据可查询时,要记得将数据库关闭,否则,如果此时想往同一数据库写东西的话会因为数据正在锁定收到这样的提示database is locked。 //获取下载完成的文件信息 func isExistdWith(_ id: String) -> Bool{ guard db.open() else { return false } do { let resultSet = try db.executeQuery("select * from tableName where id = ?", values: [id]) if resultSet.next() {let isCompleted = resultSet.bool(forColumn: self.isCompleted) db.close()//return之前要close数据库 return isCompleted } } catch {} db.close()//return之前要close数据库 return false }UIDatePicker的时间格式 当我们用UIDatePicker做选择时间的控件时,DatePicker会根据手机时间的设置自动选择是12小时制还是24小时制,如果我们需要强制控制DatePicker是显示12小时制还是24小时制可以这么做: datePicker.datePickerMode = .time datePicker.locale = Locale.init(identifier: "en_GB")//for 24 Hrs datePicker.locale = Locale.init(identifier: "en_US")//for 12 HrsiOS skill map变量对外只读,对内可读写 struct Person { private(set) var name : String! }设置UITableViewCell分割线对齐 默认的cell分割线都是偏向右边多一些的,如果我们想让分割线对齐的话,正确的做法是: tableView.separatorInset = UIEdgeInsets.init(top: 0, left: 40, bottom: 0, right: 40)设置左右边距都是40 但是使用这种方法会带来一个问题就是默认的textLabel会跟着右移。为了保持label的居中我们可以再加一句: tableView.separatorInset = UIEdgeInsets.init(top: 0, left: 40, bottom: 0, right: 40) tableView.layoutMargins = UIEdgeInsets.init(top: 0, left: 40, bottom: 0, right: 40)富文本显示图片元素 如果我们需要文字中插入图片元素时,可以使用富文本处理: let attch = NSTextAttachment() attch.image = UIImage.(named:"logo") attch.bounds = CGRect.init(x: 0, y: 0, width: 18, height: 18) let imageAttribute = NSAttributedString(attachment: attch) titleLabel.attributedText = attributed添加spotlight搜索索引 首先导入CoreSpotlight和MobileCoreServices框架,然后加入以下代码: var searchableItems = [CSSearchableItem]() //索引项 let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeData as String) //title attributeSet.title = "Item Title" //desription attributeSet.contentDescription = "match.description" //thumb attributeSet.thumbnailData = try? Data.init(contentsOf: URL(string: url)!) //keywords attributeSet.keywords = ["Love", "Peace"] let searchableItem = CSSearchableItem(uniqueIdentifier: "app_keywords", domainIdentifier: "com.company.app", attributeSet: attributeSet) searchableItems.append(searchableItem) //建立索引 CSSearchableIndex.default().indexSearchableItems(searchableItems) { (error) -> Void in if error != nil { print(error?.localizedDescription ?? "Error") } }Hexo编译问题 在执行hexo g编译markdown文件时莫名报错: TypeError: Cannot set property 'lastIndex' of undefined解决方案是在_config.yml中将auto_detect设为false Podfile用法 # 下面两行是指明依赖库的来源地址 source 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/Artsy/Specs.git'# 说明平台是ios,版本是8.0 platform :ios, '8.0'# 忽略引入库的所有警告(强迫症者的福音啊) inhibit_all_warnings!# 生成的是framework而不是静态库 use_frameworks!# 针对MyApp target引入AFNetworking # 针对MyAppTests target引入OCMock, target 'MyApp' do pod 'AFNetworking', '~> 3.0' target 'MyAppTests' do inherit! :search_paths pod 'OCMock', '~> 2.0.1' end pod 'JSONKit', :podspec => 'https://example.com/JSONKit.podspec' # 引入内部库 pod 'ABTest', :git => 'https://bitbucket.org/sealcn/remoteabtest.git' pod 'ABTest', :git => 'https://bitbucket.org/sealcn/remoteabtest.git', :tag=> '0.0.6' pod 'ABTest', :git => 'https://bitbucket.org/sealcn/remoteabtest.git', :commit=> '082f8319af' # 编译配置,指定仅在Debug模式下启用 pod 'Reveal-SDK', :configurations => ['Debug'] # 使用本地文件 pod 'AFNetworking', :path => '~/Documents/AFNetworking' # 指定版本号0.1.3到0.2,不包括0.2 pod 'CHIPageControl', '~> 0.1.3' # 仅安装QueryKit下的Attribute和QuerySet模块 pod 'QueryKit', :subspecs => ['Attribute', 'QuerySet']end # 这个是cocoapods的一些配置,官网并没有太详细的说明,一般采取默认就好了,也就是不写. post_install do |installer| installer.pods_project.targets.each do |target| puts target.name end end

《精进》阅后总结

最近刚看完采铜的这本《精进-如何成为一个很厉害的人》,一本很值得推荐的书。这种讲述如何学习,如何自我提升的书很容易一不小心写成空洞的心灵鸡汤。然而我在实际阅读中根本没有这种想法,因为它专业性很强,我感受到的是作者的博学和诚意,还有经常性的好像被点拨了一下的惊喜。没有深入的思考和深厚的学术功底是写不出这种书,推荐大家有机会也仔细地读一读。 阅读过程中画了很多自己受启发的观点,也可以说是我读到的这本书中的精华部分,摘录了下来。全书分七个章节,逐一讲述如何成为一个很厉害的人:时间之尺林德沃提出的更好地对待时间的十条建议: 1、活在当下 2、严肃地对待时间 3、留意自己拥有的空间并享受它 4、反思自己和其他人的时间视角 5、从现在出发联结过去 6、并不完全沉浸于过去 7、指定实现目标的计划 8、平衡计划和非计划时间 9、视未来存在与当下 10、对未来保持积极的态度 需要好好思考一下,如何通过一点一滴的人生增量,完成个人核心竞争力的锻造。 让“远期未来”更加具体,为“近期未来”增加挑战。 把时间花在值得做的事情上。收益指+收益半衰期。 使用时间之尺,审视事件的长期价值,尽可能删减掉非必要事件。 侯世达定律:实际做事花费的时间总比预期要长,即使预期中考虑了侯世达定律。 提升时间使用的“深度”,减少被动式休闲的比例,保持至少一项长期的业余爱好。寻找心中的“巴拿马”仅仅是好的选择是不够的我们需要的事最好的选择。 一个成熟的人,他的标准来自他的内心。 人不能只为他自己而活。我们必须认知所有的生命都是珍贵的,而我们和所有的生命是结合在一起的。这种认知指引了我们心灵和宇宙的关系。 最近一周,我所做过的最有意义的一件事:________ 永远不要放弃寻找“第三选择”,因为最好的选择,往往来自在更高目标指引下的我们的创造。 婚恋拇指法则:生理上有冲动,精神上受鼓舞,沟通上很流畅。 人生是持续而反复的构造,校正选择,做出建设性的改变。 不管你做了哪个选择,你的某些东西永远不会改变,最终带着你走向目的地的,可能并不是某一个选择,而是那些你不会改变的东西。即刻行动开始并完成一件事情,比做好它更重要。因为只要开始了,你就有机会把它做得更好。 先把必须要做的小时处理掉,使我们保持积极和从容心态的一剂良方。 “精益创业”有个关键概念叫“最小化可行产品”,它指的是可以使用最少的资源、被最快制作出来的、可执行基本功能的、能被用户使用的实验性产品。创业者应该尽快把最小化可行产品发布出去,然后根据用户使用它的反馈来进行优化,这一过程称为“构建-测量-学习”的循环。 成熟心智的一个特点就是,它能不偏不倚地、公正地对待自己和其他人的意见,既不固步自封也不附和盲从。 多线程工作,首先需要一段专注不受干扰的时间,完成工作中最核心部分的思考。 行动后要及时反思,并梳理这件事情的“反应连”,特别关注其中发生的意外现象。怎样的学习,才能够直面现实学习,应该以学习者心中的问题未中心,让问题引导着我们去探求答案。 不要只做信息的搬运工,通过解码,深入事物的深层。 伟大的艺术作品,常常有很深厚的内涵和很精巧细微的技法,不论你在哪个或深或浅的层次上解读他,他都能呈现出美妙的意味,但如果你不做一番细心的努力和挖掘,就只能尝到最表层的那一小部分味道。 教育心理学家把在某一领域有专长的人士,分为“常规性专长”和“适应性专长”。 求职分为三个层次:信息、知识和技能、技能是学习的终点,信息和知识是迈向这个终点的路河桥。 利用交替运用意识和潜意识进行创造性思考。向未知的无限逼近简化思维意味着我们既要简化外界输入的信息,也要简化我们表达出来的信息,更要简化我们一直思考着的信息。 如何过滤没有价值的信息:精选可信赖的信息源、不追逐当下流行或过热的信息、重事实信息,轻观点和评论、定期闭关,屏蔽外界纷扰。 用足够多的资料“喂养”潜意识。 比如整理书柜并更换一种新的图书分类方法,或者改变日常上下班的路线去探索一条从未走过的路线,也可以尝试与不同的同学或同事共进午餐等,这些小变化引起的扰动可能会刺激出我们新的想法。 把思维转化为外显图形,为我们的思考扩展出了一个更大的空间。 现实中的问题,总是牵涉太多的因素,借助矩阵、清单等工具可以完善思考的周密程度。我知道 我不知道别人知道 公开区 盲区别人不知道 隐秘区 未知区为了让思维更好的发散,获得更多的灵感,一是要关掉大脑里评论的空间,而是要适当的引入混乱与随机。 一个具有高度可塑性的大脑在良好思维工具的辅助下,在持续不断的行动的打磨中,会强大的超出你的想象。努力,是一种最需要学习的才能努力不是一场意志力的较量,而是一种需要学习的策略,可以不断学习和优化。 在心理学家眼中,“才能”被定义为“自发地重复出现且可悲高效利用的思维、情感或行为模式。” 以大多数人的努力程度之低,根本轮不到拼天赋。 挑战是设计出来的,不断为自己设计“必要的难度”挑战。 不痛苦地坚持到底,只有深入下去,才能培养出真正的兴趣。 因努力而热爱。 把时间投放在一个领域,以尽量高的标准要求自己,培养出非常高的才能。每一个成功者,都是唯一的创造成功,而不是复制成功。 做一个积极的预设判断要优于消极的预设判断。 尝试可能会犯错,可能会遭遇失败,但失败是包含信息的,甚至比成功包含的信息量更大。 一个自我教育者应该学会定期审视自己的所知和未知,能评估自己所学知识的价值,特别是在实践领域中的价值。 在现实世界中思考理论问题,在理论世界中思考现实问题。 为大众带来新鲜的见解和启发,形成对公众的影响力。 抗拒自己的欲望,或者延迟满足欲望。 根据自己的内心需求,而不是外界的认同,作出独立的选择,甚至去做一些酷的事情。 如果你找到了一条别人都还没走过的路,只要把这条路走完,你就赢了。

使用Cocoapods管理私有库组件

CocoaPods是OS X和iOS下的一个第三方开源类库管理工具,通过CocoaPods工具我们可以为项目添加依赖库(这些类库必须是CocoaPods本身所支持的),并且可以轻松管理其版本。它是目前iOS开发中使用最广泛的开源库管理工具,如果我们内部协作的组件化能够使用这种方式管理的话,那将是很便利的。 在通过Cocoapods建立内部私有库之前,我们需要再熟悉下Cocoapods的工作流程,我们创建内部私有库时也会依照这个流程来。本文目录 一、Cocoapods的工作流程 二、建立Cocoapods私有库 三、使用私有库 四、问题总结Cocoapods工作流程 工作流程如图所示:远程索引库: 这里存放了各个框架的描述文件,托管在github上: CocoaPods/Specs 本地索引库: 在安装cocoapods时,执行的pod setup就是讲远程索引克隆到本地,本地索引的目录为: ~/.cocoapods/repos/master本地索引和远程索引的目录一致,结构如下:每个库的每个版本都对应一个json格式的描述文件: { "name": "YYImage", "summary": "Image framework for iOS to display/encode/decode animated WebP, APNG, GIF, and more.", "version": "1.0", "license": { "type": "MIT", "file": "LICENSE" }, "authors": { "ibireme": "ibireme@gmail.com" }, "social_media_url": "http://blog.ibireme.com", "homepage": "https://github.com/ibireme/YYImage", "platforms": { "ios": "6.0" }, "source": { "git": "https://github.com/ibireme/YYImage.git", "tag": "1.0" }, "requires_arc": true, "default_subspecs": "Core", "subspecs": [ { "name": "Core", "source_files": "YYImage/*.{h,m}", "public_header_files": "YYImage/*.{h}", "libraries": "z", "frameworks": [ "UIKit", "CoreFoundation", "QuartzCore", "AssetsLibrary", "ImageIO", "Accelerate", "MobileCoreServices" ] }, { "name": "WebP", "dependencies": { "YYImage/Core": [] }, "ios": { "vendored_frameworks": "Vendor/WebP.framework" } } ] }本地索引文件 当执行pod search命令时,如果本地索引不存在,就会创建出来: $ pod search afn Creating search index for spec repo 'master'..本地索引文件路径为: ~/Library/Cache/Cocoapods/Pods远程框架库 以YYImage为例,它的远程框架库就是json文件中的source: https://github.com/ibireme/YYImage.git 所以再用文字总结下Cocoapods工作流程大概就是 1、本地安装cocoapods,建立本地索引库和远程索引库的映射 2、本地项目pod install 3、查找本地索引文件,然后找到各个库对应版本的json文件 4、通过json文件source字段找到引用库的git地址 5、把库文件拉到本地项目 建立Cocoapods私有库(framework) 建议采用framework的形式创建私有库,这可以很好的在开发阶段检查出库的不兼容或者文件权限出现的问题,Swift编写的代码通过Cocoapods生成的都是framework。 0、准备工作: 如何建立远程索引库 首先我们需要建立一个内部的远程索引库,类似Cocoapods/Spec的功能,之后添加的库文件索引文件都会存放到这里:https://zhangferry@bitbucket.org/sealcn/sealrepo.git 建立本地和远程索引仓库的关联: pod repo add SealRepo https://zhangferry@bitbucket.org/sealcn/sealrepo.git执行pod repo可以看到我们有了两个索引仓库,可以去在这个目录~/.cocoapods/repos看到我们刚建立的SealRepo。 如何组织文件结构 我们可以看下Alamofire的文件组织结构:我们看到这几个文件:Source用于存放Framework源文件, Example用于放Demo项目 docs和Documentation放说明文档,这个是可选的, Tests测试文件也是可选。 我们制作私有库时会仿照这个格式。一、制作framework因为是Swift的工程,接口的开放直接通过open、public等关键字指定,所以工程中的ABTest.h头文件可以删除,加入我们自己的库文件。注意:在写公有库文件时,对外界开放的属性,方法需要带上public或者open关键字。 二、添加Example工程 通过Xcode菜单栏File->New->Target...添加一个Example工程。 引入第三方库 如果无第三库引用可以跳过这一步。 注意:引入Podfile文件,需要framework和Example两个target都添加上。 测试项目 需要先编译framework,没有问题之后,导入到Demo项目里 import ABTest运行Dome,测试开发功能有没有问题。 push项目到远程库 如果已经关联过远程私有仓库,这一步可以跳过。 在远程配置一个git地址,然后将本地项目关联到远程私有仓库: git remote add origin 仓库地址如过是首次关联远程仓库,在push之前我们一般需要先拉去远程分支 git pull origin master如果提示: There is no tracking information for the current branch.那是因为本地库和远程库没有建立联系,git认为这两个仓库可能不是同一个,如果我们确认对应库没问题,可以使用: $ git pull origin master --allow-unrelated-histories 将远程库文件强制拉到本地仓库。 之后再执行push命令将项目推到远程仓库。 git push -u origin master三、Cocoapods配置文件 1、添加.swift-version .swift-version文件用来告诉cocoapods当前文件swift的版本,用命令行建立: $ echo "3.0" > .swift-version2、添加LICENSE 每个使用cocoapods添加的库都需要准守开源协议,一般是MIT协议,因为bitbucket没法自动生成,我们可以手动生成这个同名文件,然后把协议内容复制进去: MIT LicenseCopyright (c) [year] [fullname]Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.3、创建库描述文件 可以通过命令行生成描述文件: $ pod spec create ABTest然后我们编辑ABTest.podspec文件,可以仿照下面的写法 Pod::Spec.new do |s|s.name = "ABTest" s.version = "0.0.1" s.summary = "ABTest with Firebase" s.description = "This is a ABTest Framworks on swift" s.homepage = "https://bitbucket.org/sealcn/remoteabtest/src/master/" s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "zhangferry" => "zhangfei@dailyinnovation.biz" }# ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.platform = :ios, "8.0" # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.source = { :git => "https://zhangferry@bitbucket.org/sealcn/remoteabtest.git", :tag => s.version } s.source_files = "Source", "Source/*.swift"# ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # s.resource = "icon.png" # s.resources = "Resources/*.png"# ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.requires_arc = true s.static_framework = true s.dependency "Firebase/Core" s.dependency "Firebase/RemoteConfig" #s.ios.vendored_frameworks = "ABTest.framework" s.xcconfig = { 'SWIFT_INCLUDE_PATHS' => '$(PODS_ROOT)/Firebase/CoreOnly/Sources' } end此时我们的文件目录看起来应该大概是这个样子:4、验证本地podspec文件 pod lib lint该命令用于检查podspec文件书写是否正确,如果有error需要解决,warning可以不用管(可能会遇到较多问题,需耐心解决0。0)。解决之后再次运行检查命令,当命令行显示: -> ABTest (0.0.1) ABTest passed validation.说明我们本地配置成功了,到这里本地的第一个版本就算完成了! 然后我们需要将本次修改提交打上tag,提交到远程仓库。 git add . git commit -m "build v0.0.1" git push origin master git tag 0.0.1 git push --tags5、验证远程索引文件 上传代码成功之后,我们需要再次验证它跟远程仓库(ABTest远程库和.podspec)是否匹配正确,执行: pod spec lint当出现: ABTest.podspec passed validation时,说明我们远程仓库匹配正确。 6、提交podspec文件到SpecsRepo $ pod repo push SealRepo ABTest.podspec这个命令会包含pod spec lint命令,验证通过之后,会添加.podspec文件到本地索引库:和远程索引库:使用私有库 引用私有库 我们可以像使用其他库文件一样在Podfile文件中添加使用私有库了,引入方法有两种: 1、全局添加 在Podfile文件最上面添加一行: source 'https://zhangferry@bitbucket.org/sealcn/sealrepo.git'注意:如果私有仓库和cocoapods仓库出现同名库,会出现不可预期的情况(随机拉下来公有库或者私有库文件)。这时我们需要使用单独添加的方式。 2、单独添加 pod 'ABTest', :git => 'https://zhangferry@bitbucket.org/sealcn/remoteabtest.git'使用时通过import方法导入库就可以了。 更新私有库 当我们需要升级私有库,添加或者修改方法时,只需要: 1、修改.podspec文件中s.version的版本号 2、提交本地修改至远程,打上对应tag 3、使用项目的工程执行pod update 可能遇到的问题 1、pod search 查不到本地库 这个可能是cocoadpods本身问题,pod install安装没有问题 2、更新了版本,但是pod update没有找到 我们可以采用如下形式,手动指定版本号: pod 'ABTest', :git => 'https://zhangferry@bitbucket.org/sealcn/remoteabtest.git', :tag => '0.0.4'3、提示The 'Pods-App' target has transitive dependencies that include static binaries 这是因为引入的库被编译成了静态库,我们可以在.podspec文件中加入: s.static_framework = true4、引入的第三方库,在pod lint时提示找不到 可以手动指定pod目录,将firsebase替换成你的库文件路径: s.xcconfig = { 'SWIFT_INCLUDE_PATHS' => '$(PODS_ROOT)/Firebase/CoreOnly/Sources' }5、提示source_files对应文件为空 每次pod lint时都是根据版本号进行查找的,可以检查下当前修改跟版本号是否对应。

iOS技能图谱

结合目前遇到过的一些技术点和唐巧的iOS技能图谱产出的这个综合版的iOS技能图谱,供大家参考交流:

2018年七月

虽然一直都有想要保持写日记的习惯,但是对于我这种上班时间不稳定切偏晚,经常11点12点才能离开公司的人,就更难了。但如果不写点什么,总感觉这段时间就丢失了,往会看的时候会产生一种失落感。于是找到一种折中的方案,索性把时间跨度拉大一点,一个月写一篇总结。东西就放在简书上,会和博客同步。不管产出的东西多少,希望这个习惯能尽可能长的保持下去。关于工作 七月份一直在维护冥想类的新产品Peace,七月份上的线,一周一个版本,目前是1.3了。从最开始的数据不理想,到分析投放数据,开会讨论找问题,对比竞品,找到问题,指定解决方案,快速迭代,产品在越来越好,数据也在越来越好。我也在这个过程中全程目睹了一个新产品的发展历程,这种体验可以说是一件很棒的事了。 但是这段时间也是最忙的一段时期,一周一版一点也不轻松。排期排到周五,就不管多晚周五那天(甚至是已经周六)都要发版的,最晚的一次是战斗到夜里一点。随后老大也调整了策略,多留出一天时间,进行codereview,排期不再那么满,但是快速迭代的节奏还是要一直保持的。 说下最近的能力问题,项目中磨练了很多,踩了很多坑,也总结掌握了一些经验,但并不能感觉到自己已经达到了何种地步。都是碎片化的知识点,知识面不系统,也没法找到一个系统的东西去参考,说自己掌握了那些还差那些。直到遇到了技能图谱这个东西,代表个人能力的技能树。于是参考网上的iOS技能图谱,自己做了一个图谱。瞬间思路也清晰了,以后的博客内容,学习内容就可以根据这个东西来了。 关于生活 生活方面,园区正在组织篮球赛,我作为篮球队的一员在比赛的一个月之前,就已经开始训练了,一周一次两小时的训练。体育这个东西一直以来也是我心心念的小梦想,怎么说呢,虽然不是运动员,但运动本身总能给我带来不一样的,超出我自身限制的体验。我以前总幻想可能这就是兴趣吧,我喜欢这个,但成熟之后,想法就变成了,体育没那么简单,作为一个爱好就够了。 当运动员的梦想渐行渐远时,去年和今年的两场半程马拉松,又把这个心结给勾了出来,两场比赛都完赛时,那种巨大的满足感,只有两个字可以形容,贼舒服! 话题拉回来,渐渐的也迎来了篮球联赛的正式比赛,小组赛6进2,我们打了两场,一胜一负。我当了一场主力,虽然表现一般,只为球队贡献了6分,但也算是战斗到最后一刻。很荣幸啦,那种奔跑如风的感觉,观众为自己加油喝彩的感觉,不管结果怎样,我都享受到了比赛了。 关于自己 去了趟天津,见自己高中时的死党,大家,一起吃饭喝酒,谈论工作,结婚,房价。想起高中时,我们几个因为食堂没有座位蹲在一起吃饭场景,恍若隔日啊。 和女朋友去了趟十渡,玩了标志性的玻璃栈道,高山漂流,抗日英雄纪念馆,十渡风景也很美。其中高山漂流一路冲下来,身上湿了一大半,遇到一个小朋友,说“你们怕水吗?”我还没弄明白怎么回事就说了句“不怕啊”,他就跟妈妈一起开始向我俩泼水了。这熊孩子,跟我刚!当自己放下顾忌,肆无忌惮地疯玩时,这才是真正的快乐吧。 关于自身的成长,感觉自己自律性还是差一些,坏习惯多一些,我能感觉到坏习惯对我形成的阻力,但是要完全克服或者完全抹掉还是需要相当一段时间努力的。 我也在排除一些对自己产生干扰的外界因素,卸载了手机里让我耗费时间的王者荣耀,刺激战场,头条系产品。当拿起手机发现没什么可玩时,我能感受到自己时间是富余的。 就这些吧,希望自己能一直遵从内心,成为那个让自己满意的人。

iOS开发月报#1|201807

关闭隐式动画 CATransaction.begin() CATransaction.setDisableActions(true) self.layer.frame = self.bounds CATransaction.commit()AVPlayer出现一直缓存,缓存一段时间之后才开始播放的问题 player.automaticallyWaitsToMinimizeStalling = false//延迟播放,默认开关于这个属性的一些说明:In versions of iOS prior to iOS 10.0 and versions of OS X prior to 10.12, this property is unavailable, and the behavior of the AVPlayer corresponds to the type of content being played. For streaming content, including HTTP Live Streaming, the AVPlayer acts as if automaticallyWaitsToMinimizeStalling is YES. For file-based content, including file-based content accessed via progressive http download, the AVPlayer acts as if automaticallyWaitsToMinimizeStalling is NO.大致是说在iOS10之前的客户端,虽然这个参数不可用,但是非流媒体类型的播放这个配置默认为false,所以在iOS10下建议这个属性值为false。 AVPlayer是否正在播放的判断 当我们使用KVO监听player.rate来判断player的是否正在播放时,会发现这个值是不准的。其实准确的说是player.rate=1不代表正在播放,player.rate=0是可以代表正在暂停的。所以player.rate=0代表暂停,正在播放的状态可以这样判断: self.timeObserve = self.player.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 1), queue: DispatchQueue.main, using: {(time) in if self.player.timeControlStatus == AVPlayerTimeControlStatus.playing { //AVPlayerTimeControlStatus为iOS之后的API self.state = .playing } })下载时URLSessionConfiguration的配置 使用Alamofire下载时,我们通常需要一个SessionManager配置下载参数: let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 50//50s超时 /** 最大同时下载数 ---- iOS对于同一个IP服务器的并发最大默认为4,OS X为6 */ configuration.httpMaximumConnectionsPerHost = 4 /** A Boolean value that indicates whether TCP connections should be kept open when the app moves to the background. */ configuration.shouldUseExtendedBackgroundIdleMode = true//为true支持后台下载 manager = Alamofire.SessionManager(configuration: configuration)不要存储沙盒绝对地址 当我们向沙盒写入数据时,将该绝对路径保存下来,下次再打开该地址并不会获取到我们存入的数据。原因如下: iOS8之后,苹果添加的新特性,将每次打开app内的沙盒[唯一编码路径](红框部分)重新生成,并保持上一次的沙盒文件(Documents、Library、tmp)移到新生成的文件内,旧文件删除,就是说,你保存的文件都在,只不过每次打开后,都会有一个新的绝对路径。所以存储路径应该存相对路径: //这两个都代表document的相对路径 let rootPath = NSHomeDirectory() + "/Documents/" let rootPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).firstchildViewController的viewDidAppear方法调用 如果一个ViewController中嵌套了多个childViewController。当宿主VC(我们暂且这么称呼它)调用viewDidAppear等方法时,其中的childViewController都会默认调用对应方法。如果我们不想childViewController调用该方法可以重写该VC的属性: override var shouldAutomaticallyForwardAppearanceMethods: Bool { return false }图片切换渐入渐出的方法 通过UIImageView展示图片和layer.contents展示图片都可以使用以下方法: let transition = CATransition() transition.duration = 0.5 transition.type = kCATransitionFade self.view.layer.add(transition, forKey: "layer.contents") self.view.layer.contents = image.cgImage//适用于imageViewcell移出视图,移入视图的方法 //TableViewCell override func prepareForReuse() { super.prepareForReuse()//使用重用池的cell,显示过的cell移至可视范围 } //TableView func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { //cell移出视图时调用 }tableview,collectionView数据reload之后的操作 我们如果要想实现在reload之后弹出alertView,或者滚动到特定一行,可能会直接写: tableView.reloadData() tableView.scrollToRow(at: indexPath, at: .middle, animated: true)看似没问题,但是滚动没起作用,因为reloadData是立即返回的,不会等tableview刷新完成。 解决办法就是需要等reload完成之后再做我们需要的操作,reload是否完成有几种方式监听: //collectionView collectionView.performBatchUpdates(nil) { (finished) in //reload完成 } //tableView方法只有iOS11可用 tableView.performBatchUpdates(nil) { (finished) in //reload完成 }//替代func beginUpdates(),func endUpdates() //tableView等reload完成还可以使用 tableView.reloadData() DispatchQueue.main.async { //reload完成 }

可能被忽略的UIButton细节

关于System Button看一个简单的例子: button.setTitle("Title", for: .normal) button.setImage(UIImage(named: "icon"), for: .normal)buttonType分别设置为system和custom,仅做如上设置,显示效果对比(上面的custom,下面的是system)system Button显示出蓝色其实是tintColor的效果,关于tintColor的说法是:This property has no default effect for buttons with type custom. For custom buttons, you must implement any behavior related to tintColor yourself. 在custom类型的button中设置tintColor是不生效的,需要自定义样式。在system类型的button里有一个默认蓝色的tintColor,当然我们可以修改它为其他颜色,会对image和title同时生效。 另外可以发现image不是原始图片,而是被填充为tintColor的颜色。这是因为system类型下button的image被默认以alwaysTemplate类型渲染的,如果想要显示原始图片可以做如下操作: let image = UIImage(named: "icon")?.withRenderingMode(.alwaysOriginal) button.setImage(image, for: .normal)关于触摸反馈看一个常见的代码: let button = UIButton()//默认样式custom button.setTitle("Title", for: .normal) button.setTitleColor(UIColor.blue, for: .normal) button.backgroundColor = UIColor.red view.addSubview(button)以上是的button的常见写法。遗憾的是这种写法,不会带触摸反馈效果。那如果我们想加触摸反馈,需要如何处理: 1、仅文字的触摸反馈 //system: let button = UIButton(type: .system) button.setTitleColor(UIColor.blue, for: .normal)//自动添加反馈效果 button.setTitleColor(UIColor.green, for: .highlighted)//会和系统效果叠加,不可控,不建议写 //custom let button = UIButton(type: .custom) button.setTitleColor(UIColor.blue, for: .normal) button.setTitleColor(UIColor.green, for: .highlighted)//自定义反馈样式2、带图片和文字的触摸反馈 button.setTitle("Title", for: .normal) button.setImage(UIImage(named: "icon"), for: .normal) //system:会同时对图片文字添加反馈效果 //custom:默认仅对图片有触摸反馈3、带背景图的按钮 button.setBackgroundImage(UIImage(named: "background"), for: .normal) //system是按钮整体反馈 //custom是仅背景图片反馈,title,image无反馈4、关闭触摸反馈 button.isUserInteractionEnabled = false //custome,system均会关闭触摸反馈 button.adjustsImageWhenHighlighted = false //custom:会关闭image,backgroundImage的反馈 //system:此设置无效5、showsTouchWhenHighlighted 这个属性是系统提供的一种highlighted样式,点击时按钮高亮。但是效果确实有点丑丑的,基本不用这种效果 其他特性 全局修改UIButton的样式可以: let gobalBtn = UIButton.appearance()//所有继承UIView的类都可以使用这个方法 gobalBtn.setTitle("Good", for: .normal)//会将所有button的title改为GoodsetAttributedTitle方法 这个方法可以将button的title以富文本的形式进行设置,支持对不同state的设置。需要注意它和setTitle的优先级 //用attributed方式设置button的title和titleColor let string = "Title" let attributed = NSMutableAttributedString(string: string) let range = NSMakeRange(0, string.count) attributed.addAttributes([NSAttributedStringKey.foregroundColor : UIColor.green], range: range) button.setAttributedTitle(attributed, for: .normal) //此时用setTitle重新设置title样式,不会生效,attributed优先级大于直接设置 button.setTitle("Next Button", for: .normal) button.setTitleColor(UIColor.blue, for: .normal)