更新:所有代码已经更新到 Swift4.1,请移步 github下载

=======================================================

iOS 开发已经做了快 4 年了,听说 Swift 也已经有两年多,但是一直都只是把学习停留在表面。无意中听说了有一个叫Sam Lu在 Twitter 上发起了一个 100 天做 40 个 Swift 小程序的活动,再加上国内看到了Allen_ 朝辉写的 Swift 学习的文章,心里暗自下了一个决定:30 天写 30 个 Swift 小程序, 希望能推动自己学习 Swift 的计划。这 30 个小程序难度不同,有的一个晚上就能写完,有的要占用周末大部分时间来细研究。大部分不会的东西 Google 都能找到,就算 Swift 版本没有找到 Objective-C 版本然后用 Swift 重写就好,好在他们对应关系比较明确。

用例方面,既参考了 Sam Lu 的 40 个小项目,也参考了Allen_ 朝辉的项目,还有的是我自己仿写的知名 App。

其实我并不是唯一在国内发起这个 30 天 30 个 Swift 小程序并且将其开源的作者,但是我可能是唯一一个从头到尾用 XCode 8 + Swift3 环境编写的作者。而且,为了让代码更加可读,所有代码完全手写,而非用 Storyboard(除了只能用 Storyboard 的,例如 apple watch app)。实际上多人协作的项目中我们尽可能少用 Storyboard,因为很容易出现冲突问题。况且从学习的角度,storyboard 很难说清楚操作步骤是什么。在这上面我其实花了不少时间,但是我认为很值得。

希望能有更多对 Swift 感兴趣的开发者加入这项#30 天 30 个 Swift 小程序 的活动里面来。以下为 Github 链接: github.com/nimomeng/30…

Project 30 - Google Now App

GoogleNow.gif

我学到了

  • 这次 Project 演示了 Present/Dismissd 如何做 Transition 动画,这和做 Push/Pop 的转场动画的基本原理都是一样的
  • 这次的动画参考了 BubbleTransition 的动画效果,在它之上加了修改,支持传入自定义的 UI 属性,方便做组合型动画(例如本例中按钮不仅放大而且上下移动)
  • 动画变化的原理是将相应的 ViewController 进行 Scale 变换,再通过一个 Bubble 的蒙版看起来像是气泡效果
  • 其它的细节知识如下:
    • 画圆形按钮的方法,必须要 cornerRadius 属性为边长的 1/2,具体代码如下:
        triggerButton.layer.cornerRadius = triggerButton.frame.width / 2
        triggerButton.layer.masksToBounds = true
复制代码

Project 29 - Beauty Contest

BeautyContest.gif

我学到了

  • 这个项目是基于Yalantis 的 Koloda来制作的。 Koloda 是一个非常好用的 UIImage 选择器
  • Swift 中的懒加载的使用方法:
    • 两种方式:
lazy var firstWay = "first"
复制代码

以及

lazy var secondWay: String = {return "Second"}()
复制代码

注意:第二种方式要注意定义好字段类型,以便于编译时的类型检查;以及不要忘记最后的小括号

  • 为什么要用 Lazy:因为这里面需要先知道 KolodaView 的尺寸,才能定 Overlay 的尺寸。因此这里有一个依赖关系,因此用懒加载最合适。
  • Swift 中的 unowned 和 weak 的区别:
    • unowned 更像 OC 里的 unsafe_unretained; weak 还是那个 weak。
    • 如果确定使用时一定不会被释放,可以用 unowned; 否则最好用 weak

Project 28 - SnapChat Like App

Snap Chat Like App.gif

我学到了

  • UIScrollView 的基本使用和细节小点,例如禁止弹跳的 bounces 属性,整页切换的 isPagingEnabled 属性, 起始位置 contentOffset 属性等

  • 加载子 Viewcontroller 的 addChildViewController 方法

  • "xxx class has no initializers" 问题:

      You have to use implicitly unwrapped optionals so that Swift can cope with 
      circular dependencies (parent <-> child of the UI components in this case) 
      during the initialization phase.
    

    @IBOutlet var imgBook: UIImageView!
    @IBOutlet var titleBook: UILabel!
    @IBOutlet var pageBook: UILabel!

    复制代码
  • 权限问题,具体错误描述为: "This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data." 解决方法:iOS10 之后的权限问题,在 info.plist 里添加相应的权限以及描述即可。 本例中权限为: NSCameraUsageDescription PhotoMe needs the camera to take photos. NSMicrophoneUsageDescription PhotoMe needs the microphone to record audio with Live Photos. NSPhotoLibraryUsageDescription PhotoMe will save photos in the Photo Library.

  • AVCaptureSession 的使用方法:

    • AVCaptureSession 是 AVFoundation 的核心类, 用于捕捉视频和音频, 协调视频和音频的输入和输出流.
    • 创建AVCaptureSession实例,并设置其sessionPreset值,也就是设置画面的质量。
    • 给 Session 添加 Input。一般是 Video 或者 Audio 数据, 也可以两者都添加, 即 AVCaptureSession 的输入源 AVCaptureDeviceInput。具体步骤是先获取对应的device实例(此时决定是用 Video 还是 Audio),再由实例获取其 Input Source。最后将 input source add 到 session 中。
    • 给 Session 添加 Output,即 AVCaptureSession 的输出源。一般输出源分成: 音视频源, 图片源, 文件源等。这里以静态图片的输出源为例,指的是 AVCapturePhotoOutput。最后将其也 add 到 session 中。
    • 设置预览图层,即 AVCaptureVideoPreviewLayer。在 input,output 等重要信息都添加到 session 以后, 可以用 session 创建 AVCaptureVideoPreviewLayer, 这是摄像头的视频预览层。这里千万别忘了将 Layer 添加到 View 中。
    • 启动 Session,即captureSesssion.startRunning()
  • Photo 的捕获方法

    • AVCaptureSession 设置成功,并启动
    • 创建AVCapturePhotoSettings对象,并配置相应的属性,例如是否打开 flash,是否开启防抖模式等等
    • 执行输出源的 capture 方法,并制定具体的 AVCapturePhotoSettings 对象以及 delegate 对象
    • 在 capture 的 delegate 方法:
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?)
复制代码

中执行获取图像的具体逻辑。本例中是先将 buffer 转换为 data,再转换为 UIImage,最终 write 到相册文件夹中。

Project 27: Carousel Effect (跑马灯效果)

Carousel Effect.gif

我学到了

  • UICollectionView 的使用

    • 与 UItableView 的不同在于,每一个对应的 Cell(不论是 content 的 cell 还是 header,footer 的),都需要预先执行 register 方法
    • Cell 间距太窄的问题,可以通过 minimumLineSpacingForSection 的 DataSource 代理方法来解决掉
    • 如果选择的 layout 为 UICollectionViewFlowLayout,可以通过修改 scrollDirection 属性来修改滚动方向
  • 自定义 Layout 要在对应的子类里实现如下方法

      prepare()
      shouldInvalidateLayout(forBoundsChange newBounds: CGRect)
      targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) 
      layoutAttributesForElements(in rect: CGRect)
    复制代码

其中:

  • prepare 可以定义初始化操作

  • shouldInvalidateLayout, 判断是否需要更新每个 Cell 的 bounds。如果我们的 layout 是那种每个 cell 需要动态变化的 layout,则设置为 true;否则为了性能考虑,请设置为 false。默认为 flase。

  • targetContentOffset,如果我们需要图片在滚动的过程中在特定位置可以停下来(类似 iphone 上专辑图片的选择),请在此函数中国年给出停下来的具体规则

  • layoutAttributesForElements 返回所有元素此时的所有布局。我们会在这里定义在滚动过程中所有其他元素的 attribute 布局相关属性。例如本例中,离屏幕中间越近,图片被缩放的越大;离屏幕越小,图片被缩放的越小。

  • Reference:

  • Visual Effect View 的使用

    • 尽量在需要模糊化的图层之后添加进去,会自动虚化所覆盖的图层

      let blurEffect: UIBlurEffect = UIBlurEffect(style: .light)
      let blurView: UIVisualEffectView = UIVisualEffectView(effect: blurEffect)
      blurView.frame = self.view.bounds
      bgView.addSubview(blurView)
      复制代码
  • UISegmentedControl 的使用(略)

  • 其它:#selector() 中的 func 如果带有参数,请将具体参数也一起写进去,例如: #selector(action_segmentValueChanged(sender:)这个规则和 OC 不太一样,要注意。

Project 26 - Twitter-like Splash

TwitterLikeSplash.gif

我学到了

  • 这个效果尝试了用简单的 UIView 的 animation 方法会比较吃力,因此转而使用 CAAnimation 来做。
  • 我们需要的效果,设置 keyPath 为 "bounds"。吐槽一下,苹果为什么不做一个枚举。。。完整的 keyPath 列表如下所示: KeyPath 对照表
  • 由于 logo 的动画定制化要求比较高,所以关于这个变化的动画,选择 CAAnimation 里的 CAKeyFrameAnimation 来做。主要关注 keyTimes,values 属性,也就是 keyFrame 的属性。timingFunctions 属性是 keyframe 的 count - 1, 也就是 frame1 到 frame2,frame2 到 frame3 的动画过渡函数。这个不多说了,之前的 Project 有提到过。
  • 在 logo 变大的过程中,logo 中间的 alpha 值也应该有白色变为透明,因此应该先添加一个 maskView, 藏在最上层,logo 层之下,作为白色的底。动画 trigger 的时间和 duration 与 logo 的动画保持步调一致,并且记得在动画 complete 的时候被移除掉。这里使用了 CABasicAnimation 的 animationDidStop 代理来完成。
  • logo 的透明度变化既可以使用简单的 UIView 的 animation 方法来做,也可以采用 layer 级别的 CABasicAnimation 来完成。因为对前者比较熟悉了,所以我在这里使用后者,注意 keyPath 是opacity。代码比较简单,这里不赘述。
  • 整体效果还是很炫的:)
  • CAKeyFrameAnimation 参考此篇文档

Project 25 Custom Transition

CustomTransition.gif

我学到了

  • NavigationController 的动画是可以自定义的,去实现 UINavigationControllerDelegate 里的方法就好
  • 如果切换动画只需要关注之前的 VC 和之后的 VC,不需要关注中间过程,直接实现以下方法即可:
navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
复制代码
  • 上述方法的返回值UIViewControllerAnimatedTransitioning需要自定义动画,需要实现UIViewControllerAnimatedTransitioning代理,实现具体的两个方法:
    • 转场动画时间,直接返回一个时间即可
transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
复制代码
  • 转场动画过程,如下所示,这个比较复杂:
animateTransition(using transitionContext: UIViewControllerContextTransitioning) 
复制代码
  • 第一步,获得转场动画的 fromVC,toVC,container:
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as! XXXController
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as! YYYController
let container = transitionContext.containerView
复制代码
  • 之后是动画前的准备工作,例如 image 赋值,例如坐标的计算:
let snapshotView = fromVC.selectedCell.imageView.snapshotView(afterScreenUpdates: false)
snapshotView?.frame = container.convert(fromVC.selectedCell.imageView.frame, from: fromVC.selectedCell)
....
复制代码
  • 最后当然是 Animation 动画的执行逻辑了,可以通过 UIView 的 animate 方法去实现。具体参数和方法可以参考之前的 Project 来进灵活组合。
    • 进入的动画最后一定不能忘记加上 transitionContext.completeTransition(true) , 说明了让 navigationController 来接管控制权利(在 completion 的 block 中)
    • 退出的动画记得带上 transitionContext.completeTransition(!transitionContext.transitionWasCancelled) 说明动画执行完成
  • 如果需要关注动画的执行过程,则在上述的基础之上还应该实现下述方法:
navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
复制代码

其中,UIViewControllerInteractiveTransitioning是动画过渡对象

  • 获取 iOS 中手从左往右沿屏幕滑动的事件,是通过UIScreenEdgePanGestureRecognizer方法并设置其 edges 为 left 实现的:
 let edgePanGesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(edgePanGestrueAction(_:)))
edgePanGesture.edges = UIRectEdge.left
复制代码
  • 这篇教程针对的是 Push 与 Pop 的自定义动画的制作
  • 参考文档 1文档 2,并在他们的基础之上做了改动
  • 这个例子我很喜欢,图片是罗斯科。

Project 24 - Vertical Menu Transition

Vertical Menu Transition.gif

我学到了

  • 本文和 Google Now App 项目思路一致,都是针对 Present/Dismiss 的操作进行自定义 Transition
  • 由于动画需要局部截图,因此建议将 Present 和 Dismiss 的 Transition 写到一起,通过一个变量来进行不同动画的切换和控制。变量可以通过animationController(forDismissed dismissed: UIViewController)animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController)来进行设置。
  • target-action 方式,一般会将 target 设置为 self。如果设置为对应的 delegate,则 action 字段应该填写#selector(CustomTransitionDelegate.functionName)
  • 在 Present/Dismiss 的自定义转场动画中,记得在 complete 回调中加入动画结束语句块:
    transitionContext.completeTransition(true)
    fromViewController?.endAppearanceTransition()
    toViewController?.endAppearanceTransition()
复制代码

Project 23 - Side Navigation App

SideNavigation.gif

#### 我学到了

  • Swift-OC 混编方法
    • 新建一个头文件,例如名为 Bridge.h
    • 单击 Project 文件,选择 Build Setting, 找到 Objective-C Bridge Header,输入 Bridge.h 的路径
    • 之后所有需要在 swift 文件中引用的 OC 文件的头文件放到 Bridge.h 中进行 import

Swift-OC

  • 侧滑效果借鉴了SWRevealViewController,使用步骤如下(原项目只提到了 OC 中的调用方法)
    • 项目中至少有以下几类 viewController:第一页展示的 VC,比如 FrontViewController;tabeView 所在的 MenuViewController
    • 在 AppDelegate 中根据规则创建自定义 Window,具体步骤为:
      • 建立 UIWindow
      • 新建两个 UINavigationController, 分别以 FrontViewController 和 MenuViewController 为 rootViewController
      • 实例化 SWRevealViewController,并设置 rearViewController 的值和 frontViewController 的值。其中,rearViewController 是 tableView 所在的 UINavigationController,frontViewController 是 FrontViewController 所在的 UINavigationController
      • 将实例化的 SWRevealViewController 设置为 Window 的 rootViewController
 window = UIWindow(frame: UIScreen.main.bounds)
 let rearNavigationController = UINavigationController(rootViewController: MenuViewController())
let frontNavigationController = UINavigationController(rootViewController: FrontViewController())
let revealController = SWRevealViewController(rearViewController: rearNavigationController, frontViewController: frontNavigationController)
revealController?.delegate = self 
window?.rootViewController = revealController
window?.makeKeyAndVisible()
复制代码
  • 需要在每一个 ViewController 中加入左滑激活 Menu 的逻辑,一句话:
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
复制代码
  • 效果需要,最好隐藏掉 status bar 以及 navigationBar:
self.navigationController?.isNavigationBarHidden = true
复制代码
override var prefersStatusBarHidden: Bool {  return true  }
复制代码

Project 22 - Basic Animations

Basic Animations.gif

我学到了

  • 本次涉及到最基本的 UIAnimation,很多复杂的 Animation 其实是各种简单的 Animation 的叠加,所以不能轻视
  • Position 的 Animation 既可以通过直接修改 frame 的 origin 属性,也可以直接通过 UIView 的 transform 来进行修改
  • Opacity 直接改 Alpha 值就可以了
  • Scale 是修改了 UIView 的 transform,传入要缩放的相对比例并创建对应的 CGAffineTransform 对象。例如:
heartView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
复制代码
  • Color 是直接修改 backgroundColor 就可以了
  • Rotation 是通过修改 UIView 的 transform,传入要旋转的值并创建对应的 CGAffineTransform 对象。其中,值为 0 - 2*Pi 之间,表示 0 到 360°之间。注意,正值为逆时针转动。例如:
self.rollingBallView.transform = CGAffineTransform(6.28)
复制代码

Project 21 CoreData App

CoreDataAppDemo.gif

我学到了

  • 一定要勾选 UseCoreData,这样在 Appdelegate 里会自动生成部分代码 Paste_Image.png
  • 不一定非要通过 Editor 生成 SubClass
  • 本例中 Entity 如图所示:

Paste_Image.png

  • 在需要调用 CoreData 的类中,import CoreData
  • 本例比较简单,只进行了 getResult 和 Add 的操作,思路分别为:
    • getResult:
      let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: EntityName)
        do {
            let searchResults = try getContext().fetch(fetchRequest)
            dataSource = searchResults as! [TodoList]
        } catch  {
           // todo error handler
        }
复制代码

注意,或取出来的 searchResult 可以直接实例化为 TodoList(TodoList 是我的 Entity 名字),这样后续就可以直接使用 TodoList 的 content 方法了。

  • saveContent:
        let context = getContext()
        // 定义一个entity,这个entity一定要在xcdatamodeld中做好定义
        let entity = NSEntityDescription.entity(forEntityName: EntityName, in: context)
        let todoList = NSManagedObject(entity: entity!, insertInto: context)
        todoList.setValue(content, forKey: "content"
        do {
            try context.save()
        }catch{}
复制代码

对应 getConent 方法的代码两行:

 let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
复制代码

如此操作后使用的时候直接通过获取 TodoList 对象,然后调用其 content 方法即可完成。 cell.textLabel?.text = (dataSource[indexPath.row]).content

  • UIAlertController 添加输入款的方法:
alertController.addTextField { (textField) in
textField.placeholder = "Please input the todo Item"}
复制代码
  • 该方法在 XCode8.3 + Swift3.2 测试通过,CoreData 在 iOS10 的变化很大,之前的版本可能和上述操作方法有出入
  • 参考文章

Project 20 - Apple Watch OS App - Guess Game

WatchApp_Guess.gif

我学到了

  • Watch 程序,需要在 create project 的先选择 Watch OS 的 Section,之后选择如下:

    watchOS.png

  • watch 中的 UI 只可以通过 Storyboard 来进布局,布局文件在 WatchKit App 中的 Interface.storyboard 中

  • 例子中涉及到了 watch 和主 app 的交互,这里使用的是WCSession方法,使用步骤如下:

    • 确定 app 所在设备是否支持 WCSession
    • 生成一个 WCSession 对象,并设置其 delegate
    • 激活此 WCSession 对象 至此部分,代码为:
      let wcsession = WCSession.default()
        if WCSession.isSupported() {
            wcsession.delegate = self
            wcsession.activate()
        }
复制代码
  • 发送通信(watch 与主 app 之间)通过 WCSession 对象的 updateApplicationContext 方法来进行,例如 try wcsession.updateApplicationContext(["numberToBeGuessed": number])
  • 接收方通过代理方法来接收并解析发送的消息
    func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) 
复制代码

Project 19 - TodayWidget

TodayWidget.gif

我学到了

  • 创建 Today Widget: File > New > Target…,然后选择 iOS 中的 Application Extension 的 Today Extension
  • 为了方便 Widget 与 App 数据共享,需要切换成 App Group 模式。步骤为打开主 target, 选择 capability, 找到 App Group, 打开:

AppGroup

  • 在主 Target 下为这个 app group 添加一个名称,然后去 Extension 的 target 下去采用相同操作,并勾选这个 group
  • 我们可以采用 UserDefault 作为主 app 与 widget 之间的共享存储。但是此处不能使用 standardUserDefaults,只能通过 suiteName 的方式来进行共享,且名字是之前在 app group 中添加的名称,代码如下:
let userDefault = UserDefaults(suiteName: "group.nimoAndHisFriend.watchDemo")
复制代码
  • 在 Widget 的 ViewController 里写入相应的读取逻辑代码:
 let userDefaults = UserDefaults(suiteName: "group.nimoAndHisFriend.watchDemo")
var leftTimeWhenQuit = userDefaults?.double(forKey: "lefttime")
复制代码

为了想让 widget 里的数据也进行同步更新,可以在 extension 的代码里也加入一个 timer 来进行同步操作。这样 widge 和主程序的 widge 即可同步

  • 如果想了解更多关于 Widget 的使用,请参考文档

Project 18 - Spotlight Search

SpotlightSearch.gif

我学到了

  • Spotlight Search 的使用:
    • 引入 CoreSpotlight 库import CoreSpotlight
    • 这里用 CSSearchableItem 来进行需要被索引的对象的添加。先创建一个 CSSearchableItemAttributeSet,也就是 Item 的属性类,对具体属性进行添加,包括 title,contentDescription,以及 thumbData 的设置。
    • 需要单独说明的是,CSSearchableItemAttributeSet 对象的 thumbData 可以通过获取 UIImage 对象后对其 UIImageJPEGRepresentation 方法来获取或者 UIImagePNGRepresentation 方法来获取(取决于 image 对应的文件是什么类型)
    • 创建 CSSearchableItem 对象,并进一步通过 indexSearchableItems 方法将创建的 CSSearchableItem 添加到索引中:
let tmpItems = [searchItem]
CSSearchableIndex.default().indexSearchableItems(tmpItems) { (error) in
}
复制代码
  • 如果调试过程中,发现模拟器上重新了之前的 spotlight 缓存无法清除的情况,请更换新的模拟器,或者重置模拟器。或者干脆切换成真机进行调试,真机这种情况少一些:)

Project 17 - 3D Touch Quick Action

3DTouchQuickAction.gif

  • 3D Touch 的具体功能分成两种:第一种是在 SpringBoard 里长按图标进行直接功能跳转,第二种是在 APP 内部对特定的视图元素长按进行 Peek & Pop
  • 在做任何 3D Touch 相关功能的引入之前,务必确保用户机型支持 3D Touch。 self.window?.traitCollection.forceTouchCapability == .available
  • 针对第一种功能,建立UIApplicationShortcutItem类型的 Item,然后设置 application 的 shortcutItems 属性即可。要注意,在设置 icon 时,只可以设置系统内置的集中 icon,不支持自定义图标
  • 针对第二种功能,需要在想加入支持 3D Touch 的 VC 中注册并添加相应事件
  • 添加UIViewControllerPreviewingDelegate
  • 在此 VC 种注册 3D Touch 支持。self.registerForPreviewing(with: self, sourceView: self.view)
  • 实现两种 delegate:
    func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) 
复制代码
  • 如果想实现例子中的额外 Action button, 需要 override 对应的 previewActionItems 属性,并返回你需要的 UIPreviewAction 的 Array

Project 16 - LoginAnimation

LoginAnimation.gif

我学到了

  • 开始以为很简单,普通 UIView 的 Animation 方法即可完成。后来发现弹跳效果并不是我使用的常规方法可以完成的
  • 弹跳动画需要使用usingSpringWithDamping来完成,其中的属性要注意:
    • usingSpringWithDamping:值越小动画越夸张,借用网上图来说明其区别:

      usingSpringWithDamping

    • initialSpringVelocity: 值越大则起始速度越大,再借用网上图片来说明其区别:

      initialSpringVelocity

    • options 的各个动画曲线有何区别:可以看图来进行区分:

options

  • UIView.animation 的 usingSpringWithDamping 与不带 usingSpringWithDamping 的参数动画有什么区别呢?可以看下图:

Animation Comparation

  • 带 Spring 属性的动画太有意思了!:)
  • 此部分参考文档 1,文档 2

Project 15 - Tumblr Menu

Tumblr Menu.gif

我学到了

  • 这个例子本质上是对动画 +BlurEffect
  • 三排的动画有一个先后顺序,这个可以通过 animation 的 delay 参数进行调节
  • button 的上图下文效果需要设置,这里自定义了一个 CustomButton,对样式进行了封装。参考了这篇文章

Project 14 - Video Splash

VideoSplash.gif

我学到了

  • 创建一个 AVPlayerViewController,并将其 view 放到背景中
  • 之后结合 AVPlayerViewController 进行视频播放,并自动循环
  • 视频播放部分借鉴了此篇文章中的第十个用例,据说也是参考了一个叫 VideoSplashViewController 的库

Project 13: Animation In TableViewCell

AnimationInTableViewCell.gif

我学到了

  • 开始的思路是在 willDisplay 的 delegate 里进行动画操作,效果良好,但是发现在滚动 cell 时发生 cell 错乱的现象,原因是在滚动时 cell 重绘导致重新调用 willDisplay 进而坐标错误。粗看了下,解决起来有点儿麻烦,于是换思路。以此这种“进场动画”不应该在渲染过程中的 delegate 中执行。
  • 将动画放到 ViewWillAppear 里来做。可以通过 tableView 的 visibleCells 获取将要显示的所有 cell 的 Array,逐一遍历来进行动画操作。
  • 改变 Cell 的动画,采用上一章所说的 usingSpringWithDamping 的动画,usingSpringWithDamping 设置为 0.8,initialSpringVelocity 设置为 0.(不然动画会弹跳过大,造成顺次露出白色间隙,很不美观)
  • 改变 Cell 的具体方式,既可以直接操作cell.frame.origin.y,也可以通过cell.transform = CGAffineTransform(translationX: 0, y: tableHeight),效果是一样的。不过如果要用到缩放或者旋转的动画,恐怕只能使用后者了。
  • 动画确实是很有意思的:)

Project 12 - Emoji Slot Machine

Emoji Slot Machine.gif

我学到了

  • 乍一看没思路,本来打算用三个 collectionView 来做,但是发现有点儿复杂
  • 后来转变思路,用 UIPickerView 来做,component 设置为 3 即可
  • 随机数用 arc4random() 来算出来,之后使用 UIPickerView 的 selectRow 方法进行设置值即可达到老虎机的效果
  • 为了仿真,不能让 pickerView 转到第一个或者最后一个,不然就会碰到边界了,因此在算随机 Row 时,使用Int(arc4random())%(emojiArray.count - 2) + 1的方法来实现
  • 三个同时一致的情况实在太少了,因此为了方便模拟,我加了个双击操作,双击强制出 666。。。
  • 这个 case 还挺有意思的,哈哈

Project 11 - Gradient in TableView

GradientInTableView.gif

我学到了

  • 这个比较简单,注意将 CAGradientLayer 应用在 UITableViewCell 上即可
  • 建议将 CAGradientLayer 作为 cell 的 backgroundView,而不是直接在 cell.layer 上进行添加
  • 美观起见,隐藏掉 Cell 的 Select 效果以及 separatorStyle: table.separatorStyle = .none cell.selectionStyle = .none

Project 10 - Stretchy Header

Stretchy Header.gif

我学到了

  • 通过监听 ScrollView(及其子类)的 scrollViewDidScroll 代理可以知道 scrollView 被拉动的位移(offset)
  • 通过位移以及限定的缩放值可以得出图片需要放大的倍率
  • 通过设置 ImageView 的 transform 来完成修改即可,核心代码为
bannerImgView.transform = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
复制代码

Project 9 - Swipeable Cell

Swipeable Cell.gif

我学到了

简单起见,我用 Project 13 的代码基础上进行修改,换了个清爽的绿色:)

  • 实现 editActionsForRowAt 这个 delegate 方法,返回值是 Array, 新建几个你需要的功能返回即可
  • 每一个 Action 直接通过 UITableViewRowAction 的 init 方法新建即可。在新建方法里有 block,直接将点击逻辑写进去就行了。
  • 这种交互适用于 Accessory 比较简单的情况,例如对交互按钮大小和内容无要求的情况;如果有特殊要求,需要自定义 UITableViewCell,手动控制 Cell 与捕捉 UIPanGesture 来进行实现。注意,这种方式要排除上下滑动 Cell 的情况,不要错误触发。

Project 8 - Color Gradient

ColorGradient.gif

我学到了

  • 颜色渐变效果采用的是类 CAGradientLayer
  • 色彩空间的概念可以借助于 Color 数组来实现,注意,成员变量是 CGColor 类型,然后通过设置 CAGradientLayer 的 colors 属性来实现
  • 上下滑动时改变颜色是通过加 PanGestureRecognizer 来实现。具体效果参考了应用Solar Solar

Project 7 - Simple Photo Browser

SimplePhotoBrowser.gif

我学到了

  • 缩放图片的方式:将 imageView 添加到 ScrollView 上
  • 设置好 scrollView 的 max/minZoomScale
  • 设置好 delegate 对象,至少实现 viewForZooming 的代理方法

Project 6 - Video Player

Video Player.gif

我学到了

  • AVPlayer: 视频播放器实体
  • AVPlayerViewController: 简单封装了的视频播放器,有简单的控制功能
  • AVPlayerLayer: 视频的 Layer 层,所有功能需要写控件进行控制,适合对播放器进行深度开发
  • 后台播放的 plist 设置方式
  • do...catch... 语法的使用
  • background modes 的设置。
  • 如何做到 app 在后台长期运行:参考简书的文章
  • 如何显示锁屏信息,以及如何响应锁屏设置(实现 remoteControlReceived 的代理方法)

Project 5 - Pull To Refresh

PullToRefresh.gif

我学到了

  • 下拉刷新组件: UIRefreshControl 设置好提示文字attributedTitle, 添加好 target 事件 (UIControlEvents.valueChanged事件 ) 后,添加到 tableView 中,即可

Project 4 - Limited Input Text Field

Limit Input Text Field.gif

我学到了

  • 通过新建 UIBarButtonItem 来创建 navigationBarItem 的左右 Item
  • 通过 TextView 的 textViewDidChange 事件捕捉当前输入内容,从而进行限制输入字数
  • 通过监听 NSNotification.Name.UIKeyboardWillChangeFrame 事件来监视 Keyboard 的弹出和收起。在对应回调中,通过 note.userInfo?[UIKeyboardFrameEndUserInfoKey] 来拿到键盘的 endFrame,从而拿到键盘的高度,对计数器进行 frame 操作
  • 同理,通过 note.userInfo?[UIKeyboardAnimationDurationUserInfoKey] 拿到键盘的动画 duration,进而可以通过 UIView 的 animation 动画做到同步变化计数器的 frame

Project 3 - Find My Position

Find My Position.gif

我学到了

  • 定位点配置: 在 plist 中添加配置: NSLocationAlwaysUsageDescription
  • 用 CLLocationManager 来进行定位
  • 在逆地址解析的方法 reverseGeocodeLocation 调用时如果遇到了 block 中一直出现Domain=GEOErrorDomain Code=-8 "(null)"之类的错误,将 CLGeocoder改成全局变量即可
  • 之后这种简单功能可以直接通过苹果内置方法来完成,不需要再通过引入高德 SDK(省去了高德 SDK 的大小)

Project 2: Watch Demo

Watch's Demo.gif

我学到了

  • Update cocoaPods to 1.2.0
  • Learn how to use SnapKit (Quite similar with Masonry)
  • Learn how to use Timer in Swift
  • 我学到了: guard 语句,详见 guard 详解

Project 1: Change Custom Font

Custom Font.gif

我学到了

  • 如何修改字体属性,熟悉字体属性

  • 字体名称可以去 storyboard 中查询,或者通过如下代码来进行查询:

    func printAllSupportedFontNames() {
    let familyNames = UIFont.familyNames
    for familyName in familyNames {
        print("++++++ \(familyName)")
        let fontNames = UIFont.fontNames(forFamilyName: familyName)
        for fontName in fontNames {
            print("----- \(fontName)")
        }}}
    复制代码

写在最后:

能坚持看到这里的,我给你们手动双击 666!

image.png

实话实说,文章有点标题党,实际开发时间是 40 天左右,因为开发时间在下班后到睡觉前,所以有时因为要出去聚餐,有时犯懒,还有时晚上要你懂得,所以完成这三十个项目的时间比计划的时间要长。。。

image.png

写完这些项目,感觉上一方面是提高了使用 Swift 语言的熟练度,另一方面更是复习了一遍 iOS 开发的知识点,因为写到后来我已经基本感觉不出来跟用 OC 开发有什么思路上的差异。这也回答了别人问过我的问题,“如果我现在学 iOS 开发,是应该学 OC 还是 Swift”:

我觉得从 iOS SDK 的熟悉角度来说,没有本质区别,如果熟悉 OC 下对应语法去使用 Swift 写没有太大区别。所以与其花时间纠结不如赶紧找两个项目上手进行练习。

image.png

下一步,我打算再重新梳理下 Swift 语法,对这些项目进行小规模的重构,从结构上去看看能否挖掘到 Swift 的特性,从另一个角度(目前是功能角度)来学习 Swift。所以也许还会有下一篇。

image.png

  • IOS

    iOS 是由蘋果公司為 iPhone 開發的操作系統。它主要是給 iPhone、iPod touch、iPad 以及 Apple TV 使用。就像其基於的 Mac OS X 操作系統一樣,它也是以 Darwin 為基礎的…

    189 引用 • 1 回帖
感谢    赞同    分享    收藏    关注    反对    举报    ...