iOS高仿:花田小憩3.0.1

作者: Monkey_ALin | 来源:发表于2016-06-09 15:10 被阅读9989次

    前言

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

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

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

    github地址

    <a href="https://github.com/SunLiner/Floral">github地址</a>

    声明

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

    项目部分截图

    由于项目的大体功能都已经实现了的, 所以整个项目还是比较庞大的.所以, 下面罗列部分功能的截图.
    由于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 : 在这个项目中, 没有用第三方的下拉刷新控件, 而是自己实现了一个简单的下拉刷新轮子, 然后赋值给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图中的高斯模糊效果不是很明显, 实际效果特别好.

    高斯模糊

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

    // 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然后给详情页发送一个通知, 让其刷新界面. 暂时没有想到更好的方法, 如果您有更好的做法, 请务必告诉我, 谢谢...

    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)
        }
    
    

    ⑧ 验证码的倒计时功能

    验证码倒计时
    /// 点击"发送验证码"按钮
        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: 此功能测试的时候, 必须用真机!!!

    ⑩ 登录状态.

    我们可以通过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 项目, 有什么不足或者错误的地方, 希望大家指出来, 万分感激!!!

    下载地址

    <a href="https://github.com/SunLiner/Floral">github地址</a>

    如果对您有些许帮助, 请<a href="https://github.com/SunLiner/Floral">☆star</a>

    后续

    可能有些功能模块存在bug, 后续我都会一一进行修复和完善的, 并更新在github上.

    如果您有任何疑问,或者发现bug以及不足的地方, 可以在下面给我留言, 或者关注我的新浪微博, 给我私信.

    联系我

    <a href="https://github.com/SunLiner">github</a>

    <a href="http://www.weibo.com/5589163526/profile?rightmod=1&wvr=6&mod=personinfo&is_all=1">微博</a>

    <a href="http://www.jianshu.com/users/9723687edfb5/latest_articles">简书</a>

    相关文章

      网友评论

      • 健健锅:能不能把接口 贴出来
      • 一个不太努力的代码搬运工:大神可否升级3.0,我自己弄了半天还是出错,不知道怎么回事
      • 浮桥小麦:楼主,你抓包的接口有列一个接口文档吗? 我想自己弄个OC的练手
      • c48109346fc9:swift3项目编译报错...
      • 乂滥好人:我想问下,现在下拉刷新上拉加载除了自带的,swift有什么比较好的开源库吗?
      • 励志当学霸的皮皮:厉害!真心感谢博主分享!
      • 柯柯格格:问一下,这种项目的资源都是拿的IPA包,还是反编译的??? :pray:
      • return以诺:大兄弟,留个联系方式
      • d80c89a0af81:可不可以更新下~适配swift 3.0 T_T
      • 述己:想写这个,有接口吗??
      • 若小北00:用Xcode 8打开会有很多报错,不知道你那边有没有这种情况,应该是跟swift3.0有关吧,但不知道怎么去修改
      • Coder2_google:非常感谢分享。
      • 77ba8e0a2ddc:博主,这个花田小憩的接口地址...你有记录么,如果有15300086553@163.com ,发给我可好,我想自己用OC写写..谢谢!
      • 幸福的尾巴__:目前报错.....报错信息
        2016-09-06 21:35:29.347 Floral[7628:319685] -[__NSCFString longValue]: unrecognized selector sent to instance 0x7b168e50
        2016-09-06 21:35:29.355 Floral[7628:319685] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString longValue]: unrecognized selector sent to instance 0x7b168e50'
        *** First throw call stack:
        代码行
        init(dict: [String: AnyObject]) {
        super.init()
        setValuesForKeysWithDictionary(dict)
        }
        全局大断点崩溃处 setValuesForKeysWithDictionary(dict)
      • kinmo:这些小轮子可以啊 :sunglasses:
      • 5e394c4049d1:接此学习下swift
      • 政揚:做的實在很唯美,很精緻,讓小弟大開眼界了!謝謝分享!持續關注~~
      • 迷路的安然和无恙:你好,这个项目内存好高啊!专题那栏随便点开一个,内存飙升到800多兆....这个大神是否考虑优化一下
      • 迷路的安然和无恙:看你的源码是幸福的.....写的清晰有条理!大神
      • 迷路的安然和无恙: :flushed: 你真很厉害!请多指教!
      • 遛狗的猫:最近在写一个类似的项目,很有参考价值
      • ChinaSwift:准备下载看,已经开始感觉到博主的用心良苦了 :blush:
      • bf6662411ce1:博主是不是处女座的`~哈哈 感谢分享.在学校代码中 :smiley:
      • 风衫码农:[<Floral.ADS 0x7fa6815437e0> setNilValueForKey]: could not set nil as the value for the key fnOrder.'

        点击tabBar上mails按钮就不崩溃 崩溃在这一行
        // 商品的h5地址(只剩"df277edb-a0c6-43fb-919a-cf2a9ac7e952", 需要自己拼接)
        var fnUrl : String?


        init(dict:[String : AnyObject]) {
        super.init()
        setValuesForKeysWithDictionary(dict)
        }




        Monkey_ALin:@风衫码农 已经修复。
      • loser_dog:感谢前辈的无私奉献..如果有OC版本就更好了.
      • 你的小福蝶:大神,我在研究你的这个项目,楼主是有多喜欢CollectionViewController啊,各种深度定制,简直6的不行
        Monkey_ALin:@开心刘哈哈 :sweat: 是根据需求来使用的...真的很多吗, O(∩_∩)O哈哈~
      • 480a52903ce5: 获得置顶的商品这里 let dict = (result as! Array).first! as [String : AnyObject] 出现崩溃情况
        Monkey_ALin:@叫我干苦力的小码农 已经修复。
      • 480a52903ce5:点击商城界面 就会出现崩溃了, 博主
      • Zero1:您好,Theme页面有个小BUG。滚动tableView的时候,点击左上角按钮弹出BlurView,布局还是会出现不正确的情况~我检查了一下约束没问题啊 :flushed:
      • 飞羽田海:楼主 你的商城 积分部分的数据能抓取到吗?
      • 清無:github上提了issue
      • 标准答案:好像是第三方的那个
      • 标准答案:为什么会报红啊
      • 大同若鱼:伟大的卤煮大人,正在学习您的代码,请问:
        // MARK: - 生命周期相关
        override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupUI()
        }
        这句话一定要加吗?
        大同若鱼:@Monkey_ALin thx ^_^
        Monkey_ALin:@b8cf4b201714 初始化UI, 你也可以加在其他地方. 不一定要在控制器初始化方法中.
      • 清無:十分详细,值得学习学习,感谢无私分享。。。待找bug
        Monkey_ALin:@菲拉兔 :smiley: 我喜欢你这名字. 欢迎issue
      • d76cff7a9ce8:感谢大神
      • 崽orz:求出教程啊!!!!!!
        崽orz:@Monkey_ALin 可以啊~~~
        Monkey_ALin:@崽orz :sweat: 什么教程? 录成视频吗?
      • 黄大厨:本地化下就更完美了!
      • 日出东方一片红:报了ios的培训班,有朋友说ios人太多了,让换html5,如何是好?
        日出东方一片红:@飞羽田海 我换了h5,补了5600得差价
        日出东方一片红:@飞羽田海 ios吗?你都学完了?
        飞羽田海:@Zezood 果断换啊 我马上就培训完了 学的还可以 还不是等于0
      • 古正龙国君:楼主,你真是太厉害了,待读了源码后会请教你一些问题。 :stuck_out_tongue_winking_eye:
      • JC_Wang:期待大神直播项目 :stuck_out_tongue_winking_eye:
      • MarkTang:写的很好感谢分享
      • 480a52903ce5:谢谢分享。
      • 奋斗的菜鸟:这种定时器的用法相比NSTimer有什么优点吗?
        奋斗的菜鸟:@Monkey_ALin 扫噶
        Monkey_ALin:@奋斗的菜鸟 计时更精确, 更底层一些. 逼格高一些...
      • aece04d72b25:非常感谢分享,提供了一个非常好的学习机会 :+1: :+1: :+1:
      • 汉斯哈哈哈:林哥这么厉害,先Mark,改天读读
      • fce2a3ecd48a:感谢分享
      • seky:感谢你的分享 :smiley:
      • cf1fea187f0e:学习了, 谢谢博主分享!
      • 那一处风景ljz:就对大神,膜拜
      • 孙柯佳人:真厉害 写的不错 :+1:
      • 诸葛俊伟:非常感谢博主分享,给我这样的 swift 新手提供了一个非常好的学习机会。额。。我有个小小的建议,文章中markdown可以用一用加粗斜体之类的,`code`这个还是比较适合用在代码上。再次感谢!
        Monkey_ALin:@诸葛俊伟 :smiley: 谢谢哈. 下次试试
      • 1d1146c7943d:感谢分享!
      • c93d5c7594f4:正在学习,多谢楼主分享,楼主还有其他oc的项目吗?
        翀鹰精灵:@Monkey_ALin 直播,期待……
        翀鹰精灵:@Monkey_ALin 哇塞,直博,oc写的吗?期待期待期待……
        Monkey_ALin:@jelwell 近期会有一个直播类的OC Demo开源出来,要写完了...
      • e2f2d779c022:写的很不错,数据没有做本地化吗?接下来有没有考虑做进去?
        Monkey_ALin:@NewPan 现在数据没有做本地化. 近期我会加上的, 这个项目我会逐步进行完善的.O(∩_∩)O谢谢支持
      • leacode:收藏了,加油哦
      • 6991a5a482e5:收藏啦,赞
      • xiaoheng:下载学习 感谢一下作者哈。
      • leonbaichi:真厉害!
      • iOSCheese:问一下登陆注册的后端服务器数据库 是怎么解决的,你是有自己的服务器后端吗
        Monkey_ALin:@iOSCheese 这个只是运气好而已……他们的加密比较简单,就一个MD5简单加密!
        iOSCheese:@Monkey_ALin 啊?你连加密算法都能破解呀
        Monkey_ALin:@iOSCheese 因为我是抓包获取的花田小憩官方的接口,只需要知道加密算法就好了。
      • xingou:新增地址的手机号码正则不对,^1[34589]\\d{9}$ ,我的手机号就是177开头的
        Monkey_ALin:@巴拉拉噢噢 哈哈,失误,我一会微改一下!
      • 常义:嫌swift语法老更新,所以没怎么去学,看到你已经用的这么成熟,向你学习!
        405b0a0f8845:@常义 同意加1
        常义: @Monkey_ALin 👍你很厉害
        Monkey_ALin:@常义 看语法容易忘得快,还是找个项目练练手,很快就能掌握了。我也是小菜一枚
      • 44d3387e09f3:好像github不能打开呢
        Monkey_ALin:@Marcello1993 需要Xcode7.2以上,打开.xcworkplace,不是.xcodeproj哦。如果还有错误的话,提供一下报错信息,我看一下……谢谢!
        44d3387e09f3:打开了,但是编译不通过
        Monkey_ALin:@Marcello1993 网址打不开吗?还是项目打不开?
      • 极限Coder:真不错O(∩_∩)O哈哈~,大牛啊.
        Monkey_ALin:@极限Coder :blush: 谢谢!我不是什么大牛哦, 我也是小菜一枚

      本文标题:iOS高仿:花田小憩3.0.1

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