高仿小日子Swift2.0

不知不觉十一马上到了,承诺给大家分享的Swift项目也该兑现了,本次项目为高仿小日子,废话不多说,先看效果吧


探店效果图
探店效果图

探店详情页效果图
探店详情页效果图

附近地图效果图
附近地图效果图

体验效果图
体验效果图

分类效果图
分类效果图

我的效果图
我的效果图

搜索效果图
搜索效果图

摇一摇效果图
摇一摇效果图

更多效果请用Xcode7.0正式版运行程序查看

此项目相对来说比较简单,之前用Swift1.2编写的,9月18号,苹果发布了Xcdoe7.0正式版,小熊也将代码更新到了Swift2.0,由于Swift语言还不稳定,每个版本都会出现语法修改,本项目用最新的Xcode7正式版编写,建议使用Xcode7正式版运行工程,项目的接口依然有加密,抱着学习的态度,小熊截取了网络请求返回数据,并且将数据写入到了本地,所以读者用到的数据都是固定的

本来想挑一个复杂点的项目分享给大家,考虑到公司的项目要在十一前上线,时间比较紧,以及自己对Swift语言还处于摸索阶段,经常也是连蒙加猜的,很多写法还是沿用OC的套路,不过相信对于大家学习Swift语言还是有一定帮助的.很多时候,小熊都是被一个很简单的语法卡住很久,比如如何动态实例化AnyClass对象,还有升级到2.0后,通过NSStringFromClass:方法创建对象是需要追加命名空间的前缀,我记忆很深刻...

此次写代码的过程中吸取了读者的提议,用的源代码管理工具是git,大家可以在项目的历史版本回退查看项目的从无到有是如何一步一步完善的,由于白天上班,晚上写这个项目,所以中间博客没有怎么更新,读者也可以从代码提交时间看出...都是半夜1 . 2点左右

在学习Swift语言中,自己也断断续续的总结了一些OC和Swift语法的区别,以及Swift于OC中相同方法的不用写法,我也会在后续时间发布到博客里,让大家避免不必要的麻烦,请持续关注小熊的博客


下面来介绍此项目的每个模块的思路,这里就按照程序运行展示的页面的顺序逐一与大家分析吧

引导页(新特性页面)

  • 引导页只在用户第一次运行程序,或者APP升级后第一次运行程序时候出现,为了是提示用户APP的新功能以及改变如下图所展示的效果

    引导页
    引导页

  • 目前比较主流的方法是使用UICollectionview来实现引导页可以直接复用,如果只是单纯的图片和简单的动画建议使用UICollectionview,如果有比较复杂的动画,也可以用ScrollView来实现,根据项目的不同需求来选择,这里的引导页只有一页,我就直接用的View

  • 在程序启动成功的方法中,首先判断用户是否是第一次运行当前版本的APP,我的做法是用版本号来判断,首先在info中拿出当前程序的版本号,在从UserDefaults取出上次用户运行程序的版本号对比,如果相等,说明没有新版本,直接进入,如果不相等则显示导引页,并且将当前版本写入UserDefaults中,以便下次用户运行APP判断.具体代码如下,返回的控制器作为keyWindow的rootViewController,如果是引导页,并且用户点击了体验按钮,可以发出通知,将KeyWindow的rootViewController切换即可

privete func showLeadpage() -> UIViewController {
    let versionStr = "CFBundleShortVersionString"
    let cureentVersion = NSBundle.mainBundle().infoDictionary![versionStr] as! String
    let oldVersion = (NSUserDefaults.standardUserDefaults().objectForKey(versionStr) as? String) ?? ""

    if cureentVersion.compare(oldVersion) == NSComparisonResult.OrderedDescending {
    NSUserDefaults.standardUserDefaults().setObject(cureentVersion, forKey: versionStr)
    NSUserDefaults.standardUserDefaults().synchronize()
    return LeadpageViewController()
    }

UITabbarController(KeyWindow.rootViewController)

  • 这里小熊自己定义的MainTabBarController继承至UITabBarController,里面装了四个MainNavigationController继承UINavigationController作为子控制器
  • MainNavigationController继承UINavigationController,重写了pushViewController方法,方便统一管理返回按钮和每次Push出新的控制器,自动隐藏底部的TabBar,具体代码如下
 override func pushViewController(viewController: UIViewController, animated: Bool) {

    if self.childViewControllers.count > 0 {
        let vc = self.childViewControllers[0]

        if self.childViewControllers.count == 1 {
            backBtn.setTitle(vc.tabBarItem.title!, forState: .Normal)
        } else {
            backBtn.setTitle("返回", forState: .Normal)
        }

        viewController.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backBtn)
        viewController.hidesBottomBarWhenPushed = true
    }

    super.pushViewController(viewController, animated: animated)
}

左上角的城市

  • 由于探店, 体验, 分类都有城市选择按钮,并且一个改变,其余的也都会改变,我的做法是提供一个父类,MainViewController继承至UIViewController,设置navigationItem.leftBarButtonItem为自定义的按钮,并且在viewDidLoad:方法中,添加通知,监听city的改变,一旦监听到当前城市发生改变的通知后,对应的控制器就可以执行对应的操作,需要注意的是每次要将改变后的城市写入到本地持久化存储,以便程序被关闭后再次运行时,可以保留上次用户所选择的城市
  • 选择城市的控制器是CityViewController如下图,城市的展示小熊用的UICollectionview来展示的,需要注意每次ViewController弹出后,用户上一次选择的城市都会自动进入选中状态,具体实现是拿出上一次用户选择的城市,遍历城市列表取出所属的indexPath,然后执行下面方法
    let lastSelectedCityIndexPaht = selectedCurrentCity()
    collView.selectItemAtIndexPath(lastSelectedCityIndexPaht, animated: true, scrollPosition: UICollectionViewScrollPosition.None)
城市选择控制器
城市选择控制器
  • 当用户再次选择城市后,将城市写入到本地保存,并且发出城市改变的通知,通知监听者做出相应的操作

探店如下图所示

探店效果图
探店效果图
  • 导航条上的美天和美辑是将navigationItem.titleView 设置为自定义的DoubleTextView来实现,内部封装好功能,并且通过设置代理将点击事件传递给控制器,顺便提一嘴,Swift中的代理和OC中的一样,也是采用软引用即:weak var delegate: <Delegate>?,提供一个方法便于在scrollView的offsetX发生改变时更新内部控件状态具体实现请参考项目中的代码

  • 在view的最底层添加一个scrollView,设置scorllView的contentSize为屏幕的宽度的2倍,在scrollView上添加俩个TableView,分别是美天的TableView和美辑的TabelView,注意观察,美天tableViewCell有一种cell的样式于美辑的样式是一样的,所以这里只用了两种cell,参考下图,这里是根据服务器返回数据来判断加载哪一种cell,美天的tableView采用的是样式是Group样式,每一组有一个或者多个cell


    EventCell样式效果图
    EventCell样式效果图

    ThemeCell样式效果图
    ThemeCell样式效果图
  • 在TableView的代理方法中,根据服务器返回的数据创建对应的cell,喜欢专研的朋友可以查看项目中CustomData文件中的events.json文件查看规律

  • 这里提一下Swift的闭包方法中,需要像OC中一样创建一个weak的指针weak var tmpSelf = self,在闭包方法中调用tmpSelf,直接调用self会引起循环引用,照成内存泄露,控制器无法被销毁问题

  • 点击EventCell弹出的详情页我着重讲一下,先看效果图


    美天详情效果图
    美天详情效果图
  • 顶部的导航条是自定义的view,在EventViewController的viewWillAppaer方法中,将系统的导航条隐藏掉,添加定义的view作为导航条,按钮也是如此,根据scrollView的偏移量计算透明度,图片的拉伸和按钮的图片改变,由于有俩个scrollView,有些效果需要判断不同scrollView来执行,需要注意的是适当添加标记来防止重复判断,消耗内存

  • 店发现和店详情掌握之前提的导航条titelView也可以轻松搞定,位置也是根据scrollView的Y轴的偏移量来计算,代码并不复杂,具体代码如下


extension EventViewController: UIScrollViewDelegate {

    func scrollViewDidScroll(scrollView: UIScrollView) {

        // 解决弹出新的控制器后返回后contentSize自动还原的问题
        if loadFinishScrollHeihgt > webView.scrollView.contentSize.height && scrollView === webView.scrollView {
            webView.scrollView.contentSize.height = loadFinishScrollHeihgt
        }

        let offsetY: CGFloat = scrollView.contentOffset.y
        // 判断顶部自定义导航条的透明度,以及图片的切换
        customNav.alpha = 1 + (offsetY + NavigationH + EventViewController_ShopView_Height) / scrollShowNavH
        if offsetY + EventViewController_ShopView_Height >= -NavigationH && showBlackImage == false {
            backBtn.setImage(UIImage(named: "back_1"), forState: .Normal)
            likeBtn.setImage(UIImage(named: "collect_1"), forState: .Normal)
            sharedBtn.setImage(UIImage(named: "share_1"), forState: .Normal)
            showBlackImage = true
        } else if offsetY < -NavigationH - EventViewController_ShopView_Height && showBlackImage == true {
            backBtn.setImage(UIImage(named: "back_0"), forState: .Normal)
            likeBtn.setImage(UIImage(named: "collect_0"), forState: .Normal)
            sharedBtn.setImage(UIImage(named: "share_0"), forState: .Normal)
            showBlackImage = false
        }

        // 顶部imageView的跟随动画
        if offsetY <= -DetailViewController_TopImageView_Height - EventViewController_ShopView_Height {
            topImageView.frame.origin.y = 0
            topImageView.frame.size.height = -offsetY - EventViewController_ShopView_Height
            topImageView.frame.size.width = AppWidth - offsetY - DetailViewController_TopImageView_Height
            topImageView.frame.origin.x = (0 + DetailViewController_TopImageView_Height + offsetY) * 0.5
        } else {
            topImageView.frame.origin.y = -offsetY - DetailViewController_TopImageView_Height - EventViewController_ShopView_Height
        }

        // 处理shopView
        if offsetY >= -(EventViewController_ShopView_Height + NavigationH) {
            shopView.frame = CGRect(x: 0, y: NavigationH, width: AppWidth, height: EventViewController_ShopView_Height)
        } else {
            shopView.frame = CGRect(x: 0, y: CGRectGetMaxY(topImageView.frame), width: AppWidth, height: EventViewController_ShopView_Height)
        }

        // 记录scrollView最后的偏移量,用于切换scrollView时同步俩个scrollView的偏移值
        lastOffsetY = offsetY
    }
}

  • 店发现 这里相对来说有点麻烦,这里服务器返回的是好长的一串htmlString,由于没有css的布局文件,直接加载htmlStr的话图片是超级的大,有的像素达到4480*3000,直接用webView.loadHTMLString:的方法很不现实,这里我放一段服务器返回的字符串给读者看一下如下图

    html返回的字符串
    html返回的字符串

  • 由于没有css的布局文件,只能靠自己来约束图片的大小,我采用的方法是通过正则语句过滤出所有图片的宽和高,然后按照屏幕的宽度等比例计算图片的大小,在将修改过的宽和高替换插入到原有的htmlStr中,观察服务器返回的htmlStr是没有title和tag的,也就意味这展示的内容没有标题和标签,所以再动态的将title以及tag的标签插入到字符串中插入代码如下

                if model?.title != nil {
                    titleStr = String(format: "<p style='font-size:20px;'> %@</p>", model!.title!)
                }

                if model?.tag != nil {
                    titleStr = titleStr?.stringByAppendingFormat("<p style='font-size:13px; color: gray';>%@</p>", model!.tag!)
                }
  • 再加载修改过图片宽高的htmlStr,然后webViewDidFinishLoad的方法中,通过JS代码将内容的背景色修改,此种方法瑕疵很多,代码如下
webView.stringByEvaluatingJavaScriptFromString("document.getElementsByTagName('body')[0].style.background='#F5F5F5';")
  • 对webView图片的处理,小熊感觉还有一个更好的思路可以尝试,不过在项目中没有实现成功,这里我觉得可以动态的将所有的的htmlStr中image都拦截掉,不让webView自己加载图片,并且通过条件过滤保留图片的url地址,动态的计算出图片的位置和大小,用本地加载图片的方法加载图片,放到对应webView.scrollView的位置,这样就可以实现缓存,以及点击等事件,不过需要于js端的配合,这个以后我一定会再尝试的,可行的话我会将demo分享给大家

  • 在webView的最底部,也会根据服务器返回的数据动态的加入猜你喜欢的cell,这里只需要在webView加载完毕后,确认webView.scrollView.contentSize.height后面添加对应的moreView即可!需要注意的是,添加内容后,也要相应的增加webView.scrollView.height的高度用来显示新添加的控件

附近(效果如下图所示)

附近地图效果图
附近地图效果图
  • 在程序启动成功的方法中,首先会获取用户授权使用定位功能,这里我封装了一个管理用户位置的单例,当用户授权App可以使用定位后,获取用户当前的经纬度并且将用户位置的经纬度保存,当用户点击附近按钮弹出控制器时,发送网络请求给服务器,服务器通过用户当前的经纬度返回附近店铺的信息,由于没有数据,所以每次返回的都是小熊写在本地的数据,解析对应的数据加载到tableView显示,通过用户的位置以及店铺的位置可以计算出距离给label设置用户当前距离店铺的距离KM
  • 翻转动画,根据右上角按钮的状态使用下面方法完成翻转动画
        if sender.selected {
            UIView.transitionFromView(nearTableView, toView: mapView, duration: 1.0, options: UIViewAnimationOptions.TransitionFlipFromLeft, completion: nil)
        } else {
            UIView.transitionFromView(mapView, toView: nearTableView, duration: 1.0, options: UIViewAnimationOptions.TransitionFlipFromRight, completion: nil)
        }
  • mapView中,有一个UICollectionview,一个定位到当前用户位置的按钮,以及自定义的大头针若干,判断大头针的选中状态切换大头针的图片,根据选中的shopCell切换选中大头针等,都是一些计算性的代码,具体代码参考WNXMapView.swift文件,点击shopCell推出对应的控制器即可

体验

  • 体验控制器值得一提的是TableViewCell的高度计算方法采用了iOS8.0的新方法,通过约束cell底部来自动计算cell的高度,在xib中约束好cell底部后,在初始化TableView时,通过下面俩句代码就可以实现cell自动计算高度
        // 估算cell的高度
        tableV.estimatedRowHeight = 200
         // 设置tableView的自动布局样式
        tableV.rowHeight = UITableViewAutomaticDimension
  • 这个方法局限性比较大,只可以在确定cell底部约束对象时才可以采用,根据需求情况有时使用会比较方便必须iOS8.0以上才可以

体验详情控制器效果如下图所示

  • 可以看出体验详情控制器与探店详情控制器大同小异,不同的是底部动态添加的控件.
  • 报名控制器需要注意键盘对TextField的遮盖,监听键盘将要改变Frame的通知,设置scrollView的偏移Y轴的偏移值,保证键盘无法遮盖住输入框,网网项目中最耗时的就是这些细节性的动画,既考验开发人员的细心,又考验开发人员的耐心
  • 当用户输入好报名信息并且点击提交后,将报名信息通过sql语句插入到本地数据库,为了给后面我的订单中使用

搜索控制器(效果图如下)

搜索控制器
搜索控制器
  • 这里有个小小的动画,是根据键盘监听键盘Frame改变发出的通知来做出相应的动画,值得提醒大家的是:通过真机调试发现,当用户手机安装三方的键盘时(搜狗输入法...),键盘弹出和隐藏的通知会连续发出多次,需要加上标记来判断即可,模拟器中我还没发现这种情况,望大家注意

我的

用户头像View
  • 首先利用封装的用户账号管理工具判断当前用户是否登陆,如果未登录,则推出登陆界面,当用户登陆或者注册后,将用户的账号,以及加密过的密码发送给服务器,以及在本地备份,便于程序关闭后,下次运行程序时,用户不需要再次登陆.

  • 如果用户已登录并且点击iconView后,打开UIImagePickerController,根据用户选择的是打开相册还是相机来设置UIImagePickerController对象的sourceType.当用户点击相机时,需要先判断用户当前摄像头是否可用,注意模拟器没有摄像头,需要在真机调试才可以打开摄像头,设置照片为可编辑模式,这样用户选择的图片将会是正方形,方便头像的圆形的裁剪

  • 这里有一个地方值得注意,就是随着相机像素的提高,实际用户选择的图片都是很大的,有的高达5.6M,如果直接使用用户选着的图片,非常消耗内存,并且也用不到这么高像素的图片,可以当用户选着好图片后,在UIImagePickerController对应的代理方法中,先将图片进行重新绘制为需要的大小,在设置给iconView具体代码如下所示

/// MARK: 摄像机和相册的操作和代理方法
extension MeViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    /// 打开照相功能
    private func openCamera() {
        if UIImagePickerController.isSourceTypeAvailable(.Camera) {
            pickVC.sourceType = .Camera
            self.presentViewController(pickVC, animated: true, completion: nil)
        } else {
            SVProgressHUD.showErrorWithStatus("模拟器没有摄像头,请链接真机调试", maskType: SVProgressHUDMaskType.Black)
        }
    }
    
    /// 打开相册
    private func openUserPhotoLibrary() {
        pickVC.sourceType = .PhotoLibrary
        pickVC.allowsEditing = true
        presentViewController(pickVC, animated: true, completion: nil)
    }
    
    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
        // 对用户选着的图片进行质量压缩,上传服务器,本地持久化存储
        if let typeStr = info[UIImagePickerControllerMediaType] as? String {
            if typeStr == "public.image" {
                if let image = info[UIImagePickerControllerEditedImage] as? UIImage {
                    var data: NSData?
                    let smallImage = UIImage.imageClipToNewImage(image, newSize: iconView!.iconButton.size)
                    if UIImagePNGRepresentation(smallImage) == nil {
                        data = UIImageJPEGRepresentation(smallImage, 0.8)
                    } else {
                        data = UIImagePNGRepresentation(smallImage)
                    }
                    
                    if data != nil {
                        do {
                            // TODO: 将头像的data传入服务器
                            // 本地也保留一份data数据
                            try NSFileManager.defaultManager().createDirectoryAtPath(theme.cachesPath, withIntermediateDirectories: true, attributes: nil)
                        } catch _ {
                        }
                        NSFileManager.defaultManager().createFileAtPath(SD_UserIconData_Path, contents: data, attributes: nil)
                        
                        iconView!.iconButton.setImage(UIImage(data: NSData(contentsOfFile: SD_UserIconData_Path)!)!.imageClipOvalImage(), forState: .Normal)
                        
                    } else {
                        SVProgressHUD.showErrorWithStatus("照片保存失败", maskType: SVProgressHUDMaskType.Black)
                    }
                }
            }
        } else {
            SVProgressHUD.showErrorWithStatus("图片无法获取", maskType: SVProgressHUDMaskType.Black)
        }
        
        picker.dismissViewControllerAnimated(true, completion: nil)
    }
    
    func imagePickerControllerDidCancel(picker: UIImagePickerController) {
        pickVC.dismissViewControllerAnimated(true, completion: nil)
    }
}

摇一摇

摇一摇效果图
摇一摇效果图
  • 摇一摇使用的是苹果提供好的方法override func motionBegan(motion: UIEventSubtype, withEvent event: UIEvent?),每当用户摇晃手机时,系统就会自动调用此方法,在方法内部做出相应的动画即可关键代码就这下面这几句,如果是使用模拟器可以点击窗口上**Hardware -> Shake Gesture**来进行模拟调试
    override func motionBegan(motion: UIEventSubtype, withEvent event: UIEvent?) {
        tableView!.hidden = true
        let animateDuration: NSTimeInterval = 0.3
        let offsetY: CGFloat = 50
        
        UIView.animateWithDuration(animateDuration, animations: { () -> Void in
            self.yaoImageView1.transform = CGAffineTransformMakeTranslation(0, -offsetY)
            self.yaoImageView2.transform = CGAffineTransformMakeTranslation(0, offsetY)
            
            }) { (finish) -> Void in
                let popTime = dispatch_time(DISPATCH_TIME_NOW,Int64(0.5 * Double(NSEC_PER_SEC)))
                dispatch_after(popTime, dispatch_get_main_queue(), { () -> Void in
                    
                    UIView.animateWithDuration(animateDuration, animations: { () -> Void in
                        self.yaoImageView1.transform = CGAffineTransformIdentity
                        self.yaoImageView2.transform = CGAffineTransformIdentity
                        }, completion: { (finish) -> Void in

                            self.loadShakeData()
                            // 音效
                            AudioServicesPlayAlertSound(self.soundID!)
                    })
                })
        }

清理缓存

  • 这里我封装了一个工具类:FileTool,通过类方法可以调用查看指定路径文件夹的大小FileTool.folderSize(path: String),以及异步删除指路径下的全部文件夹FileTool.cleanFolder(path: String, complete: () -> ()),complete为删除完成后的回调

三方分享

  • 这里我封装了一个分享工具类ShareTool,集成了新浪SSO,微信SSO认证,以及新浪OAuth认证.采用的是友盟分享SDK,以及自定义的分享界面,需要注意的是由于微信没有OAuth认证,所以只有在设备安装了微信时才可以分享(插上你的真机分享点击分享试一试),模拟器中只可以使用新浪微博的分享.
  • 如果想要将新浪分享的来自xx和图片修改为自己应用的图标,可以在新浪的开发者平台注册成为开发者,添加应用并且上线,通过审核,在友盟网站我的产品中将设置主题修改相应的属性即可.
  • 在iOS9.0后,需要设置白名单以及回调的白名单才可以完成分享后的回调

关于项目的介绍基本就这么多了,更多的细节和内容直接看代码来的更快捷一些,对于Swift小熊也是又爱又恨,相信随着版本的不断更新,Swift语言也会越来越趋于稳定,Xcode对Swift的支持也会越来越好的,总算对自己的承诺有了一个小小的交代,如果有小熊的项目有帮助到大家,请到我的GitHub点个星,项目中会有很多不足之处,欢迎到我的博客留言与交流,小熊希望与大家共同进步_~,提前祝大家十一快乐


直接打开运行工程

打开工作组运行工程
打开工作组运行工程


代码下载地址

代码下载地址,欢迎点赞和反馈

小熊的技术博客

点击链接我的博客

小熊的新浪微博

我的新浪微博


本文为作者原著,欢迎转载,转载请注明作者出处

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容