仿写三个总体项目.,仿写2个完完全全项目.

前言

绝对续续的早已学习Swift一年多了, 从1.2到将来的2.2,
一向在语法之间徘徊, 学一段时间, 工作一忙,
再捡起来隔段时日又忘了.思来想去, 趁着那八个月加班不是特地多,
就控制用swift仿写2个全部项目.

花田小憩:是一个植物美学生活平台,
以自然生活为主导,
提倡植物学生活方法,
倡导美学标准的生活态度的一个APP.

个人文字功底有限, 就自身而言, 那款APP做的挺唯美的…

前言

相对续续的早已学习Swift一年多了, 从1.2到前天的2.1,
一向在语法之间徘徊, 学一段时间, 工作一忙,
再捡起来隔段日子又忘了.思来想去, 趁着那四个月加班不是专门多,
就决定用swift仿写二个完整项目.

花田小憩:是一个植物美学生活平台,
以自然生活为主导,
提倡植物学生活方法,
倡导美学标准的生活态度的一个APP.

民用文字功底有限, 就本人而言, 这款APP做的挺唯美的…

前言

纯属续续的已经学习Swift一年多了, 从1.2到现行的2.1,
一贯在语法之间徘徊, 学一段时间, 工作一忙,
再捡起来隔段时间又忘了.思来想去, 趁着那四个月加班不是特意多,
就控制用swift仿写一个完完全全项目.

花田小憩:是一个植物美学生活平台,
以自然生活为主导,
提倡植物学生活方法,
倡导美学标准的生活态度的一个APP.

个人文字功底有限, 就自作者而言, 那款APP做的挺唯美的…

github地址

<a href=”https://github.com/SunLiner/Floral"&gt;github地址&lt;/a&gt;

声明

花田小憩项目里面包车型大巴都以真正接口, 真实数据, 仅供就学, 毋作任何用途!!!

声明

花田小憩类型里面包车型客车都以实事求是接口, 真实数据, 仅供就学, 毋作别的用途!!!

声明

花田小憩类别里面包车型客车都以实际接口, 真实数据, 仅供就学, 毋作其余用途!!!

TIP: 要是截图呈现不全的话, 能够访问下边包车型大巴链接

简书

TIP: 假使截图展现不全的话, 能够访问下边包车型地铁链接

简书

品种部分截图

出于项目标大致效率都早就落实了的, 所以整个项目可能比较庞大的.所以,
下边罗列部分机能的截图.
由于gif录制的时候, 会重新渲染3遍图片,
所以导致品种中用到高斯模糊的地点, 看起来感觉比较乱,
实效依旧不错的.

新特性

首页

详情页

评论

分享

超级市场首页

杂货铺详情页

商品搜索

支付

图表浏览器

专栏小说家

民用基本

类型有些截图

鉴于品种的大致作用都早已完成了的, 所以整个项目依旧比较庞大的.所以,
上面罗列部分作用的截图.
出于gif摄像的时候, 会重新渲染一回图片,
所以导致项目中用到高斯模糊的地点, 看起来倍感相比较乱,
实效依旧不错的.

图片 1

图片 2

图片 3

图片 4

图片 5

图片 6

图片 7

图片 8

图片 9

图片 10

图片 11

图片 12

花色某个截图

是因为项指标大致功效都已经完成了的, 所以整个项目依旧比较庞大的.所以,
下边罗列部分作用的截图.
鉴于gif摄像的时候, 会重新渲染二次图片,
所以导致项目中用到高斯模糊的地点, 看起来倍感相比较乱,
实效依然不错的.

图片 13

图片 14

图片 15

图片 16

图片 17

图片 18

图片 19

图片 20

图片 21

图片 22

图片 23

图片 24

品类条件

编译器 : Xcode7.3及以上

语言 : Swift2.2

一切项目都以使用纯代码付出形式

tip: 从前编写翻译环境那儿有点错误, 因为小编项目中用了斯维夫特2.2的表征,
2.2自此方法名急需写成#selector(AddAddressViewController.save),
不再选拔双引号了

品类条件

编译器 : Xcode7.2

语言 : Swift2.1

漫天项目都以行使纯代码付出方式

体系环境

编译器 : Xcode7.2

语言 : Swift2.1

全体项目都以选择纯代码开发情势

其三方框架

use_frameworks!
platform :ios, "8.0"

target 'Floral' do

pod 'SnapKit', '~> 0.20.0' ## 自动布局
pod 'Alamofire', '~> 3.3.1' ## 网络请求, swift版的AFN
pod 'Kingfisher', '~> 2.3.1' ## 轻量级的SDWebImage

end

还用到了MBProgressHUD.
除去,大致全体都是本身造的小轮子

其三方框架

use_frameworks!
platform :ios, "8.0"

target 'Floral' do

pod 'SnapKit', '~> 0.20.0' ## 自动布局
pod 'Alamofire', '~> 3.3.1' ## 网络请求, swift版的AFN
pod 'Kingfisher', '~> 2.3.1' ## 轻量级的SDWebImage

end

还用到了MBProgressHUD.
除却,差不多全体都以本人造的小轮子

其三方框架

use_frameworks!
platform :ios, "8.0"

target 'Floral' do

pod 'SnapKit', '~> 0.20.0' ## 自动布局
pod 'Alamofire', '~> 3.3.1' ## 网络请求, swift版的AFN
pod 'Kingfisher', '~> 2.3.1' ## 轻量级的SDWebImage

end

还用到了MBProgressHUD.
除此之外,差不离全体都以本身造的小轮子

目录结构详解

Classes下富含九个职能目录:

Resources : 项目用到的能源,包蕴plist文件, js文件字体

Network : 网络请求, 全体的网络请求都在那里面,
接口参数都有详实的诠释

Tool : 包含tools(工具类),
3rdLib(第三方:友盟分享, MBProgressHUD ),
Category(所有项目用到的分类)

Home : 首页(专题), 包含专题分类, 详情, 每周Top10, 评论,
分享等等效能模块

Main : UITabBarController, UINavigationController安装以及新特点

Malls : 商城, 包含商城分类, 商品搜索, 详情, 购物车, 购买,
订单, 地址管理, 支付等等效能模块

Profile : 个人基本, 专栏笔者, 登录/注册/忘记密码, 设置等效率模块

大家能够下载项目, 对照这些目录结构举办查看, 很典型的MVC文本结构,
依旧很便宜的.

目录结构详解

Classes下富含捌个职能目录:

Resources : 项目用到的能源,包蕴plist文件, js文件字体

Network : 互连网请求, 所有的网络请求都在那中间,
接口参数都有详细的注明

Tool : 包含tools(工具类),
3rdLib(第三方:友盟分享, MBProgressHUD ),
Category(所有项目用到的分类)

Home : 首页(专题), 包含专题分类, 详情, 每周Top10, 评论,
分享等等效能模块

Main : UITabBarController, UINavigationController安装以及新特征

Malls : 商城, 包含商城分类, 商品搜索, 详情, 购物车, 购买,
订单, 地址管理, 支付等等效率模块

Profile : 个人民代表大会旨, 专栏小编, 登录/注册/忘记密码, 设置等效用模块

世家能够下载项目, 对照那一个目录结构实行查看, 很独立的MVC文件结构,
依旧很有利的.

目录结构详解

Classes下富含7个效用目录:

Resources : 项目用到的财富,包涵plist文件, js文件字体

Network : 网络请求, 全体的互连网请求都在那其中,
接口参数都有详实的注释

Tool : 包含tools(工具类),
3rdLib(第三方:友盟分享, MBProgressHUD ),
Category(所有项目用到的分类)

Home : 首页(专题), 包含专题分类, 详情, 每周Top10, 评论,
分享等等作用模块

Main : UITabBarController, UINavigationController设置以及新特色

Malls : 商城, 包含商城分类, 商品搜索, 详情, 购物车, 购买,
订单, 地址管理, 支付等等作用模块

Profile : 个人基本, 专栏小编, 登录/注册/忘记密码, 设置等功效模块

世家能够下载项目, 对照那些目录结构进行查看, 很出色的MVC文件结构,
还是很方便的.

品种部分效用模块详解

种类部分效用模块详解

类型部分功用模块详解

① 新特性NewFeatureViewController : 这几个功用模块照旧比较不难的, 用到了UICollectionViewController, 然后本人添加了UIPageControl, 只要求监听最后一个cell的点击即可.

那时有三个注意点是: 大家须要依照版本号来判定是跻身新特征界面,
广告页依然首页.

  private let SLBundleShortVersionString = "SLBundleShortVersionString"
    // MARK: - 判断版本号
  private func toNewFeature() -> Bool
    {
        // 根据版本号来确定是否进入新特性界面
        let currentVersion = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"] as! String
        let oldVersion = NSUserDefaults.standardUserDefaults().objectForKey(SLBundleShortVersionString) ?? ""

        // 如果当前的版本号和本地保存的版本比较是降序, 则需要显示新特性
        if (currentVersion.compare(oldVersion as! String)) == .OrderedDescending{
            // 保存当前的版本
             NSUserDefaults.standardUserDefaults().setObject(currentVersion, forKey: SLBundleShortVersionString)
            return true
        }
        return false
    }

① 新特性NewFeatureViewController : 这些效能模块依然比较简单的, 用到了UICollectionViewController, 然后自个儿添加了UIPageControl, 只必要监听倒数cell的点击即可.

那时有叁个注意点是: 大家供给依据版本号来判定是跻身新特征界面,
广告页依旧首页.

  private let SLBundleShortVersionString = "SLBundleShortVersionString"
    // MARK: - 判断版本号
  private func toNewFeature() -> Bool
    {
        // 根据版本号来确定是否进入新特性界面
        let currentVersion = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"] as! String
        let oldVersion = NSUserDefaults.standardUserDefaults().objectForKey(SLBundleShortVersionString) ?? ""

        // 如果当前的版本号和本地保存的版本比较是降序, 则需要显示新特性
        if (currentVersion.compare(oldVersion as! String)) == .OrderedDescending{
            // 保存当前的版本
             NSUserDefaults.standardUserDefaults().setObject(currentVersion, forKey: SLBundleShortVersionString)
            return true
        }
        return false
    }

① 新特性NewFeatureViewController : 那一个效用模块依旧相比较不难的, 用到了UICollectionViewController, 然后本身添加了UIPageControl, 只要求监听最终2个cell的点击即可.

那时有八个注意点是: 我们必要根据版本号来判定是跻身新特色界面,
广告页照旧首页.

  private let SLBundleShortVersionString = "SLBundleShortVersionString"
    // MARK: - 判断版本号
  private func toNewFeature() -> Bool
    {
        // 根据版本号来确定是否进入新特性界面
        let currentVersion = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"] as! String
        let oldVersion = NSUserDefaults.standardUserDefaults().objectForKey(SLBundleShortVersionString) ?? ""

        // 如果当前的版本号和本地保存的版本比较是降序, 则需要显示新特性
        if (currentVersion.compare(oldVersion as! String)) == .OrderedDescending{
            // 保存当前的版本
             NSUserDefaults.standardUserDefaults().setObject(currentVersion, forKey: SLBundleShortVersionString)
            return true
        }
        return false
    }

② 下拉刷新RefreshControl : 在那几个类型中, 没有用第一方的下拉刷新控件, 而是自身实现了贰个大约的下拉刷新轮子, 然后赋值给UITableViewControllerpublic var refreshControl: UIRefreshControl?属性. 首要原理就是判断下拉时的frame变化:

// 监听frame的变化
        addObserver(self, forKeyPath: "frame", options:.New, context: nil)

 // 刷新的时候, 不再进行其他操作
    private var isLoading = false
    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        let y = frame.origin.y
        // 1. 最开始一进来的时候, 刷新按钮是隐藏的, y就是-64, 需要先判断掉, y>=0 , 说明刷新控件已经完全缩回去了...
        if y >= 0 || y == -64
        {
            return
        }

        // 2. 判断是否一进来就进行刷新
        if beginAnimFlag && (y == -60.0 || y == -124.0){
            if !isLoading {
                isLoading = true
                animtoringFlag = true
                tipView.beginLoadingAnimator()
            }
            return
        }

        // 3. 释放已经触发了刷新事件, 如果触发了, 需要进行旋转
        if refreshing && !animtoringFlag
        {
            animtoringFlag = true
            tipView.beginLoadingAnimator()
            return
        }

        if y <= -50 && !rotationFlag
        {
            rotationFlag = true
            tipView.rotationRefresh(rotationFlag)
        }else if(y > -50 && rotationFlag){
            rotationFlag = false
            tipView.rotationRefresh(rotationFlag)
        }
    }

② 下拉刷新RefreshControl : 在那一个项目中, 没有用第二方的下拉刷新控件, 而是自个儿达成了一个归纳的下拉刷新轮子, 然后赋值给UITableViewControllerpublic var refreshControl: UIRefreshControl?属性. 首要原理正是判断下拉时的frame变化:

// 监听frame的变化
        addObserver(self, forKeyPath: "frame", options:.New, context: nil)

 // 刷新的时候, 不再进行其他操作
    private var isLoading = false
    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        let y = frame.origin.y
        // 1. 最开始一进来的时候, 刷新按钮是隐藏的, y就是-64, 需要先判断掉, y>=0 , 说明刷新控件已经完全缩回去了...
        if y >= 0 || y == -64
        {
            return
        }

        // 2. 判断是否一进来就进行刷新
        if beginAnimFlag && (y == -60.0 || y == -124.0){
            if !isLoading {
                isLoading = true
                animtoringFlag = true
                tipView.beginLoadingAnimator()
            }
            return
        }

        // 3. 释放已经触发了刷新事件, 如果触发了, 需要进行旋转
        if refreshing && !animtoringFlag
        {
            animtoringFlag = true
            tipView.beginLoadingAnimator()
            return
        }

        if y <= -50 && !rotationFlag
        {
            rotationFlag = true
            tipView.rotationRefresh(rotationFlag)
        }else if(y > -50 && rotationFlag){
            rotationFlag = false
            tipView.rotationRefresh(rotationFlag)
        }
    }

② 下拉刷新RefreshControl : 在那几个项目中, 没有用第一方的下拉刷新控件, 而是本身完成了叁个归纳的下拉刷新轮子, 然后赋值给UITableViewControllerpublic var refreshControl: UIRefreshControl?属性. 重要原理正是判定下拉时的frame变化:

// 监听frame的变化
        addObserver(self, forKeyPath: "frame", options:.New, context: nil)

 // 刷新的时候, 不再进行其他操作
    private var isLoading = false
    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        let y = frame.origin.y
        // 1. 最开始一进来的时候, 刷新按钮是隐藏的, y就是-64, 需要先判断掉, y>=0 , 说明刷新控件已经完全缩回去了...
        if y >= 0 || y == -64
        {
            return
        }

        // 2. 判断是否一进来就进行刷新
        if beginAnimFlag && (y == -60.0 || y == -124.0){
            if !isLoading {
                isLoading = true
                animtoringFlag = true
                tipView.beginLoadingAnimator()
            }
            return
        }

        // 3. 释放已经触发了刷新事件, 如果触发了, 需要进行旋转
        if refreshing && !animtoringFlag
        {
            animtoringFlag = true
            tipView.beginLoadingAnimator()
            return
        }

        if y <= -50 && !rotationFlag
        {
            rotationFlag = true
            tipView.rotationRefresh(rotationFlag)
        }else if(y > -50 && rotationFlag){
            rotationFlag = false
            tipView.rotationRefresh(rotationFlag)
        }
    }

高斯模糊: 使用的是系统自带的高斯模糊控件UIVisualEffectView, 它是@available(iOS 8.0, *), 附一段简单的行使代码

private lazy var blurView : BlurView = {
        let blur = BlurView(effect: UIBlurEffect(style: .Light))
        blur.categories = self.categories
        blur.delegate = self
        return blur
    }()

可以依据alpha = 0.5, 调整alpha来调动模糊效果,
gif图中的高斯模糊效果不是很醒目, 实效越发好.

高斯模糊

高斯模糊: 使用的是系统自带的高斯模糊控件UIVisualEffectView, 它是@available(iOS 8.0, *), 附一段简单的利用代码

private lazy var blurView : BlurView = {
        let blur = BlurView(effect: UIBlurEffect(style: .Light))
        blur.categories = self.categories
        blur.delegate = self
        return blur
    }()

能够依据alpha = 0.5, 调整alpha来调动模糊效果,
gif图中的高斯模糊效果不是很理解, 实际效果尤其好.

图片 25

高斯模糊: 使用的是系统自带的高斯模糊控件UIVisualEffectView, 它是@available(iOS 8.0, *), 附一段简单的施用代码

private lazy var blurView : BlurView = {
        let blur = BlurView(effect: UIBlurEffect(style: .Light))
        blur.categories = self.categories
        blur.delegate = self
        return blur
    }()

能够依照alpha = 0.5, 调整alpha来调动模糊效果,
gif图中的高斯模糊效果不是很显明, 实效尤其好.

图片 26

商城购物车动画:那组动画依旧相比简单的, 直接附代码, 固然有如何嫌疑, 能够留言大概私信作者

// MARK : - 动画相关懒加载
    /// layer
    private lazy var animLayer : CALayer = {
        let layer = CALayer()
        layer.contentsGravity = kCAGravityResizeAspectFill;
        layer.bounds = CGRectMake(0, 0, 50, 50);
        layer.cornerRadius = CGRectGetHeight(layer.bounds) / 2
        layer.masksToBounds = true;
        return layer
    }()

    /// 贝塞尔路径
    private lazy var animPath = UIBezierPath()

    /// 动画组
    private lazy var groupAnim : CAAnimationGroup = {
        let animation = CAKeyframeAnimation(keyPath: "position")
        animation.path = self.animPath.CGPath
        animation.rotationMode = kCAAnimationRotateAuto

        let expandAnimation = CABasicAnimation(keyPath: "transform.scale")
        expandAnimation.duration = 1
        expandAnimation.fromValue = 0.5
        expandAnimation.toValue = 2
        expandAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)

        let narrowAnimation = CABasicAnimation(keyPath: "transform.scale")
        // 先执行上面的, 然后再开始
        narrowAnimation.beginTime = 1
        narrowAnimation.duration = 0.5
        narrowAnimation.fromValue = 2
        narrowAnimation.toValue = 0.5
        narrowAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)

        let groups = CAAnimationGroup()
        groups.animations = [animation,expandAnimation,narrowAnimation]
        groups.duration = 1.5
        groups.removedOnCompletion = false
        groups.fillMode = kCAFillModeForwards
        groups.delegate = self
        return groups
    }()


    // MARK: - 点击事件处理
    private var num = 0
    func gotoShopCar() {
        if num >= 99 {
            self.showErrorMessage("亲, 企业采购请联系我们客服")
            return
        }
        addtoCar.userInteractionEnabled = false

        // 设置layer
        // 贝塞尔弧线的起点
        animLayer.position = addtoCar.center
        layer.addSublayer(animLayer)
        // 设置path
        animPath.moveToPoint(animLayer.position)

        let controlPointX = CGRectGetMaxX(addtoCar.frame) * 0.5

        // 弧线, controlPoint基准点, endPoint结束点
        animPath.addQuadCurveToPoint(shopCarBtn.center, controlPoint: CGPointMake(controlPointX, -frame.size.height * 5))

        // 添加并开始动画
        animLayer.addAnimation(groupAnim, forKey: "groups")
    }

    // MARK: - 动画的代理
    // 动画停止的代理
    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        if anim ==  animLayer.animationForKey("groups")!{
            animLayer.removeFromSuperlayer()
            animLayer.removeAllAnimations()

            num += 1
            shopCarBtn.num = num

            let animation = CATransition()
            animation.duration = 0.25

            shopCarBtn.layer.addAnimation(animation, forKey: nil)

            let shakeAnimation = CABasicAnimation(keyPath: "transform.translation.y")
            shakeAnimation.duration = 0.25
            shakeAnimation.fromValue = -5
            shakeAnimation.toValue = 5
            shakeAnimation.autoreverses = true

            shopCarBtn.layer .addAnimation(shakeAnimation, forKey: nil)
            addtoCar.userInteractionEnabled = true

        }
    }

商城购物车动画:那组动画照旧比较简单的, 直接附代码, 借使有什么样狐疑, 能够留言恐怕私信作者

// MARK : - 动画相关懒加载
    /// layer
    private lazy var animLayer : CALayer = {
        let layer = CALayer()
        layer.contentsGravity = kCAGravityResizeAspectFill;
        layer.bounds = CGRectMake(0, 0, 50, 50);
        layer.cornerRadius = CGRectGetHeight(layer.bounds) / 2
        layer.masksToBounds = true;
        return layer
    }()

    /// 贝塞尔路径
    private lazy var animPath = UIBezierPath()

    /// 动画组
    private lazy var groupAnim : CAAnimationGroup = {
        let animation = CAKeyframeAnimation(keyPath: "position")
        animation.path = self.animPath.CGPath
        animation.rotationMode = kCAAnimationRotateAuto

        let expandAnimation = CABasicAnimation(keyPath: "transform.scale")
        expandAnimation.duration = 1
        expandAnimation.fromValue = 0.5
        expandAnimation.toValue = 2
        expandAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)

        let narrowAnimation = CABasicAnimation(keyPath: "transform.scale")
        // 先执行上面的, 然后再开始
        narrowAnimation.beginTime = 1
        narrowAnimation.duration = 0.5
        narrowAnimation.fromValue = 2
        narrowAnimation.toValue = 0.5
        narrowAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)

        let groups = CAAnimationGroup()
        groups.animations = [animation,expandAnimation,narrowAnimation]
        groups.duration = 1.5
        groups.removedOnCompletion = false
        groups.fillMode = kCAFillModeForwards
        groups.delegate = self
        return groups
    }()


    // MARK: - 点击事件处理
    private var num = 0
    func gotoShopCar() {
        if num >= 99 {
            self.showErrorMessage("亲, 企业采购请联系我们客服")
            return
        }
        addtoCar.userInteractionEnabled = false

        // 设置layer
        // 贝塞尔弧线的起点
        animLayer.position = addtoCar.center
        layer.addSublayer(animLayer)
        // 设置path
        animPath.moveToPoint(animLayer.position)

        let controlPointX = CGRectGetMaxX(addtoCar.frame) * 0.5

        // 弧线, controlPoint基准点, endPoint结束点
        animPath.addQuadCurveToPoint(shopCarBtn.center, controlPoint: CGPointMake(controlPointX, -frame.size.height * 5))

        // 添加并开始动画
        animLayer.addAnimation(groupAnim, forKey: "groups")
    }

    // MARK: - 动画的代理
    // 动画停止的代理
    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        if anim ==  animLayer.animationForKey("groups")!{
            animLayer.removeFromSuperlayer()
            animLayer.removeAllAnimations()

            num += 1
            shopCarBtn.num = num

            let animation = CATransition()
            animation.duration = 0.25

            shopCarBtn.layer.addAnimation(animation, forKey: nil)

            let shakeAnimation = CABasicAnimation(keyPath: "transform.translation.y")
            shakeAnimation.duration = 0.25
            shakeAnimation.fromValue = -5
            shakeAnimation.toValue = 5
            shakeAnimation.autoreverses = true

            shopCarBtn.layer .addAnimation(shakeAnimation, forKey: nil)
            addtoCar.userInteractionEnabled = true

        }
    }

商城购物车动画:那组动画照旧比较不难的, 间接附代码, 尽管有何样疑忌, 能够留言或许私信笔者

// MARK : - 动画相关懒加载
    /// layer
    private lazy var animLayer : CALayer = {
        let layer = CALayer()
        layer.contentsGravity = kCAGravityResizeAspectFill;
        layer.bounds = CGRectMake(0, 0, 50, 50);
        layer.cornerRadius = CGRectGetHeight(layer.bounds) / 2
        layer.masksToBounds = true;
        return layer
    }()

    /// 贝塞尔路径
    private lazy var animPath = UIBezierPath()

    /// 动画组
    private lazy var groupAnim : CAAnimationGroup = {
        let animation = CAKeyframeAnimation(keyPath: "position")
        animation.path = self.animPath.CGPath
        animation.rotationMode = kCAAnimationRotateAuto

        let expandAnimation = CABasicAnimation(keyPath: "transform.scale")
        expandAnimation.duration = 1
        expandAnimation.fromValue = 0.5
        expandAnimation.toValue = 2
        expandAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)

        let narrowAnimation = CABasicAnimation(keyPath: "transform.scale")
        // 先执行上面的, 然后再开始
        narrowAnimation.beginTime = 1
        narrowAnimation.duration = 0.5
        narrowAnimation.fromValue = 2
        narrowAnimation.toValue = 0.5
        narrowAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)

        let groups = CAAnimationGroup()
        groups.animations = [animation,expandAnimation,narrowAnimation]
        groups.duration = 1.5
        groups.removedOnCompletion = false
        groups.fillMode = kCAFillModeForwards
        groups.delegate = self
        return groups
    }()


    // MARK: - 点击事件处理
    private var num = 0
    func gotoShopCar() {
        if num >= 99 {
            self.showErrorMessage("亲, 企业采购请联系我们客服")
            return
        }
        addtoCar.userInteractionEnabled = false

        // 设置layer
        // 贝塞尔弧线的起点
        animLayer.position = addtoCar.center
        layer.addSublayer(animLayer)
        // 设置path
        animPath.moveToPoint(animLayer.position)

        let controlPointX = CGRectGetMaxX(addtoCar.frame) * 0.5

        // 弧线, controlPoint基准点, endPoint结束点
        animPath.addQuadCurveToPoint(shopCarBtn.center, controlPoint: CGPointMake(controlPointX, -frame.size.height * 5))

        // 添加并开始动画
        animLayer.addAnimation(groupAnim, forKey: "groups")
    }

    // MARK: - 动画的代理
    // 动画停止的代理
    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        if anim ==  animLayer.animationForKey("groups")!{
            animLayer.removeFromSuperlayer()
            animLayer.removeAllAnimations()

            num += 1
            shopCarBtn.num = num

            let animation = CATransition()
            animation.duration = 0.25

            shopCarBtn.layer.addAnimation(animation, forKey: nil)

            let shakeAnimation = CABasicAnimation(keyPath: "transform.translation.y")
            shakeAnimation.duration = 0.25
            shakeAnimation.fromValue = -5
            shakeAnimation.toValue = 5
            shakeAnimation.autoreverses = true

            shopCarBtn.layer .addAnimation(shakeAnimation, forKey: nil)
            addtoCar.userInteractionEnabled = true

        }
    }

⑤ 大旨详情页:商城详情页的做法也是基本上的, 但是更简约一点.

大旨详情页

最首要一点在于, 详情页的显得重庆大学依赖于H5页面.
而我辈需求基于webview的惊人来规定webviewCell的中度.小编的做法是监听UIWebViewwebViewDidFinishLoad,
取出webView.scrollView.contentSize.height下一场给详情页发送3个公告,
让其刷新界面. 一时并未想到更好的格局, 固然您有更好的做法, 请务必告诉自个儿,
多谢…

⑤ 大旨详情页:商城详情页的做法也是大概的, 可是更简圣元(Synutra)点.

图片 27

首要一点在于, 详情页的展现重庆大学依靠于H5页面.
而我们供给遵照webview的惊人来规定webviewCell的中度.笔者的做法是监听UIWebViewwebViewDidFinishLoad,
取出webView.scrollView.contentSize.height接下来给详情页发送多个公告,
让其刷新界面. 权且并未想到更好的点子, 若是您有更好的做法, 请务必告诉自身,
谢谢…

⑤ 宗旨详情页:商城详情页的做法也是基本上的, 可是更简短一点.

图片 28

关键一点在乎, 详情页的显得重庆大学借助于H5页面.
而作者辈要求根据webview的可观来鲜明webviewCell的中度.作者的做法是监听UIWebViewwebViewDidFinishLoad,
取出webView.scrollView.contentSize.height接下来给详情页发送1个通报,
让其刷新界面. 近年来没有想到更好的方法, 如若您有更好的做法, 请务必告诉本身,
感谢…

UIWebView中图纸的点击

第三步: 我们创立1个image.js文件, 代码如下:

//setImage的作用是为页面的中img元素添加onClick事件,即设置点击时调用imageClick
function setImageClick(){
    var imgs = document.getElementsByTagName("img");
    for (var i=0;i<imgs.length;i++){
        var src = imgs[i].src;
        imgs[i].setAttribute("onClick","imageClick(src)");
    }
    document.location = imageurls;
}

//imageClick即图片 onClick时触发的方法,document.location = url;的作用是使调用
//webView: shouldStartLoadWithRequest: navigationType:方法,在该方法中我们真正处理图片的点击
function imageClick(imagesrc){
    var url="imageClick::"+imagesrc;
    document.location = url;
}

第②步:在UIWebView的代办方法webViewDidFinishLoad中, 加载JS文件,
并给图片绑定绑定点击事件

// 加载js文件
        webView.stringByEvaluatingJavaScriptFromString(try! String(contentsOfURL: NSBundle.mainBundle().URLForResource("image", withExtension: "js")!, encoding: NSUTF8StringEncoding))

        // 给图片绑定点击事件
        webView.stringByEvaluatingJavaScriptFromString("setImageClick()")

第③步:在UIWebView的代办方法-webView:shouldStartLoadWithRequest:navigationType:中判断图片的点击

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
        let urlstr = request.URL?.absoluteString
        let components : [String] = urlstr!.componentsSeparatedByString("::")
        if (components.count >= 1) {
            //判断是不是图片点击
            if (components[0] == "imageclick") {
                parentViewController?.presentViewController(ImageBrowserViewController(urls: [NSURL(string: components.last!)!], index: NSIndexPath(forItem: 0, inSection: 0)), animated: true, completion: nil)
                return false;
            }
            return true;
        }
        return true
    }

UIWebView中图纸的点击

第1步: 大家成立3个image.js文件, 代码如下:

//setImage的作用是为页面的中img元素添加onClick事件,即设置点击时调用imageClick
function setImageClick(){
    var imgs = document.getElementsByTagName("img");
    for (var i=0;i<imgs.length;i++){
        var src = imgs[i].src;
        imgs[i].setAttribute("onClick","imageClick(src)");
    }
    document.location = imageurls;
}

//imageClick即图片 onClick时触发的方法,document.location = url;的作用是使调用
//webView: shouldStartLoadWithRequest: navigationType:方法,在该方法中我们真正处理图片的点击
function imageClick(imagesrc){
    var url="imageClick::"+imagesrc;
    document.location = url;
}

第②步:在UIWebView的代办方法webViewDidFinishLoad中, 加载JS文件,
并给图片绑定绑确定地点击事件

// 加载js文件
        webView.stringByEvaluatingJavaScriptFromString(try! String(contentsOfURL: NSBundle.mainBundle().URLForResource("image", withExtension: "js")!, encoding: NSUTF8StringEncoding))

        // 给图片绑定点击事件
        webView.stringByEvaluatingJavaScriptFromString("setImageClick()")

第③步:在UIWebView的代办方法-webView:shouldStartLoadWithRequest:navigationType:中判断图片的点击

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
        let urlstr = request.URL?.absoluteString
        let components : [String] = urlstr!.componentsSeparatedByString("::")
        if (components.count >= 1) {
            //判断是不是图片点击
            if (components[0] == "imageclick") {
                parentViewController?.presentViewController(ImageBrowserViewController(urls: [NSURL(string: components.last!)!], index: NSIndexPath(forItem: 0, inSection: 0)), animated: true, completion: nil)
                return false;
            }
            return true;
        }
        return true
    }

UIWebView中图纸的点击

第壹步: 大家创建多个image.js文件, 代码如下:

//setImage的作用是为页面的中img元素添加onClick事件,即设置点击时调用imageClick
function setImageClick(){
    var imgs = document.getElementsByTagName("img");
    for (var i=0;i<imgs.length;i++){
        var src = imgs[i].src;
        imgs[i].setAttribute("onClick","imageClick(src)");
    }
    document.location = imageurls;
}

//imageClick即图片 onClick时触发的方法,document.location = url;的作用是使调用
//webView: shouldStartLoadWithRequest: navigationType:方法,在该方法中我们真正处理图片的点击
function imageClick(imagesrc){
    var url="imageClick::"+imagesrc;
    document.location = url;
}

第②步:在UIWebView的代办方法webViewDidFinishLoad中, 加载JS文件,
并给图片绑定绑定点击事件

// 加载js文件
        webView.stringByEvaluatingJavaScriptFromString(try! String(contentsOfURL: NSBundle.mainBundle().URLForResource("image", withExtension: "js")!, encoding: NSUTF8StringEncoding))

        // 给图片绑定点击事件
        webView.stringByEvaluatingJavaScriptFromString("setImageClick()")

第③步:在UIWebView的代办方法-webView:shouldStartLoadWithRequest:navigationType:中判断图片的点击

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
        let urlstr = request.URL?.absoluteString
        let components : [String] = urlstr!.componentsSeparatedByString("::")
        if (components.count >= 1) {
            //判断是不是图片点击
            if (components[0] == "imageclick") {
                parentViewController?.presentViewController(ImageBrowserViewController(urls: [NSURL(string: components.last!)!], index: NSIndexPath(forItem: 0, inSection: 0)), animated: true, completion: nil)
                return false;
            }
            return true;
        }
        return true
    }

⑦ 登录/注册/忘记密码: 眼尖一点的对象可能在上头的gif中一度发现, 花田小憩中的登录/注册/忘记密码界面差不离是同一的, 作者的做法是用3个控制器LoginViewController来代表登录/注册/忘记密码几个功效模块, 通过八个变量isRegisterisRevPwd来判定是哪位意义, 显示怎么界面, 大家点击注册忘记密码的时候, 会执行代理方法:

// MARK: - LoginHeaderViewDelegate
    func loginHeaderView(loginHeaderView : LoginHeaderView, clickRevpwd pwdBtn: UIButton) {
        let login = LoginViewController()
        login.isRevPwd = true
        navigationController?.pushViewController(login, animated: true)
    }

    func loginHeaderView(loginHeaderView : LoginHeaderView, clickRegister registerbtn: UIButton) {
        let login = LoginViewController()
        login.isRegister = true
        navigationController?.pushViewController(login, animated: true)
    }

⑦ 登录/注册/忘记密码: 眼尖一点的情人或许在地点的gif中已经意识, 花田小憩中的登录/注册/忘记密码界面差不离是同等的, 作者的做法是用二个控制器LoginViewController来代表登录/注册/忘记密码多个效率模块, 通过四个变量isRegisterisRevPwd来判定是哪些意义, 显示怎么界面, 大家点击注册忘记密码的时候, 会执行代理方法:

// MARK: - LoginHeaderViewDelegate
    func loginHeaderView(loginHeaderView : LoginHeaderView, clickRevpwd pwdBtn: UIButton) {
        let login = LoginViewController()
        login.isRevPwd = true
        navigationController?.pushViewController(login, animated: true)
    }

    func loginHeaderView(loginHeaderView : LoginHeaderView, clickRegister registerbtn: UIButton) {
        let login = LoginViewController()
        login.isRegister = true
        navigationController?.pushViewController(login, animated: true)
    }

⑦ 登录/注册/忘记密码: 眼尖一点的意中人或许在上边的gif中曾经意识, 花田小憩中的登录/注册/忘记密码界面大约是均等的, 小编的做法是用3个控制器LoginViewController来代表登录/注册/忘记密码几个效用模块, 通过多少个变量isRegisterisRevPwd来判断是哪些意义, 显示怎么界面, 我们点击注册忘记密码的时候, 会执行代理方法:

// MARK: - LoginHeaderViewDelegate
    func loginHeaderView(loginHeaderView : LoginHeaderView, clickRevpwd pwdBtn: UIButton) {
        let login = LoginViewController()
        login.isRevPwd = true
        navigationController?.pushViewController(login, animated: true)
    }

    func loginHeaderView(loginHeaderView : LoginHeaderView, clickRegister registerbtn: UIButton) {
        let login = LoginViewController()
        login.isRegister = true
        navigationController?.pushViewController(login, animated: true)
    }

⑧ 验证码的倒计时功能

验证码倒计时

/// 点击"发送验证码"按钮
    func clickSafeNum(btn: UIButton) {
        var seconds = 10 //倒计时时间
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
        dispatch_source_set_timer(timer,dispatch_walltime(nil, 0),1 * NSEC_PER_SEC, 0); //每秒执行
        dispatch_source_set_event_handler(timer) { 
            if(seconds<=0){ //倒计时结束,关闭
                dispatch_source_cancel(timer);
                dispatch_async(dispatch_get_main_queue(), {
                    //设置界面的按钮显示 根据自己需求设置
                    btn.setTitleColor(UIColor.blackColor(), forState:.Normal)
                    btn.setTitle("获取验证码", forState:.Normal)
                    btn.titleLabel?.font = defaultFont14
                    btn.userInteractionEnabled = true
                    });
            }else{

                dispatch_async(dispatch_get_main_queue(), {
                    UIView.beginAnimations(nil, context: nil)
                    UIView.setAnimationDuration(1)
                })
                dispatch_async(dispatch_get_main_queue(), {
                    //设置界面的按钮显示 根据自己需求设置
                    UIView.beginAnimations(nil, context: nil)
                    UIView.setAnimationDuration(1)
                    btn.setTitleColor(UIColor.orangeColor(), forState:.Normal)
                    btn.setTitle("\(seconds)秒后重新发送", forState:.Normal)
                    btn.titleLabel?.font = UIFont.systemFontOfSize(11)
                    UIView.commitAnimations()
                    btn.userInteractionEnabled = false

                })
               seconds -= 1

        }

    }
    dispatch_resume(timer)
}

⑧ 验证码的倒计时功能

图片 29

/// 点击"发送验证码"按钮
    func clickSafeNum(btn: UIButton) {
        var seconds = 10 //倒计时时间
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
        dispatch_source_set_timer(timer,dispatch_walltime(nil, 0),1 * NSEC_PER_SEC, 0); //每秒执行
        dispatch_source_set_event_handler(timer) { 
            if(seconds<=0){ //倒计时结束,关闭
                dispatch_source_cancel(timer);
                dispatch_async(dispatch_get_main_queue(), {
                    //设置界面的按钮显示 根据自己需求设置
                    btn.setTitleColor(UIColor.blackColor(), forState:.Normal)
                    btn.setTitle("获取验证码", forState:.Normal)
                    btn.titleLabel?.font = defaultFont14
                    btn.userInteractionEnabled = true
                    });
            }else{

                dispatch_async(dispatch_get_main_queue(), {
                    UIView.beginAnimations(nil, context: nil)
                    UIView.setAnimationDuration(1)
                })
                dispatch_async(dispatch_get_main_queue(), {
                    //设置界面的按钮显示 根据自己需求设置
                    UIView.beginAnimations(nil, context: nil)
                    UIView.setAnimationDuration(1)
                    btn.setTitleColor(UIColor.orangeColor(), forState:.Normal)
                    btn.setTitle("\(seconds)秒后重新发送", forState:.Normal)
                    btn.titleLabel?.font = UIFont.systemFontOfSize(11)
                    UIView.commitAnimations()
                    btn.userInteractionEnabled = false

                })
               seconds -= 1

        }

    }
    dispatch_resume(timer)
}

⑧ 验证码的倒计时功能

图片 30

/// 点击"发送验证码"按钮
    func clickSafeNum(btn: UIButton) {
        var seconds = 10 //倒计时时间
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
        dispatch_source_set_timer(timer,dispatch_walltime(nil, 0),1 * NSEC_PER_SEC, 0); //每秒执行
        dispatch_source_set_event_handler(timer) { 
            if(seconds<=0){ //倒计时结束,关闭
                dispatch_source_cancel(timer);
                dispatch_async(dispatch_get_main_queue(), {
                    //设置界面的按钮显示 根据自己需求设置
                    btn.setTitleColor(UIColor.blackColor(), forState:.Normal)
                    btn.setTitle("获取验证码", forState:.Normal)
                    btn.titleLabel?.font = defaultFont14
                    btn.userInteractionEnabled = true
                    });
            }else{

                dispatch_async(dispatch_get_main_queue(), {
                    UIView.beginAnimations(nil, context: nil)
                    UIView.setAnimationDuration(1)
                })
                dispatch_async(dispatch_get_main_queue(), {
                    //设置界面的按钮显示 根据自己需求设置
                    UIView.beginAnimations(nil, context: nil)
                    UIView.setAnimationDuration(1)
                    btn.setTitleColor(UIColor.orangeColor(), forState:.Normal)
                    btn.setTitle("\(seconds)秒后重新发送", forState:.Normal)
                    btn.titleLabel?.font = UIFont.systemFontOfSize(11)
                    UIView.commitAnimations()
                    btn.userInteractionEnabled = false

                })
               seconds -= 1

        }

    }
    dispatch_resume(timer)
}

设置模块中给我们评分

以此职能在实质上付出中等专业高校门常见:

给我们评分

代码如下, 很简短:

UIApplication.sharedApplication().openURL(NSURL(string: "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=998252000")!)

其间最终的id供给填写您协调的APP在AppStore中的id,
打开iTunes找到您本人的APP只怕您想要的APP, 就能查看到id.

tip: 此功能测试的时候, 必须用真机!!!

设置模块中给我们评分

那些职能在骨子里支付中特意常见:

图片 31

代码如下, 很简短:

UIApplication.sharedApplication().openURL(NSURL(string: "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=998252000")!)

个中最后的id供给填写您自个儿的APP在AppStore中的id,
打开iTunes找到你本人的APP恐怕您想要的APP, 就能查看到id.

tip: 此功能测试的时候, 必须用真机!!!

设置模块中给我们评分

这一个功效在骨子里付出中特意常见:

图片 32

代码如下, 很简短:

UIApplication.sharedApplication().openURL(NSURL(string: "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=998252000")!)

内部最终的id供给填写您协调的APP在AppStore中的id,
打开iTunes找到您本身的APP恐怕您想要的APP, 就能查看到id.

tip: 此功能测试的时候, 必须用真机!!!

⑩ 登录状态.

作者们能够透过NSHTTPCookieStorage中的NSHTTPCookie来判断登录状态.也得以自定义三个字段来保存.
依据本人抓包得知,
花田小憩APP的做法是第四回登录后保存用户名和密码(MD5加密的,
笔者测试过), 然后历次运维应用程序的时候, 会首先后台自动登录,
然后在开始展览评价/点赞等操作的时候呢,
参数中会带上用户的id.由于涉及到花田小憩的账号密码的有的心事,
所以登录/注册模块, 笔者就一向不没有总体的写出来. 有趣味的朋友能够私信小编,
笔者得以把接口给您, 在此表明: 仅供学习, 毋做伤天害理之事

`tip: 小编在AppDelegate.swift中给大家留了2个开关,
能够便捷的举办登录状态的切换…

⑩ 登录状态.

咱俩得以因而NSHTTPCookieStorage中的NSHTTPCookie来判定登录状态.也能够自定义四个字段来保存.
依据自家抓包得知,
花田小憩APP的做法是首先次登录后保存用户名和密码(MD5加密的,
小编测试过), 然后历次运营应用程序的时候, 会首先后台自动登录,
然后在进行评论/点赞等操作的时候吧,
参数中会带上用户的id.由于涉及到花田小憩的账号密码的一些隐衷,
所以登录/注册模块, 小编就从未有过没有全部的写出来. 有趣味的对象可以私信作者,
小编能够把接口给你, 在此评释: 仅供学习, 毋做伤天害理之事

`tip: 作者在AppDelegate.swift中给大家留了二个开关,
能够长足的拓展登录状态的切换…

⑩ 登录状态.

咱俩得以由此NSHTTPCookieStorage中的NSHTTPCookie来判断登录状态.也得以自定义二个字段来保存.
根据自个儿抓包得知,
花田小憩APP的做法是第③遍登录后保存用户名和密码(MD5加密的,
笔者测试过), 然后历次运维应用程序的时候, 会首先后台自动登录,
然后在拓展评论/点赞等操作的时候啊,
参数中会带上用户的id.由于涉及到花田小憩的账号密码的局地心事,
所以登录/注册模块, 小编就从未没有完全的写出来. 有趣味的敌人能够私信笔者,
作者能够把接口给您, 在此评释: 仅供学习, 毋做伤天害理之事

`tip: 小编在AppDelegate.swift中给大家留了二个开关,
能够长足的开始展览登录景况的切换…

⑩+①: 个人/专栏中央: 那七个职能是同叁个控制器, 是UICollectionViewController而不是UITableViewController

个体焦点

大家对UITableViewControllerheader相应很熟习吧, 向上海滑稽剧团动的时候,
会停留在navigationBar的上面,
即便UICollectionViewController也得以安装header, 可是在iOS9原先,
他是无法一贯设置停留的.在iOS9然后, 能够一行代码设置header的栖息

sectionHeadersPinToVisibleBounds = true

只是在iOS9以前, 大家须要协调达成那些职能:

//
//  LevitateHeaderFlowLayout.swift
//  Floral
//
//  Created by ALin on 16/5/20.
//  Copyright © 2016年 ALin. All rights reserved.
//  可以让header悬浮的流水布局

import UIKit

class LevitateHeaderFlowLayout: UICollectionViewFlowLayout {
    override func prepareLayout() {
        super.prepareLayout()
        // 即使界面内容没有超过界面大小,也要竖直方向滑动
        collectionView?.alwaysBounceVertical = true
        // sectionHeader停留
        if #available(iOS 9.0, *) {
            sectionHeadersPinToVisibleBounds = true
        }
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        // 1. 获取父类返回的UICollectionViewLayoutAttributes数组
        var answer = super.layoutAttributesForElementsInRect(rect)!

        // 2. 如果是iOS9.0以上, 直接返回父类的即可. 不用执行下面的操作了. 因为我们直接设置sectionHeadersPinToVisibleBounds = true即可
        if #available(iOS 9.0, *) {
            return answer
        }

        // 3. 如果是iOS9.0以下的系统

        // 以下代码来源:http://stackoverflow.com/questions/13511733/how-to-make-supplementary-view-float-in-uicollectionview-as-section-headers-do-i%3C/p%3E
        // 目的是让collectionview的header可以像tableview的header一样, 可以停留

        // 创建一个索引集.(NSIndexSet:唯一的,有序的,无符号整数的集合)
        let missingSections = NSMutableIndexSet()
        // 遍历, 获取当前屏幕上的所有section
        for layoutAttributes in answer {
            // 如果是cell类型, 就加入索引集里面
            if (layoutAttributes.representedElementCategory == UICollectionElementCategory.Cell) {
                missingSections.addIndex(layoutAttributes.indexPath.section)
            }
        }

        // 遍历, 将屏幕中拥有header的section从索引集中移除
        for layoutAttributes in answer {
            // 如果是header, 移掉所在的数组
            if (layoutAttributes.representedElementKind == UICollectionElementKindSectionHeader) {
                missingSections .removeIndex(layoutAttributes.indexPath.section)
            }
        }

        // 遍历当前屏幕没有header的索引集
        missingSections.enumerateIndexesUsingBlock { (idx, _) in
            // 获取section中第一个indexpath
            let indexPath = NSIndexPath(forItem: 0, inSection: idx)
            // 获取其UICollectionViewLayoutAttributes
            let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath)
            // 如果有值, 就添加到UICollectionViewLayoutAttributes数组中去
            if let _ = layoutAttributes{
                answer.append(layoutAttributes!)
            }
        }

        // 遍历UICollectionViewLayoutAttributes数组, 更改header的值
        for layoutAttributes in answer {
            // 如果是header, 改变其参数
            if (layoutAttributes.representedElementKind==UICollectionElementKindSectionHeader) {
                // 获取header所在的section
                let section = layoutAttributes.indexPath.section
                // 获取section中cell总数
                let numberOfItemsInSection = collectionView!.numberOfItemsInSection(section)
                // 获取第一个item的IndexPath
                let firstObjectIndexPath = NSIndexPath(forItem: 0, inSection: section)
                // 获取最后一个item的IndexPath
                let lastObjectIndexPath = NSIndexPath(forItem: max(0, (numberOfItemsInSection - 1)), inSection: section)

                // 定义两个变量来保存第一个和最后一个item的layoutAttributes属性
                var firstObjectAttrs : UICollectionViewLayoutAttributes
                var lastObjectAttrs : UICollectionViewLayoutAttributes

                // 如果当前section中cell有值, 直接取出来即可
                if (numberOfItemsInSection > 0) {
                    firstObjectAttrs =
                        self.layoutAttributesForItemAtIndexPath(firstObjectIndexPath)!
                    lastObjectAttrs = self.layoutAttributesForItemAtIndexPath(lastObjectIndexPath)!
                } else { // 反之, 直接取header和footer的layoutAttributes属性
                    firstObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: firstObjectIndexPath)!
                    lastObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionFooter, atIndexPath: lastObjectIndexPath)!
                }
                // 获取当前header的高和origin
                let headerHeight = CGRectGetHeight(layoutAttributes.frame)
                var origin = layoutAttributes.frame.origin

                origin.y = min(// 2. 要保证在即将消失的临界点跟着消失
                    max( // 1. 需要保证header悬停, 所以取最大值
                        collectionView!.contentOffset.y  + collectionView!.contentInset.top,
                        (CGRectGetMinY(firstObjectAttrs.frame) - headerHeight)
                    ),
                    (CGRectGetMaxY(lastObjectAttrs.frame) - headerHeight)
                )

                // 默认的层次关系是0. 这儿设置大于0即可.为什么设置成1024呢?因为我们是程序猿...
                layoutAttributes.zIndex = 1024
                layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size)

            }

        }

        return answer;
    }

    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
        // 返回true, 表示一旦进行滑动, 就实时调用上面的-layoutAttributesForElementsInRect:方法
        return true
    }

}

⑩+@end: 整个项目, 东西依然蛮多的, 也不是独自几百上千字能说知道的,
大致每二个页面, 每三个文本, 我都有详实的华语注释. 希望大家齐声提升.
那也是自己的率先个开源的总体的Swift 项目, 有啥样不足只怕不当的地方,
希望大家提议来, 出色多谢!!!

⑩+①: 个人/专栏骨干: 那五个效益是同3个控制器, 是UICollectionViewController而不是UITableViewController

图片 33

大家对UITableViewControllerheader应当很熟知吧, 向上海滑稽剧团动的时候,
会停留在navigationBar的下面,
即便UICollectionViewController也可以设置header, 不过在iOS9以前,
他是不可能一贯设置停留的.在iOS9之后, 能够一行代码设置header的栖息

sectionHeadersPinToVisibleBounds = true

唯独在iOS9此前, 大家须要本人达成那个意义:

//
//  LevitateHeaderFlowLayout.swift
//  Floral
//
//  Created by ALin on 16/5/20.
//  Copyright © 2016年 ALin. All rights reserved.
//  可以让header悬浮的流水布局

import UIKit

class LevitateHeaderFlowLayout: UICollectionViewFlowLayout {
    override func prepareLayout() {
        super.prepareLayout()
        // 即使界面内容没有超过界面大小,也要竖直方向滑动
        collectionView?.alwaysBounceVertical = true
        // sectionHeader停留
        if #available(iOS 9.0, *) {
            sectionHeadersPinToVisibleBounds = true
        }
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        // 1. 获取父类返回的UICollectionViewLayoutAttributes数组
        var answer = super.layoutAttributesForElementsInRect(rect)!

        // 2. 如果是iOS9.0以上, 直接返回父类的即可. 不用执行下面的操作了. 因为我们直接设置sectionHeadersPinToVisibleBounds = true即可
        if #available(iOS 9.0, *) {
            return answer
        }

        // 3. 如果是iOS9.0以下的系统

        // 以下代码来源:http://stackoverflow.com/questions/13511733/how-to-make-supplementary-view-float-in-uicollectionview-as-section-headers-do-i%3C/p%3E
        // 目的是让collectionview的header可以像tableview的header一样, 可以停留

        // 创建一个索引集.(NSIndexSet:唯一的,有序的,无符号整数的集合)
        let missingSections = NSMutableIndexSet()
        // 遍历, 获取当前屏幕上的所有section
        for layoutAttributes in answer {
            // 如果是cell类型, 就加入索引集里面
            if (layoutAttributes.representedElementCategory == UICollectionElementCategory.Cell) {
                missingSections.addIndex(layoutAttributes.indexPath.section)
            }
        }

        // 遍历, 将屏幕中拥有header的section从索引集中移除
        for layoutAttributes in answer {
            // 如果是header, 移掉所在的数组
            if (layoutAttributes.representedElementKind == UICollectionElementKindSectionHeader) {
                missingSections .removeIndex(layoutAttributes.indexPath.section)
            }
        }

        // 遍历当前屏幕没有header的索引集
        missingSections.enumerateIndexesUsingBlock { (idx, _) in
            // 获取section中第一个indexpath
            let indexPath = NSIndexPath(forItem: 0, inSection: idx)
            // 获取其UICollectionViewLayoutAttributes
            let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath)
            // 如果有值, 就添加到UICollectionViewLayoutAttributes数组中去
            if let _ = layoutAttributes{
                answer.append(layoutAttributes!)
            }
        }

        // 遍历UICollectionViewLayoutAttributes数组, 更改header的值
        for layoutAttributes in answer {
            // 如果是header, 改变其参数
            if (layoutAttributes.representedElementKind==UICollectionElementKindSectionHeader) {
                // 获取header所在的section
                let section = layoutAttributes.indexPath.section
                // 获取section中cell总数
                let numberOfItemsInSection = collectionView!.numberOfItemsInSection(section)
                // 获取第一个item的IndexPath
                let firstObjectIndexPath = NSIndexPath(forItem: 0, inSection: section)
                // 获取最后一个item的IndexPath
                let lastObjectIndexPath = NSIndexPath(forItem: max(0, (numberOfItemsInSection - 1)), inSection: section)

                // 定义两个变量来保存第一个和最后一个item的layoutAttributes属性
                var firstObjectAttrs : UICollectionViewLayoutAttributes
                var lastObjectAttrs : UICollectionViewLayoutAttributes

                // 如果当前section中cell有值, 直接取出来即可
                if (numberOfItemsInSection > 0) {
                    firstObjectAttrs =
                        self.layoutAttributesForItemAtIndexPath(firstObjectIndexPath)!
                    lastObjectAttrs = self.layoutAttributesForItemAtIndexPath(lastObjectIndexPath)!
                } else { // 反之, 直接取header和footer的layoutAttributes属性
                    firstObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: firstObjectIndexPath)!
                    lastObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionFooter, atIndexPath: lastObjectIndexPath)!
                }
                // 获取当前header的高和origin
                let headerHeight = CGRectGetHeight(layoutAttributes.frame)
                var origin = layoutAttributes.frame.origin

                origin.y = min(// 2. 要保证在即将消失的临界点跟着消失
                    max( // 1. 需要保证header悬停, 所以取最大值
                        collectionView!.contentOffset.y  + collectionView!.contentInset.top,
                        (CGRectGetMinY(firstObjectAttrs.frame) - headerHeight)
                    ),
                    (CGRectGetMaxY(lastObjectAttrs.frame) - headerHeight)
                )

                // 默认的层次关系是0. 这儿设置大于0即可.为什么设置成1024呢?因为我们是程序猿...
                layoutAttributes.zIndex = 1024
                layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size)

            }

        }

        return answer;
    }

    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
        // 返回true, 表示一旦进行滑动, 就实时调用上面的-layoutAttributesForElementsInRect:方法
        return true
    }

}

⑩+@end: 整个项目, 东西如故蛮多的,
也不是独自几百上千字能说清楚的, 差不离每多少个页面, 每1个文书,
笔者都有详尽的国语注释. 希望大家齐声升高.
那也是本身的首先个开源的完全的Swift 项目, 有怎么着不足可能不当的地方,
希望大家建议来, 相当感谢!!!

⑩+①: 个人/专栏主导: 那多个效益是同一个控制器, 是UICollectionViewController而不是UITableViewController

图片 34

大家对UITableViewControllerheader应当很纯熟吧, 向上海滑稽剧团动的时候,
会停留在navigationBar的上面,
固然UICollectionViewController也足以设置header, 不过在iOS9在先,
他是不能够直接设置停留的.在iOS9然后, 可以一行代码设置header的栖息

sectionHeadersPinToVisibleBounds = true

只是在iOS9在此之前, 大家需求团结达成这一个作用:

//
//  LevitateHeaderFlowLayout.swift
//  Floral
//
//  Created by ALin on 16/5/20.
//  Copyright © 2016年 ALin. All rights reserved.
//  可以让header悬浮的流水布局

import UIKit

class LevitateHeaderFlowLayout: UICollectionViewFlowLayout {
    override func prepareLayout() {
        super.prepareLayout()
        // 即使界面内容没有超过界面大小,也要竖直方向滑动
        collectionView?.alwaysBounceVertical = true
        // sectionHeader停留
        if #available(iOS 9.0, *) {
            sectionHeadersPinToVisibleBounds = true
        }
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        // 1. 获取父类返回的UICollectionViewLayoutAttributes数组
        var answer = super.layoutAttributesForElementsInRect(rect)!

        // 2. 如果是iOS9.0以上, 直接返回父类的即可. 不用执行下面的操作了. 因为我们直接设置sectionHeadersPinToVisibleBounds = true即可
        if #available(iOS 9.0, *) {
            return answer
        }

        // 3. 如果是iOS9.0以下的系统

        // 以下代码来源:http://stackoverflow.com/questions/13511733/how-to-make-supplementary-view-float-in-uicollectionview-as-section-headers-do-i%3C/p%3E
        // 目的是让collectionview的header可以像tableview的header一样, 可以停留

        // 创建一个索引集.(NSIndexSet:唯一的,有序的,无符号整数的集合)
        let missingSections = NSMutableIndexSet()
        // 遍历, 获取当前屏幕上的所有section
        for layoutAttributes in answer {
            // 如果是cell类型, 就加入索引集里面
            if (layoutAttributes.representedElementCategory == UICollectionElementCategory.Cell) {
                missingSections.addIndex(layoutAttributes.indexPath.section)
            }
        }

        // 遍历, 将屏幕中拥有header的section从索引集中移除
        for layoutAttributes in answer {
            // 如果是header, 移掉所在的数组
            if (layoutAttributes.representedElementKind == UICollectionElementKindSectionHeader) {
                missingSections .removeIndex(layoutAttributes.indexPath.section)
            }
        }

        // 遍历当前屏幕没有header的索引集
        missingSections.enumerateIndexesUsingBlock { (idx, _) in
            // 获取section中第一个indexpath
            let indexPath = NSIndexPath(forItem: 0, inSection: idx)
            // 获取其UICollectionViewLayoutAttributes
            let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath)
            // 如果有值, 就添加到UICollectionViewLayoutAttributes数组中去
            if let _ = layoutAttributes{
                answer.append(layoutAttributes!)
            }
        }

        // 遍历UICollectionViewLayoutAttributes数组, 更改header的值
        for layoutAttributes in answer {
            // 如果是header, 改变其参数
            if (layoutAttributes.representedElementKind==UICollectionElementKindSectionHeader) {
                // 获取header所在的section
                let section = layoutAttributes.indexPath.section
                // 获取section中cell总数
                let numberOfItemsInSection = collectionView!.numberOfItemsInSection(section)
                // 获取第一个item的IndexPath
                let firstObjectIndexPath = NSIndexPath(forItem: 0, inSection: section)
                // 获取最后一个item的IndexPath
                let lastObjectIndexPath = NSIndexPath(forItem: max(0, (numberOfItemsInSection - 1)), inSection: section)

                // 定义两个变量来保存第一个和最后一个item的layoutAttributes属性
                var firstObjectAttrs : UICollectionViewLayoutAttributes
                var lastObjectAttrs : UICollectionViewLayoutAttributes

                // 如果当前section中cell有值, 直接取出来即可
                if (numberOfItemsInSection > 0) {
                    firstObjectAttrs =
                        self.layoutAttributesForItemAtIndexPath(firstObjectIndexPath)!
                    lastObjectAttrs = self.layoutAttributesForItemAtIndexPath(lastObjectIndexPath)!
                } else { // 反之, 直接取header和footer的layoutAttributes属性
                    firstObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: firstObjectIndexPath)!
                    lastObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionFooter, atIndexPath: lastObjectIndexPath)!
                }
                // 获取当前header的高和origin
                let headerHeight = CGRectGetHeight(layoutAttributes.frame)
                var origin = layoutAttributes.frame.origin

                origin.y = min(// 2. 要保证在即将消失的临界点跟着消失
                    max( // 1. 需要保证header悬停, 所以取最大值
                        collectionView!.contentOffset.y  + collectionView!.contentInset.top,
                        (CGRectGetMinY(firstObjectAttrs.frame) - headerHeight)
                    ),
                    (CGRectGetMaxY(lastObjectAttrs.frame) - headerHeight)
                )

                // 默认的层次关系是0. 这儿设置大于0即可.为什么设置成1024呢?因为我们是程序猿...
                layoutAttributes.zIndex = 1024
                layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size)

            }

        }

        return answer;
    }

    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
        // 返回true, 表示一旦进行滑动, 就实时调用上面的-layoutAttributesForElementsInRect:方法
        return true
    }

}

⑩+@end: 整个项目, 东西还是蛮多的,
也不是独自几百上千字能说精晓的, 差不离每3个页面, 每多少个文件,
作者都有详细的国语注释. 希望大家齐声提高.
那也是笔者的第①个开源的一体化的Swift 项目, 有什么样不足大概失实的地点,
希望我们提出来, 格外谢谢!!!

下载地址

<a href=”https://github.com/SunLiner/Floral"&gt;github地址&lt;/a&gt;

倘使对您有微微支持, 请<a
href=”https://github.com/SunLiner/Floral"&gt;☆star&lt;/a&gt;

下载地址

github地址

假若对您有微微支持, 请☆star

下载地址

github地址

比方对您有微微协理, 请☆star

后续

唯恐有点效率模块存在bug, 后续笔者都会相继举行修复和完美的,
并更新在github上.

假诺你有其他难题,恐怕发现bug以及不足的地点, 能够在上边给自家留言,
只怕关心本身的今日头条博客园, 给自己私信.

后续

或许有点效率模块存在bug, 后续笔者都会挨个实行修补和周密的,
并更新在github上.

一旦您有任何疑问,或许发现bug以及不足的地点, 能够在底下给本人留言,
只怕关怀自作者的天涯论坛今日头条,
给自家私信.

后续

恐怕有个别效用模块存在bug, 后续小编都会相继进行修复和完美的,
并更新在github上.

即便你有其他难题,恐怕发现bug以及不足的地点, 能够在上面给自身留言,
或许关心本人的新浪今日头条,
给本人私信.

联系我

<a href=”https://github.com/SunLiner"&gt;github&lt;/a&gt;

<a
href=”http://www.weibo.com/5589163526/profile?rightmod=1&wvr=6&mod=personinfo&is\_all=1"&gt;微博&lt;/a&gt;

<a
href=”http://www.jianshu.com/users/9723687edfb5/latest\_articles"&gt;简书&lt;/a&gt;

联系我

github

搜狐和讯

简书

联系我

github

新浪博客园

简书

相关文章