iOS14开发-UIView

介绍

UIView 会占用屏幕上一个矩形的空间。

主要处理两件事:画出矩形控件,并处理其中的事件。

UIView 是层级结构,UIView 只有一个父 View,但可以有多个子 View。子 View 的顺序和子 View 返回的数组中的位置有关(storyboard 中左侧的树形结构图中的先后顺序)。

UIView 可以直接在 storyboard 里面拖拽使用,也可以使用纯代码方式使用。

UILabel、UITextField、UIButton

UILabel

显示静态文本。

文字换行

使用 storyboard:设置Lines为 0,然后在Text中用option+回车换行。

使用代码:label.numberOfLines = 0,设置文字的时候用\n换行。

UITextField

输入框。

框内左边视图

textField.leftView =UIImageView(image:UIImage(systemName:"phone"))textField.leftViewMode = .always复制代码

横线式输入框

classViewController:UIViewController{@IBOutletvartextfield:UITextField!overridefuncviewDidLoad(){super.viewDidLoad()    }// 自动布局时放这里overridefuncviewDidLayoutSubviews(){super.viewDidLayoutSubviews()// 设置无边框textfield.borderStyle = .none// 调用textfield.setBottomBorder(UIColor.red,1)    }}extensionUITextField{funcsetBottomBorder(_color: UIColor,_lineWidth: Int){// 创建一个层letbottomBorder =CALayer()letlineWidth =CGFloat(lineWidth)        bottomBorder.borderColor = color.cgColor// 设置层的framebottomBorder.frame =CGRect(x:0, y: frame.size.height - lineWidth, width: frame.size.width, height: frame.size.height)// 设置宽度bottomBorder.borderWidth = lineWidth// 插入layer.addSublayer(bottomBorder)// 裁剪layer.masksToBounds =true}}复制代码

设置提示文字颜色

// 使用NSAttributedStringtextField.attributedPlaceholder =NSAttributedString(string:"请输入信息", attributes: [.foregroundColor :UIColor.red])复制代码

UIButton

按钮,最重要的是点击事件。

文字换行

使用 storyboard:设置 Lines Break 为Word Wrap,然后在 title 中用option+回车换行。

使用代码:titleLabel.lineBreakMode = NSLineBreakByWordWrapping;,设置文字的时候用\n换行。

登录案例

classViewController:UIViewController{@IBOutletvarusername:UITextField!@IBOutletvarpassword:UITextField!@IBActionfuncloginBtnClicked(_sender:Any){letuname = username.textletupwd = password.text// 可以在这里对输入的信息进行判断print("用户名为:\(uname!), 密码为:\(upwd!)")    }overridefuncviewDidLoad(){super.viewDidLoad()    }// 触摸屏幕方法overridefunctouchesBegan(_touches: Set, with event: UIEvent?){// 退键盘的方式之一view.endEditing(true)    }}复制代码

UITextView

多行文本输入框。

使用类似 UITextField。

内容可滚动。

UIImageView

图片控件

Tom猫案例

classViewController:UIViewController{varimageArray: [UIImage] = [UIImage]()@IBOutletvartomcat:UIImageView!@IBActionfuncdrink(_sender:Any){        imageArray.removeAll()varimgName =""// 1.加载drink的动画图片forindexin0...80{// drink_XX.jpgimgName ="drink_\(index).jpg"// 通过名字构造一张图片letimage =UIImage(named: imgName)            imageArray.append(image!)        }// 2.让图片进行动画的播放// 图片数组tomcat.animationImages = imageArray// 动画时间tomcat.animationDuration =3.0// 动画次数tomcat.animationRepeatCount =1// 开始动画tomcat.startAnimating()    }overridefuncviewDidLoad(){super.viewDidLoad()    }}复制代码

UISwitch、UISlider、UIStepper 、UISegmentControl

classViewController:UIViewController{@IBOutletvarlight:UIImageView!@IBOutletvarvoice:UIImageView!@IBOutletvarproduct:UILabel!@IBOutletvarflower:UIImageView!overridefuncviewDidLoad(){super.viewDidLoad()    }// sender 谁触发这个事件 就将谁传进来@IBActionfuncvalueChanged(_sender:Any){// UISwitchletswitchUI = senderas?UISwitchifletswitchUI = switchUI {ifswitchUI.isOn {                light.image =UIImage(named:"light.png")            }else{                light.image =UIImage(named:"nomal.png")            }        }// UISliderletslider = senderas?UISliderifletslider = slider {ifslider.value <0.3{                voice.image =UIImage(named:"low.png")            }elseifslider.value <0.7{                voice.image =UIImage(named:"middle.png")            }else{                voice.image =UIImage(named:"high.png")            }        }// UIStepperletstepper = senderas?UIStepperifletstepper = stepper {letvalue = stepper.valueifvalue < stepper.maximumValue {                product.text ="您购买了\(Int(value))件商品"}ifvalue == stepper.minimumValue {                product.text ="您未购买任何商品"}        }// UISegmentedControlletsegment = senderas?UISegmentedControlifletsegment = segment {ifsegment.selectedSegmentIndex ==0{                flower.image =UIImage(named:"red.png")            }elseifsegment.selectedSegmentIndex ==1{                flower.image =UIImage(named:"purple.png")            }        }    }}复制代码

思考:汤姆猫和本案例,事件都是相同的,那么能否用一个 IBAction 完成?

UIActivityIndicatorView、UIProgressView

UIActivityIndicatorView:无进度的进度条。

UIProgressView:有进度的进度条。

classViewController:UIViewController{@IBOutletvarindicator:UIActivityIndicatorView!@IBOutletvarprogressView:UIProgressView!overridefuncviewDidLoad(){super.viewDidLoad()    }overridefunctouchesBegan(_touches: Set, with event: UIEvent?){        indicator.stopAnimating()// UIView动画// 动画执行的时间// 动画执行的操作UIView.animate(withDuration:5.0) {// 千万不要直接设置progress,因为这样是不会有动画效果的// self.progressView.progress = 1.0// 必须要用带animated参数的方法来进行设置 才会有动画self.progressView.setProgress(1.0, animated:true)        }    }}复制代码

UIDatePicker

日期选择器

classViewController:UIViewController{@IBOutletvarbirthday:UITextField!overridefuncviewDidLoad(){super.viewDidLoad()letdatePicker =UIDatePicker(frame:CGRect(x:0, y:0, width: view.bounds.size.width, height:300))        datePicker.datePickerMode = .dateAndTime// 当控件datePicker发生valueChanged事件时 会调用target的action方法datePicker.addTarget(self, action:#selector(getBirthday),for: .valueChanged)        birthday.inputView = datePicker    }@objcfuncgetBirthday(datePicker: UIDatePicker){// 获取日期letdate = datePicker.date// 日期格式化// 2018.10.17 2018/10/17 2018-10-17 2018年10月17日letdateFormatter =DateFormatter()// 24小时制,hh小写12小时制dateFormatter.dateFormat ="yyyy年MM月dd日 HH:mm:ss"// 赋值给birthdaybirthday.text = dateFormatter.string(from: date)    }overridefunctouchesBegan(_touches: Set, with event: UIEvent?){// 退键盘的另外一种方式birthday.resignFirstResponder()    }}复制代码

iOS 14 新增了卡片式日期选择器,且成为默认样式。如果需要显示成滚轮模式,需要手动设置:

datePicker.preferredDatePickerStyle = .wheels复制代码

注意:需要在 frame 之前设置。

给输入框的 inputView 设置 UIDatePicker。

UIPickerView

选择器控件

数据源(DataSource)

代理(Delegate)

可以通过代码和拽线的方式设置数据源和代理。

classViewController:UIViewController{letprovince: [String] = ["安徽","浙江","江苏","山东","河南","湖北"]letcity: [String] = ["合肥","杭州","南京","济南","郑州","武汉","厦门","长沙"]overridefuncviewDidLoad(){super.viewDidLoad()    }}extensionViewController:UIPickerViewDataSource{// UIPickerViewDataSource// 返回选择器的列数funcnumberOfComponents(inpickerView: UIPickerView)->Int{return2}funcpickerView(_pickerView: UIPickerView, numberOfRowsInComponent component: Int)->Int{ifcomponent ==0{returnprovince.count}else{returncity.count}    }}extensionViewController:UIPickerViewDelegate{// UIPickerViewDelegate// 该方法会调用多次 根据numberOfRowsInComponent的返回值决定// 每一次调用就应该返回一个数据 它会自动从第0行开始设置title// 6行 0 1 2 3 4 5funcpickerView(_pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int)->String?{ifcomponent ==0{returnprovince[row]        }else{returncity[row]        }    }// 设置UIViewfuncpickerView(_pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?)->UIView{returnUIImageView(image:UIImage(systemName:"person"))    }// 设置高度funcpickerView(_pickerView: UIPickerView, rowHeightForComponent component: Int)->CGFloat{return44}// 选择的数据列(滚动的时候调用)funcpickerView(_pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int){ifcomponent ==0{print(province[row])        }else{print(city[row])        }    }}复制代码

说明:

titleForRow方法在代理方法里而不是在数据源方法里。

内容除了设置 String 类型,还可以设置 UIView 类型,且一旦设置了 UIView,设置 String 的失效。

代理方法可以设置内容的高度。

数据联动

在某一列滚动的时候,重新设置联动列的显示数据,然后进行刷新操作。

UIScrollView、UIPageControl

UIScrollView

滚动控件

三个重要属性

contentSize:UIScrollView 滚动的范围。

contentOffset:UIScrollView 当前显示区域的顶点相对于内容左上角的偏移量(滚动到了什么位置)。

contentInset:ScrollView的内容相对于 UIScrollView 的上下左右的留白。

UIPageControl

页面指示器

一般配合 UIScrollView 分页使用。

图片轮播

classViewController:UIViewController{// 屏幕宽度letbannerW =UIScreen.main.bounds.size.width// 高度letbannerH =260varbanner:UIScrollView!varpageControl:UIPageControl!overridefuncviewDidLoad(){super.viewDidLoad()// 设置UIScrollViewsetupBanner()// 设置UIPageControlsetupPageControl()    }funcsetupBanner(){        banner =UIScrollView(frame:CGRect(x:0, y:0, width:Int(bannerW), height: bannerH))// 是否显示滚动条banner.showsHorizontalScrollIndicator =false// 是否需要弹簧效果banner.bounces =false// 是否分页banner.isPagingEnabled =true// 代理banner.delegate =self// 添加图片foriin0...4{// x坐标letw =Int(bannerW) * i// 图片控件letimg =UIImageView(frame:CGRect(x: w, y:0, width:Int(bannerW), height: bannerH))            img.image =UIImage(named:"img_\(i)")            banner.addSubview(img)        }// 设置contentSizebanner.contentSize =CGSize(width: bannerW *5.0, height:0)        view.addSubview(banner)    }funcsetupPageControl(){        pageControl =UIPageControl(frame:CGRect(x:0, y:0, width:200, height:20))// 指示器的颜色pageControl.pageIndicatorTintColor =UIColor.red// 当前页的颜色pageControl.currentPageIndicatorTintColor =UIColor.cyan// 总页数pageControl.numberOfPages =5pageControl.center =CGPoint(x: bannerW *0.5, y:240.0)// 监听事件pageControl.addTarget(self, action:#selector(pageIndicate),for: .valueChanged)        view.addSubview(pageControl)    }@objcfuncpageIndicate(pageControl: UIPageControl){letcurrentIndex = pageControl.currentPage// 设置偏移banner.setContentOffset(CGPoint(x:Int(bannerW) * currentIndex, y:0), animated:true)    }}extensionViewController:UIScrollViewDelegate{funcscrollViewDidEndDecelerating(_scrollView: UIScrollView){// 获取contentOffsetletcontentOffset = scrollView.contentOffset// 获取索引letindex = contentOffset.x / bannerW// 设置当前页pageControl.currentPage =Int(index)    }}复制代码

UITableView

表视图,是 iOS 开发中最重要的 UI 控件之一。

整体结构

一个 UITableView 由 Header + 多个 Section + Footer 组成。

一个 Section 由 Header + 多个 Row + Footer 组成。

一个 Row 就是 UITableViewCell。

UITableViewCell结构

里面有一个contentView,显示的内容放在上面。

contentView里默认有 3 个控件:2 个UILabel、1一个UIImageView,并由此产生了四种不同的 UITableViewCell 的显示样式。

类似 PickerView,需要提供数据源以显示数据。

基本使用

classViewController:UIViewController{overridefuncviewDidLoad(){super.viewDidLoad()    }}extensionViewController:UITableViewDataSource{// 有多少分组funcnumberOfSections(intableView: UITableView)->Int{return1}// 每个分组中有多少行functableView(_tableView: UITableView, numberOfRowsInSection section        : Int)->Int{return20}// 每一行的内容functableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath)->UITableViewCell{letcell =UITableViewCell(style: .subtitle, reuseIdentifier:"abc")// default 只显示textLabel和imageView// subtitle value1 三个都显示// value2 只显示textLabel和detailTextLabelcell.textLabel?.text ="AAA"cell.detailTextLabel?.text ="BBB"cell.imageView?.image =UIImage(named:"setting_about_pic")returncell    }}复制代码

UITableViewCell重用

重用原理

重用好处

重用标识符

classViewController:UIViewController{overridefuncviewDidLoad(){super.viewDidLoad()    }}extensionViewController:UITableViewDataSource{// 有多少分组funcnumberOfSections(intableView: UITableView)->Int{return1}// 一个分组中有多少行functableView(_tableView: UITableView, numberOfRowsInSection section: Int)->Int{return20}// 每一行长什么样functableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath)->UITableViewCell{/**

        // 纯代码实现复用

        // 去重用池子中找cell

        var cell = tableView.dequeueReusableCell(withIdentifier: "abc")

        // 池子中没有就创建一个新的

        if cell == nil {

            cell = UITableViewCell(style: .subtitle, reuseIdentifier: "abc")

        }

        cell?.textLabel?.text = "AAA"

        cell?.detailTextLabel?.text = "BBB"

        cell?.imageView?.image = UIImage(named: "setting_about_pic")

        return cell!

          */// SB方式实现复用letcell = tableView.dequeueReusableCell(withIdentifier:"abc")        cell?.textLabel?.text ="AAA"cell?.detailTextLabel?.text ="BBB"cell?.imageView?.image =UIImage(named:"setting_about_pic")returncell!    }}复制代码

数据源

数据不再固定,而是由外界提供,多使用数组。

classViewController:UIViewController{varcontent:Array?vardetailContent:Array?overridefuncviewDidLoad(){super.viewDidLoad()        content = ["iPhone 4","iPhone 4s","iPhone 5","iPhone 5s","iPhone 6","iPhone 6 Plus","iPhone 6s","iPhone 6s Plus","iPhone 7","iPhone 7 Plus","iPhone 8","iPhone 8 Plus","iPhone X","iPhone Xs","iPhone XR","iPhone Xs Max","iPhone 11","iPhone 11 Pro","iPhone 11 Pro Max","iPhone 12 mini","iPhone 12","iPhone 12 Pro","iPhone 12 Pro Max"]        detailContent = ["iPhone 4 - iOS 4","iPhone 4s - iOS 5","iPhone 5 - iOS 6","iPhone 5s - iOS 7","iPhone 6 - iOS 8","iPhone 6 Plus - iOS 8","iPhone 6s - iOS 9","iPhone 6s Plus - iOS 9","iPhone 7 - iOS 10","iPhone 7 Plus - iOS 10","iPhone 8 - iOS 11","iPhone 8 Plus - iOS 11","iPhone X - iOS 11","iPhone Xs - iOS 12","iPhone XR - iOS 12","iPhone Xs Max - iOS 12","iPhone 11 - iOS 13","iPhone 11 Pro - iOS 13","iPhone 11 Pro Max - iOS 13","iPhone 12 mini - iOS 14","iPhone 12 - iOS 14","iPhone 12 Pro - iOS 14","iPhone 12 Pro Max - iOS 14"]    }}extensionViewController:UITableViewDataSource{// 有多少分组funcnumberOfSections(intableView: UITableView)->Int{return1}// 一个分组中有多少行functableView(_tableView: UITableView, numberOfRowsInSection section: Int)->Int{returncontent!.count}// 每一行长什么样functableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath)->UITableViewCell{// SB方式实现复用letcell = tableView.dequeueReusableCell(withIdentifier:"ABC")        cell?.textLabel?.text = content?[indexPath.row]        cell?.detailTextLabel?.text = detailContent?[indexPath.row]        cell?.imageView?.image =UIImage(named:"iPhone")returncell!    }}复制代码

代理

classViewController:UIViewController{varcontent:Array?vardetailContent:Array?overridefuncviewDidLoad(){super.viewDidLoad()        content = ["iPhone 4","iPhone 4s","iPhone 5","iPhone 5s","iPhone 6","iPhone 6 Plus","iPhone 6s","iPhone 6s Plus","iPhone 7","iPhone 7 Plus","iPhone 8","iPhone 8 Plus","iPhone X","iPhone Xs","iPhone XR","iPhone Xs Max","iPhone 11","iPhone 11 Pro","iPhone 11 Pro Max","iPhone 12 mini","iPhone 12","iPhone 12 Pro","iPhone 12 Pro Max"]        detailContent = ["iPhone 4 - iOS 4","iPhone 4s - iOS 5","iPhone 5 - iOS 6","iPhone 5s - iOS 7","iPhone 6 - iOS 8","iPhone 6 Plus - iOS 8","iPhone 6s - iOS 9","iPhone 6s Plus - iOS 9","iPhone 7 - iOS 10","iPhone 7 Plus - iOS 10","iPhone 8 - iOS 11","iPhone 8 Plus - iOS 11","iPhone X - iOS 11","iPhone Xs - iOS 12","iPhone XR - iOS 12","iPhone Xs Max - iOS 12","iPhone 11 - iOS 13","iPhone 11 Pro - iOS 13","iPhone 11 Pro Max - iOS 13","iPhone 12 mini - iOS 14","iPhone 12 - iOS 14","iPhone 12 Pro - iOS 14","iPhone 12 Pro Max - iOS 14"]    }}extensionViewController:UITableViewDataSource{// 有多少分组funcnumberOfSections(intableView: UITableView)->Int{return1}// 一个分组中有多少行functableView(_tableView: UITableView, numberOfRowsInSection section: Int)->Int{returncontent!.count}// 每一行长什么样functableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath)->UITableViewCell{// SB方式实现复用letcell = tableView.dequeueReusableCell(withIdentifier:"ABC")        cell?.textLabel?.text = content?[indexPath.row]        cell?.detailTextLabel?.text = detailContent?[indexPath.row]        cell?.imageView?.image =UIImage(named:"iPhone")returncell!    }}extensionViewController:UITableViewDelegate{// Section头部functableView(_tableView: UITableView, titleForHeaderInSection section: Int)->String?{return"iPhone 大全"}// Section尾部functableView(_tableView: UITableView, titleForFooterInSection section: Int)->String?{return"iOS大全"}// 选中(点击行)functableView(_tableView: UITableView, didSelectRowAt indexPath: IndexPath){        tableView.deselectRow(at: indexPath, animated:true)letcontentText = content?[indexPath.row]letdetailText = detailContent?[indexPath.row]print("\(contentText!)--\(detailText!)")    }// 行高functableView(_tableView: UITableView, heightForRowAt indexPath: IndexPath)->CGFloat{return80.0}// Section头部高functableView(_tableView: UITableView, heightForHeaderInSection section: Int)->CGFloat{return100.0}// Section尾部高functableView(_tableView: UITableView, heightForFooterInSection section: Int)->CGFloat{return60.0}}复制代码

编辑

classViewController:UIViewController{@IBOutletvartableView:UITableView!varcontent:Array?vardetailContent:Array?overridefuncviewDidLoad(){super.viewDidLoad()        content = ["iPhone 4","iPhone 4s","iPhone 5","iPhone 5s","iPhone 6","iPhone 6 Plus","iPhone 6s","iPhone 6s Plus","iPhone 7","iPhone 7 Plus","iPhone 8","iPhone 8 Plus","iPhone X","iPhone Xs","iPhone XR","iPhone Xs Max","iPhone 11","iPhone 11 Pro","iPhone 11 Pro Max","iPhone 12 mini","iPhone 12","iPhone 12 Pro","iPhone 12 Pro Max"]        detailContent = ["iPhone 4 - iOS 4","iPhone 4s - iOS 5","iPhone 5 - iOS 6","iPhone 5s - iOS 7","iPhone 6 - iOS 8","iPhone 6 Plus - iOS 8","iPhone 6s - iOS 9","iPhone 6s Plus - iOS 9","iPhone 7 - iOS 10","iPhone 7 Plus - iOS 10","iPhone 8 - iOS 11","iPhone 8 Plus - iOS 11","iPhone X - iOS 11","iPhone Xs - iOS 12","iPhone XR - iOS 12","iPhone Xs Max - iOS 12","iPhone 11 - iOS 13","iPhone 11 Pro - iOS 13","iPhone 11 Pro Max - iOS 13","iPhone 12 mini - iOS 14","iPhone 12 - iOS 14","iPhone 12 Pro - iOS 14","iPhone 12 Pro Max - iOS 14"]    }@IBActionfuncedit(_sender:Any){        tableView.setEditing(true, animated:true)    }@IBActionfuncdone(_sender:Any){        tableView.setEditing(false, animated:true)    }}extensionViewController:UITableViewDataSource{// 有多少分组funcnumberOfSections(intableView: UITableView)->Int{return1}// 一个分组中有多少行functableView(_tableView: UITableView, numberOfRowsInSection section: Int)->Int{returncontent!.count}// 每一行长什么样functableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath)->UITableViewCell{// SB方式实现复用letcell = tableView.dequeueReusableCell(withIdentifier:"ABC")        cell?.textLabel?.text = content?[indexPath.row]        cell?.detailTextLabel?.text = detailContent?[indexPath.row]        cell?.imageView?.image =UIImage(named:"iPhone")returncell!    }}extensionViewController:UITableViewDelegate{// 允许编辑functableView(_tableView: UITableView, canEditRowAt indexPath: IndexPath)->Bool{returntrue}// 提交编辑functableView(_tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath){ifeditingStyle == .delete {// 1.删除数组中对应的数据content?.remove(at: indexPath.row)            detailContent?.remove(at: indexPath.row)// 2.TableView显示的那一样删除tableView.deleteRows(at: [indexPath], with: .automatic)        }elseifeditingStyle == .insert {// 1.增加一条数据content?.insert("iPhone 1", at: indexPath.row)            detailContent?.insert("iPhone 1 - iPhone OS", at: indexPath.row)// 2.增加一行tableView.insertRows(at: [indexPath], with: .automatic)        }    }// 删除按钮的文字functableView(_tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath)->String?{return"删除"}// 编辑的风格(默认是删除)functableView(_tableView: UITableView, editingStyleForRowAt indexPath: IndexPath)->UITableViewCell.EditingStyle{return.insert    }// 能否移动functableView(_tableView: UITableView, canMoveRowAt indexPath: IndexPath)->Bool{returntrue}// 移动表格functableView(_tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath){// 内容letcontentText = content?[sourceIndexPath.row]// 先删除content?.remove(at: sourceIndexPath.row)// 再增加content?.insert(contentText!, at: destinationIndexPath.row)// 详情操作和内容一样letdetailContentText = detailContent?[sourceIndexPath.row]        detailContent?.remove(at: sourceIndexPath.row)        detailContent?.insert(detailContentText!, at: destinationIndexPath.row)    }}复制代码

索引

classViewController:UIViewController{@IBOutletvartableView:UITableView!varsectionTitles: [String] = ["A","C","F","G","H","M","S","T","X","Z"]varcontentsArray: [[String]] = [        ["阿伟","阿姨","阿三"],        ["陈晨","成龙","陈鑫","陈丹"],        ["芳仔","房祖名","方大同","范伟"],        ["郭靖","郭美美","过儿","郭襄"],        ["何仙姑","和珅","郝歌","何仙姑"],        ["马云","毛不易"],        ["孙周","沈冰","史磊"],        ["陶也","淘宝","图腾"],        ["项羽","夏紫薇","许巍","许晴"],        ["祝枝山","周杰伦","张柏芝"],    ]overridefuncviewDidLoad(){super.viewDidLoad()        tableView.sectionIndexBackgroundColor =UIColor.red    }}extensionViewController:UITableViewDataSource{funcnumberOfSections(intableView: UITableView)->Int{returnsectionTitles.count}functableView(_tableView: UITableView, numberOfRowsInSection section: Int)->Int{returncontentsArray[section].count}functableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath)->UITableViewCell{letcell = tableView.dequeueReusableCell(withIdentifier:"abc")        cell?.textLabel?.text = contentsArray[indexPath.section][indexPath.row]returncell!    }functableView(_tableView: UITableView, titleForHeaderInSection section: Int)->String?{returnsectionTitles[section]    }}extensionViewController:UITableViewDelegate{// 索引的标题funcsectionIndexTitles(fortableView: UITableView)-> [String]? {returnsectionTitles    }// 点击索引functableView(_tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int)->Int{// 点击的索引标题print(title)// 一定要返回index 否则 点击索引不会自动滚动到指定位置returnindex    }}复制代码

自定义UITableViewCell

用 3 种自定义 Cell 的方式分别实现下面的案例:

iPhone 信息展示

新闻列表

下拉刷新

classViewController:UIViewController{@IBOutletvartableView:UITableView!varcontent:Array = ["iPhone 4","iPhone 4s","iPhone 5","iPhone 5s","iPhone 6","iPhone 6 Plus","iPhone 6s","iPhone 6s Plus","iPhone 7","iPhone 7 Plus","iPhone 8","iPhone 8 Plus","iPhone X","iPhone Xs","iPhone XR","iPhone Xs Max","iPhone 11","iPhone 11 Pro","iPhone 11 Pro Max","iPhone 12 mini","iPhone 12","iPhone 12 Pro","iPhone 12 Pro Max"]vardetailContent:Array = ["iPhone 4 - iOS 4","iPhone 4s - iOS 5","iPhone 5 - iOS 6","iPhone 5s - iOS 7","iPhone 6 - iOS 8","iPhone 6 Plus - iOS 8","iPhone 6s - iOS 9","iPhone 6s Plus - iOS 9","iPhone 7 - iOS 10","iPhone 7 Plus - iOS 10","iPhone 8 - iOS 11","iPhone 8 Plus - iOS 11","iPhone X - iOS 11","iPhone Xs - iOS 12","iPhone XR - iOS 12","iPhone Xs Max - iOS 12","iPhone 11 - iOS 13","iPhone 11 Pro - iOS 13","iPhone 11 Pro Max - iOS 13","iPhone 12 mini - iOS 14","iPhone 12 - iOS 14","iPhone 12 Pro - iOS 14","iPhone 12 Pro Max - iOS 14"]overridefuncviewDidLoad(){super.viewDidLoad()// 创建UIRefreshControlletrefresh =UIRefreshControl()// 设置显示的标题refresh.attributedTitle =NSAttributedString(string:"下拉刷新")// 设置下拉事件refresh.addTarget(self, action:#selector(loadData),for: .valueChanged)// 放到tableView的头部tableView.refreshControl = refresh    }@objcfuncloadData(){// 延迟执行DispatchQueue.main.asyncAfter(deadline: .now() +3) {// 增加一条数据self.content.insert("iPhone 3GS", at:0)self.detailContent.insert("iPhone 3GS - iOS 3", at:0)// 刷新表格 结束刷新的状态self.tableView.reloadData()// 停止刷新if(self.tableView.refreshControl?.isRefreshing)! {self.tableView.refreshControl?.endRefreshing()            }        }    }}extensionViewController:UITableViewDataSource{// 一个分组中有多少行publicfunctableView(_tableView: UITableView, numberOfRowsInSection section: Int)->Int{returncontent.count}// 每一行长什么样publicfunctableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath)->UITableViewCell{// SB方式实现复用letcell = tableView.dequeueReusableCell(withIdentifier:"ABC")        cell?.textLabel?.text = content[indexPath.row]        cell?.detailTextLabel?.text = detailContent[indexPath.row]        cell?.imageView?.image =UIImage(named:"iPhone")returncell!    }}复制代码

静态单元格

需要使用 UITableViewController。

直接在 storyboard 中布局,不需要使用数据源方法,但如果需要使用到代理方法,仍然需要在控制器中实现相应的方法。

适用于基本不需要动态修改、布局固定的页面,如个人中心、设置等。

微信“发现”界面案例。

UITableViewDiffableDataSource

在 iOS 13 中引入了新的 API — Diffable Data Source,它不仅能够驱动 UITableView 和 UICollectionView,而且可以更简单高效的实现数据的刷新。

核心知识

UITableViewDiffableDataSource:创建 UITableView 数据源。

NSDiffableDataSourceSnapshot:UITableView 的状态。

apply(_:animatingDifferences:):当要显示或更新数据时,通过调用 NSDiffableDataSourceSnapshot 对象的 apply 方法将其提供给数据源,该方法将比较当前显示的快照(渲染模型)和新快照以获得差异,最后以设定的动画方式应用这些变化从而刷新界面。

案例

创建数据源。

vardataSource:UITableViewDiffableDataSource!overridefuncviewDidLoad(){super.viewDidLoad()        dataSource =UITableViewDiffableDataSource(tableView: tableView) {// 该闭包是tableView(_:cellForRowAtIndexPath:)方法的替代品(tableView:UITableView, indexPath:IndexPath,            city:City) ->UITableViewCell?inletcell = tableView.dequeueReusableCell(withIdentifier:"cell",for: indexPath)            cell.textLabel?.text = city.namereturncell    }// 刷新时的动画dataSource.defaultRowAnimation = .fade}复制代码

DataSourceSnapshot 负责变更后的数据源处理,其有 append、delete、move、insert 等方法。

enumSection:CaseIterable{casemain}// 获取NSDiffableDataSourceSnapshotvarsnapshot =NSDiffableDataSourceSnapshot()// 更改数据snapshot.appendSections([.main])snapshot.appendItems(filteredCities, toSection: .main)// 更新dataSource.apply(snapshot, animatingDifferences:true)复制代码

为了确保 Diff 生效,数据源的 Model 必须具有唯一 Identifier,且遵循 Hashable 协议。

structCity:Hashable{letidentifier =UUID()letname:Stringfunchash(into hasher:inoutHasher){        hasher.combine(identifier)    }staticfunc==(lhs: City, rhs: City)->Bool{returnlhs.identifier == rhs.identifier    }funccontains(query: String?)->Bool{guardletquery = queryelse{returntrue}guard!query.isEmptyelse{returntrue}returnname.contains(query)    }}复制代码

UICollectionView

集合视图,是 iOS 开发中最重要的 UI 控件之一。

整体结构

一个 UICollectionView 由 Header + 多个 Section + Footer 组成。

一个 Section 由 Header + 多个 Item + Footer 组成。

一个 Item 就是 UICollectionViewCell。

类似 UITableView,需要提供数据源以显示数据。

支持 Diffable Data Source,类为 UICollectionViewDiffableDataSource,使用方式类似 UITableViewDiffableDataSource。

UICollectionViewFlowLayout

与 UITableView 不同,UICollectionView 需要提供布局参数,常用的有UICollectionViewFlowLayout,通过它可以设置内容的大小、间距和方向等信息。

classViewController:UIViewController{@IBOutletvarcollectionView:UICollectionView!letscreenW =UIScreen.main.bounds.size.widthoverridefuncviewDidLoad(){super.viewDidLoad()// 布局letlayout =UICollectionViewFlowLayout()// 设置item大小layout.itemSize =CGSize(width: (screenW -15.0) *0.5, height:212)// item之间的间距layout.minimumInteritemSpacing =5.0// 行间距layout.minimumLineSpacing =5.0// 组边距layout.sectionInset =UIEdgeInsets(top:0,left:5, bottom:0,right:5)// 滚动方向layout.scrollDirection = .vertical        collectionView.collectionViewLayout = layout    }}extensionViewController:UICollectionViewDataSource{funcnumberOfSections(incollectionView: UICollectionView)->Int{return1}funccollectionView(_collectionView: UICollectionView, numberOfItemsInSection section: Int)->Int{return10}funccollectionView(_collectionView: UICollectionView, cellForItemAt indexPath: IndexPath)->UICollectionViewCell{letcell = collectionView.dequeueReusableCell(withReuseIdentifier:"abc",for: indexPath)returncell    }}extensionViewController:UICollectionViewDelegate{funccollectionView(_collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath){print("\(indexPath.row)")    }}复制代码

UICollectionViewCompositionalLayout

在 iOS 13 中 UICollectionView 推出了一种新的组合布局 UICollectionViewCompositionalLayout,这是一次全新的升级。

介绍

UICollectionViewCompositionalLayout 是在已有的 Item 和 Section 的基础上,增加了一个 Group 的概念。多个 Item 组成一个 Group ,多个 Group 组成一个 Section,因此层级关系从里到外变为:Item -> Group -> Section ->  Layout。

核心知识

NSCollectionLayoutSize

决定了一个元素的大小。表达一个元素的 Size 有三种方法:

fractional:表示一个元素相对于他的父视图的比例。(Item 的父视图是 Group,Group 的父视图是 Section) 。

// 占据Group宽和高各25%letitemSize =NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.25), heightDimension: .fractionalHeight(0.25))复制代码

absolute:表示将元素的宽或者高写成固定一个值。

letwidthDimension =NSCollectionLayoutDimension.absolute(200)letheightDimension =NSCollectionLayoutDimension.absolute(200)复制代码

estimated:表示预估高度。一般用于自适应大小,会根据自动布局决定元素的大小。

letwidthDimension =NSCollectionLayoutDimension.estimated(200)letheightDimension =NSCollectionLayoutDimension.estimated(200)复制代码

NSCollectionLayoutItem

描述一个 Item 的布局,定义如下:

classNSCollectionLayoutItem{convenienceinit(layoutSize:NSCollectionLayoutSize)varcontentInsets:NSDirectionalEdgeInsets}复制代码

NSCollectionLayoutGroup

Group 是新引入的组成布局的基本单元,它有三种形式:

水平(horizontal)

垂直(vertical)

自定义(custom)

Group 的大小页需要通过 NSCollectionLayoutSize 决定。如果是自定义布局,需要传入一个 NSCollectionLayoutGroupCustomItemProvider 来决定这个 Group 中 Item 的布局方式。

定义如下:

classNSCollectionLayoutGroup:NSCollectionLayoutItem{classfunchorizontal(layoutSize:NSCollectionLayoutSize,subitems: [NSCollectionLayoutItem]) ->Selfclassfuncvertical(layoutSize:NSCollectionLayoutSize,subitems: [NSCollectionLayoutItem]) ->Selfclassfunccustom(layoutSize:NSCollectionLayoutSize,itemProvider:NSCollectionLayoutGroupCustomItemProvider) ->Self}复制代码

NSCollectionLayoutSection

描述一个 Section 的布局,定义如下:

classNSCollectionLayoutSection{convenienceinit(layoutGroup:NSCollectionLayoutGroup)varcontentInsets:NSDirectionalEdgeInsets}复制代码

使用步骤

创建 Item 的 NSCollectionLayoutSize,然后创建 NSCollectionLayoutItem。

创建 Group 的 NSCollectionLayoutSize,然后根据 Item 创建 NSCollectionLayoutGroup。

根据 Group 创建 NSCollectionLayoutSection。

根据 Section 创建 UICollectionViewCompositionalLayout。

由里而外,由小到大地创建布局,然后组合。

案例

funcgenerateLayout()->UICollectionViewCompositionalLayout{// 1. 构造Item的NSCollectionLayoutSizeletitemSize =NSCollectionLayoutSize(        widthDimension: .fractionalWidth(0.25),        heightDimension: .fractionalHeight(1.0))// 2. 构造NSCollectionLayoutItemletitem =NSCollectionLayoutItem(layoutSize: itemSize)// 3. 构造Group的NSCollectionLayoutSizeletgroupSize =NSCollectionLayoutSize(        widthDimension: .fractionalWidth(1.0),        heightDimension: .fractionalWidth(0.1))// 4. 构造NSCollectionLayoutGroupletgroup =NSCollectionLayoutGroup.horizontal(        layoutSize: groupSize,        subitems: [item])// 5. 构造NSCollectionLayoutSectionletsection =NSCollectionLayoutSection(group: group)// 6. 构造UICollectionViewCompositionalLayoutletlayout =UICollectionViewCompositionalLayout(section: section)returnlayout}复制代码

NSCollectionLayoutBoundarySupplementaryItem

附加视图,一般用于设置头部和尾部 View。

// 头部大小letheaderSize =NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))// 头部letheader =NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind:"header", alignment: .top)// 尾部大小letfooterSize =NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))// 尾部letfooter =NSCollectionLayoutBoundarySupplementaryItem(layoutSize: footerSize, elementKind:"footer", alignment: .bottom)// pinToVisibleBounds决定是否悬停header.pinToVisibleBounds =true// 设置Section的头尾section.boundarySupplementaryItems = [header, footer]复制代码

附加视图使用之前需要注册SupplementaryView,后面会进行讲解。

NSCollectionLayoutAnchor

在 Item 中,可能需要给其加上小红点或者未读消息数等附加视图,在 UICollectionViewCompositionalLayout 中,可以通过 NSCollectionLayoutSupplementaryItem 和 NSCollectionLayoutAnchor 这两个类来实现这样的需求。

实现一个UICollectionReusableView。

classBadgeView:UICollectionReusableView{staticletreuseIdentifier ="badge"letimageView =UIImageView(image:UIImage(systemName:"heart.fill"))overrideinit(frame:CGRect) {super.init(frame: frame)              configure()    }}复制代码

注册SupplementaryView。

collectionView.register(BadgeView.self,    forSupplementaryViewOfKind:"badge",    withReuseIdentifier:BadgeView.reuseIdentifier)复制代码

设置SupplementaryView。

dataSource.supplementaryViewProvider = {    (collectionView:UICollectionView, kind:String, indexPath:IndexPath)        ->UICollectionReusableView?inifletbadgeView = collectionView.dequeueReusableSupplementaryView(        ofKind: kind,        withReuseIdentifier:BadgeView.reuseIdentifier,for: indexPath)as?BadgeView{returnbadgeView    }else{fatalError("Cannot create Supplementary")    }}复制代码

设置Badge。

// Badge位置letbadgeAnchor =NSCollectionLayoutAnchor(edges: [.top, .trailing],fractionalOffset:CGPoint(x:0.5, y: -0.5))// Badge大小letbadgeSize =NSCollectionLayoutSize(widthDimension: .absolute(16),heightDimension: .absolute(16))// Badgeletbadge =NSCollectionLayoutSupplementaryItem(layoutSize: badgeSize, elementKind:"badge", containerAnchor: badgeAnchor)// 附加Badgeletitem =NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [badge])复制代码

Lists in UICollectionView

iOS 14 中 UICollectionView 的功能得以继续增强,可以在一定程度上替换 UITableView。

创建UICollectionView

为 UICollectionView 配置 List 式的布局,还可以配置滑动菜单。

extensionViewController{// 创建列表式UICollectionViewfuncmakeCollectionView()->UICollectionView{varconfig =UICollectionLayoutListConfiguration(appearance: .insetGrouped)// 右侧滑动删除config.trailingSwipeActionsConfigurationProvider = { indexPathin// 找到删除的内容guardletcity =self.dataSource.itemIdentifier(for: indexPath)else{returnnil}returnUISwipeActionsConfiguration(                actions: [UIContextualAction(                    style: .destructive,                    title:"Delete",                    handler: { [weakself]_,_, completionin// 调用删除数据self?.deleteCity(city: city, indexPath: indexPath)self?.updateList()// 最后一定要调用completioncompletion(true)                    }                )]            )        }// 左侧滑动添加config.leadingSwipeActionsConfigurationProvider = {  indexPathinreturnUISwipeActionsConfiguration(                actions: [UIContextualAction(                    style: .normal,                    title:"Add",                    handler: { [weakself]_,_, completionin// 调用增加数据self?.addCity(city:City(name:"芜湖"), indexPath: indexPath)self?.updateList()                                                completion(true)                    }                )]            )        }// 列表布局letlayout =UICollectionViewCompositionalLayout.list(using: config)returnUICollectionView(frame: view.frame, collectionViewLayout: layout)    }}复制代码

注册Cell

可以像 UITableView 一样,填充 Cell 的内容。

extensionViewController{// 注册CellfuncmakeCellRegistration()->UICollectionView.CellRegistration {UICollectionView.CellRegistration{ cell,_, cityin// 自定义Cell显示的内容cell.cityLabel.text = city.name// AccessoryViewcell.accessories = [.disclosureIndicator()]        }    }}复制代码

配置数据源

extensionViewController{// 配置数据源funcmakeDataSource()->UICollectionViewDiffableDataSource {UICollectionViewDiffableDataSource(            collectionView: collectionView,            cellProvider: { view, indexPath, iteminview.dequeueConfiguredReusableCell(                    using:self.makeCellRegistration(),for: indexPath,                    item: item                )            }        )    }}复制代码

更新数据

enumSection:CaseIterable{casefirstcasesecond}extensionViewController{funcupdateList(){varsnapshot =NSDiffableDataSourceSnapshot()// 添加两个分组snapshot.appendSections(Section.allCases)// 分别往两个分组添加数据snapshot.appendItems(firstCities, toSection: .first)        snapshot.appendItems(secondCities, toSection: .second)        dataSource.apply(snapshot)    }}复制代码

使用

classViewController:UIViewController{// 创建UICollectionViewprivatelazyvarcollectionView = makeCollectionView()// 创建DataSourceprivatelazyvardataSource = makeDataSource()letcityNames = ["北京","南京","西安","杭州","芜湖"]// 第一组varfirstCities: [City] = []// 第二组varsecondCities: [City] = []overridefuncviewDidLoad(){super.viewDidLoad()fornameincityNames {            firstCities.append(City(name: name))            secondCities.append(City(name: name))        }// CollectionView关联DataSource collectionView.dataSource = dataSource        view.addSubview(collectionView)// 第一次进来刷新updateList()    }}// 增加与删除数据extensionViewController{// 删除数据funcdeleteCity(city: City, indexPath: IndexPath){ifindexPath.section ==0{            firstCities.remove(at: firstCities.firstIndex(of: city)!)        }else{            secondCities.remove(at: secondCities.firstIndex(of: city)!)        }    }// 增加数据funcaddCity(city: City, indexPath: IndexPath){ifindexPath.section ==0{            firstCities.append(city)        }else{            secondCities.append(city)        }    }}复制代码

纯代码

使用步骤

// 1.创建UIViewletsubView =UIView()// 2.设置framesubView.frame =CGRect(x:0, y:0, width:200, height:200)// 1和2可以合并// let subView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))// 3.设置其他属性subView.backgroundColor = .red// 4.UIControl可以添加事件...// 5.添加到父Viewview.addSubview(subView)复制代码

添加事件

iOS 14 之前使用 Target-Action 方式添加事件。

// UITextFieldlettextField =UITextField()textField.addTarget(self, action:#selector(handlerEvent),for: .editingChanged)@objcfunchandlerEvent(_sender: UITextField){print(sender.text)}// UIButtonletbutton =UIButton()button.addTarget(self, action:#selector(handlerEvent),for: .touchUpInside)@objcfunchandlerEvent(_sender: UIButton){print("按钮点击")}// UISwitchletswi =UISwitch()swi.addTarget(self, action:#selector(handlerEvent),for: .valueChanged)@objcfunchandlerEvent(_sender: UISwitch){print(sender.isOn)}// UISliderletslider =UISlider()slider.addTarget(self, action:#selector(handlerEvent),for: .valueChanged)@objcfunchandlerEvent(_sender: UISlider){print(sender.value)}// UIStepperletstepper =UIStepper()stepper.addTarget(self, action:#selector(handlerEvent),for: .valueChanged)@objcfunchandlerEvent(_sender: UISlider){print(sender.value)}// UISegmentedControlletsegmentedControl =UISegmentedControl()segmentedControl.addTarget(self, action:#selector(handlerEvent),for: .valueChanged)@objcfunchandlerEvent(_sender: UISegmentedControl){print(sender.selectedSegmentIndex)}// UIPageControlletpageControl =UIPageControl()pageControl.addTarget(self, action:#selector(handlerEvent),for: .valueChanged)@objcfunchandlerEvent(_sender: UIPageControl){print(sender.currentPage)}// UIDatePickerletdatepicker =UIDatePicker()datepicker.addTarget(self, action:#selector(handlerEvent),for: .valueChanged)@objcfunchandlerEvent(_sender: UIDatePicker){print(sender.date)}// UIRefreshControllettableView =UITableView(frame:UIScreen.main.bounds)letrefreshControl =UIRefreshControl()refreshControl.addTarget(self, action:#selector(handlerEvent),for: .valueChanged)tableView.refreshControl = refreshControl@objcfunchandlerEvent(_sender: UIRefreshControl){print(sender.isRefreshing)}复制代码

iOS 14 支持 Action 回调的方式添加事件。

// UITextFieldlettextField =UITextField()textField.addAction(UIAction{ actioninlettextField = action.senderas!UITextFieldprint(textField.text)    },for: .editingChanged)// UIButton// 方式一letbutton =UIButton(primaryAction:UIAction{_inprint("按钮点击")})// 方式二letbtn =UIButton()btn.addAction(UIAction{_inprint("按钮点击")    },for: .touchUpInside)// UISwitchletswi =UISwitch()swi.addAction(UIAction{ actioninletswi = action.senderas!UISwitchprint(swi.isOn)    },for: .valueChanged)// UISliderletslider =UISlider()slider.addAction(UIAction{ actioninletslider = action.senderas!UISliderprint(slider.value)    },for: .valueChanged)// UIStepperletstepper =UIStepper()stepper.addAction(UIAction{ actioninletstepper = action.senderas!UIStepperprint(stepper.value)    },for: .valueChanged)// UISegmentedControlletsegmentedControl =UISegmentedControl()segmentedControl.addAction(UIAction{ actioninletsegmentedControl = action.senderas!UISegmentedControlprint(segmentedControl.selectedSegmentIndex)    },for: .valueChanged)// UIPageControlletpageControl =UIPageControl()pageControl.addAction(UIAction{ actioninletpageControl = action.senderas!UIPageControlprint(pageControl.currentPage)    },for: .valueChanged)// UIDatePickerletdatepicker =UIDatePicker()datepicker.addAction(UIAction{ actioninletdatepicker = action.senderas!UIDatePickerprint(datepicker.date)    },for: .valueChanged)// UIRefreshControllettableView =UITableView(frame:UIScreen.main.bounds)letrefreshControl =UIRefreshControl()refreshControl.addAction(UIAction{ actioninletrefreshControl = action.senderas!UIRefreshControlprint(refreshControl.isRefreshing)    },for: .valueChanged)tableView.refreshControl = refreshControl

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

推荐阅读更多精彩内容