iOS 高仿:花田小憩3.0.1 (上)

iOS 高仿:花田小憩3.0.1 (上) 



前言


断断续续的已经学习Swift一年多了, 从1.2到现在的2.2, 一直在语法之间徘徊, 学一段时间, 工作一忙, 再捡起来隔段时间又忘了.思来想去, 趁着这两个月加班不是特别多, 就决定用swift仿写一个完整项目.


花田小憩:是一个植物美学生活平台,

以自然生活为主导,

提倡植物学生活方法,

倡导美学标准的生活态度的一个APP.


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


github地址

https://github.com/SunLiner/Floral


声明


此花田小憩项目里面的都是真实接口, 真实数据, 仅供学习, 毋作其他用途!!!


项目部分截图


由于项目的大体功能都已经实现了的, 所以整个项目还是比较庞大的.所以, 下面罗列部分功能的截图.

由于gif录制的时候, 会重新渲染一遍图片, 所以导致项目中用到高斯模糊的地方, 看起来感觉比较乱, 实际效果还是不错的.


新特性


详情页



更多项目截图请点击原文查看


项目环境


编译器 : Xcode7.3及以上


语言 : Swift2.2


整个项目都是采用纯代码开发模式


tip: 之前编译环境这儿有点错误, 因为我项目中用了Swift2.2的特性, 2.2之后方法名需要写成#selector(AddAddressViewController.save), 不再使用双引号了


第三方框架


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


项目部分功能模块详解


① 新特性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 : 在这个项目中, 没有用第三方的下拉刷新控件, 而是自己实现了一个简单的下拉刷新轮子, 然后赋值给UITableViewController的public 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图中的高斯模糊效果不是很明显, 实际效果特别好.



④ 商城购物车动画:这组动画还是比较简单的, 直接附代码, 如果有什么疑惑, 可以留言或者私信我


// 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的高度.我的做法是监听UIWebView的webViewDidFinishLoad, 取出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

    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值