30天学习编写30个Swift小程序

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

=======================================================html

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

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

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

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

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属性等bash

  • 加载子Viewcontroller的addChildViewController方法session

  • "xxx class has no initializers"问题:app

    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
相关文章
相关标签/搜索