仿写一个完完全全项目.仿写一个一体化项目.仿写一个一体化项目.

前言

决续续的早已上Swift同年差不多了, 从1.2交本的2.2,
一直当语法之间徘徊, 学一段时间, 工作同疲于奔命,
再捡起来隔段时而忘记了.思来纪念去, 趁着即片独月加班不是特别多,
就决定用swift仿写一个一体化项目.

花田小憩:是一个植物美学生活平台,
以自然生活为主导,
提倡植物学生活方法,
倡导美学标准的生活态度的一个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录制的早晚, 会重新渲染一举图片,
所以导致品种遭到因故到高斯模糊的地方, 看起感觉比较乱,
实际效果还是对的.

新特性

首页

详情页

评论

分享

杂货铺首页

杂货铺详情页

货物搜索

支付

图形浏览器

专栏作家

民用核心

路有截图

由种类的光景功能都曾经落实了之, 所以整个项目还是于大的.所以,
下面罗列有机能的截图.
鉴于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: 之前编译环境这来接触错, 因为自身种中之所以了Swift2.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下富含7独效益目录:

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文件结构,
还是那个方便的.

目录结构详解

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, 只需要监听最后一个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然后让详情页发送一个通告,
让那刷新界面. 暂时无想到再好的方, 如果您来双重好之做法, 请务必告诉自己,
谢谢…

⑤ 主题详情页:商城详情页的做法为是大半的, 不了更简便一点.

图片 27

要一点在乎, 详情页的示重大借助于H5页面.
而我辈用根据webview的高度来确定webviewCell的高度.我的做法是监听UIWebViewwebViewDidFinishLoad,
取出webView.scrollView.contentSize.height接下来于详情页发送一个通报,
让该刷新界面. 暂时没想到再好的章程, 如果您有再度好之做法, 请务必告诉自己,
谢谢…

⑤ 主题详情页:商城详情页的做法呢是多的, 不了更简便易行一点.

图片 28

重点一点在乎, 详情页的显得重大靠于H5页面.
而我们需要根据webview的可观来规定webviewCell的高度.我之做法是监听UIWebViewwebViewDidFinishLoad,
取出webView.scrollView.contentSize.height接下来叫详情页发送一个通,
让其刷新界面. 暂时没想到再好之章程, 如果您发出重好的做法, 请务必告诉我,
谢谢…

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
    }

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
    }

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中一度意识, 花田小憩吃的报到/注册/忘记密码界面几乎是同等的, 我的做法是因此一个控制器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中既发现, 花田小憩蒙的登录/注册/忘记密码界面几乎是一律的, 我之做法是用一个控制器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中被大家留下了一个开关,
可以高速的拓展登录状态的切换…

⑩ 登录状态.

俺们好透过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 项目, 有什么不足或不当的地方,
希望大家指出来, 万分感激!!!

⑩+①: 个人/专栏基本: 这半只作用是同一个控制器, 是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: 整个项目, 东西要不行多之,
也非是一味几百达主配能说亮的, 几乎每一个页面, 每一个文件,
我还来详尽的华语注释. 希望大家一同进步.
这也是自的首先个开源之完整的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: 整个项目, 东西要十分多之,
也不是仅仅几百高达总许会说理解的, 几乎每一个页面, 每一个文本,
我都来详细的中文注释. 希望大家并进步.
这为是自之第一只开源之完整的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

初浪微博

简书

相关文章