美文网首页swift学习iOS动画特效swift
抓住iOS的未来 - 30天学习编写30个Swift小程序

抓住iOS的未来 - 30天学习编写30个Swift小程序

作者: nimomeng | 来源:发表于2017-05-28 15:32 被阅读3511次

    更新:所有代码已经更新到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链接:
    https://github.com/nimomeng/30-swift-projects-in-30-days

    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里添加相应的权限以及描述即可。
      本例中权限为:
      <key>NSCameraUsageDescription</key>
      <string>PhotoMe needs the camera to take photos. </string>
      <key>NSMicrophoneUsageDescription</key>
      <string>PhotoMe needs the microphone to record audio with Live Photos.</string>
      <key>NSPhotoLibraryUsageDescription</key>
      <string>PhotoMe will save photos in the Photo Library.</string>

    • 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到相册文件夹中。
    - Reference: [iOS AVCaptureSession学习笔记](http://www.jianshu.com/p/b5618066dc2c)
    
    
    
    #Project 27: Carousel Effect (跑马灯效果)
    
    ![Carousel Effect.gif](http:https://img.haomeiwen.com/i2340489/f1c54a0e91776c53.gif?imageMogr2/auto-orient/strip)
    
    #### 我学到了
    - 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:
        - [UICollectionView综合视图](http://www.jianshu.com/p/c0f4d0833ff8)
        - [用UICollectionView实现相册功能]
    (http://www.jianshu.com/p/4f691ec731e5)
    
    - 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](http:https://img.haomeiwen.com/i2340489/94851cd3a75aff9a.gif?imageMogr2/auto-orient/strip)
    
    #### 我学到了
    - 这个效果尝试了用简单的UIView的animation方法会比较吃力,因此转而使用CAAnimation来做。
    - 我们需要的效果,设置keyPath为"bounds"。吐槽一下,苹果为什么不做一个枚举。。。完整的keyPath列表如下所示:
    ![KeyPath 对照表](http:https://img.haomeiwen.com/i2340489/8fd2ad3a1592d039.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/640)
    - 由于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参考[此篇文档](http://blog.csdn.net/u013316626/article/details/54379540)
    
    # Project 25 Custom Transition
    
    ![CustomTransition.gif](http:https://img.haomeiwen.com/i2340489/8d998c7d397d2102.gif?imageMogr2/auto-orient/strip)
    
    #### 我学到了
    - 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](http://kittenyang.com/magicmove/),[文档2](http://www.jianshu.com/p/38cd35968864),并在他们的基础之上做了改动
    - 这个例子我很喜欢,图片是罗斯科。
    
    
    # Project 24 - Vertical Menu Transition
    
    ![Vertical Menu Transition.gif](http:https://img.haomeiwen.com/i2340489/d8ac5f10378c6199.gif?imageMogr2/auto-orient/strip)
    
    #### 我学到了
    - 本文和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](http:https://img.haomeiwen.com/i2340489/649e18fc36d15c9a.gif?imageMogr2/auto-orient/strip)
    
    ####我学到了
    - Swift-OC混编方法
      - 新建一个头文件,例如名为Bridge.h
      - 单击Project文件,选择Build Setting,找到Objective-C Bridge Header,输入Bridge.h的路径
      - 之后所有需要在swift文件中引用的OC文件的头文件放到Bridge.h中进行import
      
    ![Swift-OC](http:https://img.haomeiwen.com/i2340489/dcfebb7a5446ea03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/500)
    
    - 侧滑效果借鉴了[SWRevealViewController](https://github.com/John-Lluch/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](http:https://img.haomeiwen.com/i2340489/bf50d015a23d2062.gif?imageMogr2/auto-orient/strip)
    
    #### 我学到了
    - 本次涉及到最基本的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](http:https://img.haomeiwen.com/i2340489/7113254108dd8b22.gif?imageMogr2/auto-orient/strip)
    
    #### 我学到了
    - 一定要勾选 UseCoreData,这样在Appdelegate里会自动生成部分代码
    ![Paste_Image.png](http:https://img.haomeiwen.com/i2340489/1f3d1001ef0571ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    - 不一定非要通过Editor生成SubClass
    - 本例中Entity如图所示:
    
    ![Paste_Image.png](http:https://img.haomeiwen.com/i2340489/79ba92016a6aa5c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800)
    
    - 在需要调用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的变化很大,之前的版本可能和上述操作方法有出入
    - [参考文章](http://www.cnblogs.com/Free-Thinker/p/5944551.html)
    
    #Project 20 - Apple Watch OS App - Guess Game
    
    
    ![WatchApp_Guess.gif](http:https://img.haomeiwen.com/i2340489/7843cf75c364d69d.gif?imageMogr2/auto-orient/strip)
    
    
    #### 我学到了
    - Watch程序,需要在create project的先选择Watch OS的Section,之后选择如下:
    ![watchOS.png](http:https://img.haomeiwen.com/i2340489/c35efafcea74c758.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/600)
    
    - 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]) 
    
    - 示例参考了[WatchKit Introduction: Building a Simple Guess Game](http://www.appcoda.com/watchkit-introduction-tutorial/)
    
    # Project 19 - TodayWidget
    
    
    ![TodayWidget.gif](http:https://img.haomeiwen.com/i2340489/121ed2f83ae5409d.gif?imageMogr2/auto-orient/strip)
    
    
    #### 我学到了
    - 创建Today Widget: File > New > Target…,然后选择 iOS 中的 Application Extension 的 Today Extension
    - 为了方便Widget与App数据共享,需要切换成App Group模式。步骤为打开主target,选择capability,找到App Group,打开:
    
    ![AppGroup](http:https://img.haomeiwen.com/i2340489/f638e5eaf80c5bd7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/640)
    - 在主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的使用,请[参考文档](https://onevcat.com/2014/08/notification-today-widget/)
    
    # Project 18 - Spotlight Search
    
    ![SpotlightSearch.gif](http:https://img.haomeiwen.com/i2340489/e7e702f5b8e98752.gif?imageMogr2/auto-orient/strip)
    
    ####我学到了
    - 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](http:https://img.haomeiwen.com/i2340489/66b03a3d2f4f541f.gif?imageMogr2/auto-orient/strip)
    
    - 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, viewControllerForLocation location: CGPoint) -> UIViewController? 
        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<UITableViewRowAction>,新建几个你需要的功能返回即可
    • 每一个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中添加配置:
      <key>NSLocationAlwaysUsageDescription</key>
      <true/>
    • 用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

    相关文章

      网友评论

      • mrsensen:pod install之后还是提示no such module snapkit
        nimomeng:@mrsensen 请问是第几个项目?
      • 647a2d7c78a5:楼主,问一个问题, 我写的绑定事件

        ```
        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handelPanGesture(sender:)))
        @ObjC func handelPanGesture(sender: UIPanGestureRecognizer) {}
        ```

        为什么不加 @ObjC 会抱错,楼主你的就不会,跪谢了
        freemanIT:在swift 中 如果一个按钮添加点击方法 如果定义为Private 或者 定义为 FilePrivate 那么会在Addtaget方法中找不到私有方法, 这时候需要加上 @ObjC
      • e39904225c48:P2 的star 该

        func startHandler(){
        if self.timer != nil {
        self.timer.invalidate()
        }else
        要不开了好多timer
        nimomeng:感谢提醒。
        已经修改!
      • c15fbfef25c3:运行后报错“Reason: Incompatible library version: CustomFont requires version 1.0.0 or later, but SnapKit provides 0.0.0” 这个怎么解决?
        nimomeng:是不是Snapkit的版本有问题?
        更新下最新代码试试看?
      • 小鬼别多嘴:有源码吗,想研究研究
        nimomeng:@小鬼别多嘴 在项目根目录运行pod install 看看
        小鬼别多嘴:@nimomeng 你好,刚才我下载了,为什么pods文件红了?怎么解决?
        nimomeng:@小鬼别多嘴 https://github.com/nimomeng/30-swift-projects-in-30-days
      • 吉s她Hmm:大写的6 给你9分 因为6翻了
        nimomeng:@吉s她Hmm 谢谢:smile:
      • spkingr:太牛了,学习榜样!
        nimomeng:@spkingr :blush:互相学习
      • 若锦:厉害了,赞一个👍
        nimomeng:@若锦 谢谢:smile:
      • saman0:高手,已经关注你了,也收藏了这个文章了,加个好友吧。
        nimomeng:@saman0 不敢当不敢当,互相学习!已关注
      • Raindew:能坚持写完确实不容易!支持。准备抽时间写一写!
        nimomeng:@Raindew 现在再下载一下试试,我删了不少不相关的资源文件,应该小了不少。
        Raindew:@nimomeng 下载解压后发现就15天的。不知道为什么少了。希望提供个百度云下载看看。命名不要太长,不然解压错误。
        nimomeng:@Raindew 加油,共勉!:smile:
      • 需要药:嵩哥,masonry拼错了
        需要药:@nimomeng 嘿嘿
        nimomeng:@凤尾竹 嘿嘿嘿,改!
      • 丶Runs:你这是OC版本的swift. 与其说30天30个swift程序 不如三十天翻译OC到swift的程序
        丶Runs:@GloomySunday good 着重强调swift特性
        GloomySunday:oc 版本的 swift 意思是基本设计逻辑还是按照 oc 的逻辑去做。而不是swift 的。比如swift 的struct ,enum 等。 你会发现用起来比 oc 舒服很多
        nimomeng:@丶Runs OC版本的Swift指的是?
      • 32a12265f41e:不明觉厉,看到探探的效果了
        nimomeng:@Hou侯新亮 老司机:stuck_out_tongue_winking_eye:

      本文标题:抓住iOS的未来 - 30天学习编写30个Swift小程序

      本文链接:https://www.haomeiwen.com/subject/heurnttx.html