基本功笔记,公布博客园

By admin in 4858.com on 2019年9月17日

那是二个关于自定义控件的专项论题,那么自定义控件最初必定得从局部初始性的东西聊起,step
by
step.恐怕你会感到非常的粗略,只是几张图纸的轮转显示而已,同期最下边四个提示器标识滚动的页数,在顾客浏览完结之后就不会在App中再一次使用了,所以可以间接忽略页面重用难点以及加载图片带来内部存款和储蓄器难点了;
也就几百来行代码而已。可是既然是自定义控件的话,那么料定是要完成一个复用性比较高的App首页运行页面。

UI Basic Note

UITableView的样式

UITableView是iOS开采中特别主要的控件之一,它亦可体现多行数据,帮忙滚动.在大多数应用程式中都据有异常的大的比重.

  • UITableView的创立格局:
    • storyboard:直接拖到调整器中就能够.
    • 代码

    //代码创建
    UITableView *tableView = [[UITableView alloc] init];
    //设置frame
    tableView.frame = self.view.bounds;
    [self.view addSubview:tableView];
  • UITableView有三种体裁:plain和grouped.如图所示,
    基本功笔记,公布博客园。图1是plain样式即怀有的cell都在黏在一齐,未有明了的区分.图2是grouped样式,是分组的.

4858.com 1

图1

4858.com 2

图2

能够见见三种格式的界别非常明显.

在UITableView中,每一个单元格被称为cell,如果想在UITableView中显示数据,需要设置UITableView中cell的数量及每个cell显示的内容.UITableView并不能直接显示数据,它需要设置数据源(datasource),数据源遵守<UITableViewDataSource>协议,并实现其中对应的方法设置数据及内容即可.

<UITableViewDataSource>中常用的办法:

@required(必须实现的)
    //返回第section行的row数量
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
    //返回第indexPath.row行的cell内容
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
以上这两个方法会调用一次或多次,次数根据设置的section数量及每个section的cell数量而定.在开发中一般通过模型来传递数据,在控制器中创建一个数组接收模型数据,然后加载到cell中显示.

@optional(可选的)
     //返回section的数量,默认为1
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; 
    //返回grouped样式中每组的头部标题
    - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; 
    //返回grouped样式中每组的尾部标题
    - (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;

发布搜狐

必要深入分析

滚动的图纸 + 提醒器 + 最后一页提醒开关效果图:

4858.com 3guide.gif

UIView的常用方法


  • 获取父/子控件
    1. @property(nonatomic,readonly) UIView *superview;
      收获本人的父控件对象
    2. @property(nonatomic,readonly,copy) NSArray *subviews;
      获得本身的全部子控件对象
    3. @property(nonatomic) NSInteger tag;
      控件的ID(标记),父控件能够通过tag来找到相应的子控件。
    4. @property(nonatomic) CGAffineTransform transform;
      控件的形变属性(能够安装旋转角度、比例缩放、平移等天性)
  • 拉长删减控件
    1. - (void)addSubview:(UIView *) view;丰富二个子控件view
    2. - (void)removeFromSuperview; 从父控件中移除
    3. - (UIView *)viewWithTag:(NSInteger)tag;
      根据四个tag标记找寻相应的控件(一般都以子控件)
  • 控件的职责
    1. @property(nonatomic) CGRect frame;
      控件矩形框在父控件中的地方和尺寸(以父控件的左上角为坐标原点)
    2. @property(nonatomic) CGRect bounds;
      控件矩形框的岗位和尺寸(以温馨左上角为坐标原点,所以bounds的x、y一般为0)
    3. @property(nonatomic) CGPoint center;
      控件中点的岗位(以父控件的左上角为坐标原点)

UITableViewCell的创导及重用

UITableViewCell在创马上,系统会自动安装为懒加载状态.但为了急忙利用每贰个cell,大家供给安装cell的录用标志(identfier),加载cell时先从缓存池中搜求可用的cell.

课程指标

  1. 分界面搭建
  2. 自定义文字输入框
  3. 自定义显示照片的View
  4. 底部 toolBar 自定义(UIStackView)
  5. 表情键盘(二个叶影参差的自定义 View 怎么着一步一步落成出来的)

兑现方式

使用storyboard :<1>设置好UIScrollView + UIImageView +
UIPageControl +
UIButton相应的岗位和内容就能够。<2>连线完毕UIScrollView代理中scrollViewDidScroll措施,滚动进度中切换pageControl的当前页。使用纯代码:与运用storyboard的兑现方式同样,选择接纳代码情势打开管理

UIImageView


  • UIImageView的常用属性

    • @property(nonatomic,retain) UIImage *image; 呈现的图片
    • @property(nonatomic,copy) NSArray *animationImages;
      呈现的动画图片
    • @property(nonatomic) NSTimeInterval animationDuration;
      动画图片的持续时间
    • @property(nonatomic) NSInteger animationRepeatCount;
      动画片的广播次数(暗许是0,代表Infiniti播放)
  • 动画

    • - (void)startAnimating; 最初动画
    • - (void)stopAnimating; 截至动画
    • - (BOOL)isAnimating; 是不是正在举办动画
  • contentMode属性

    1. 含蓄scale单词的:图片有相当大可能率会拉伸

      • UIViewContentModeScaleToFill
        • 将图片拉伸至填充整个imageView
        • 图形显示的尺寸跟imageView的尺寸是同一的
    2. 包罗aspect单词的:保持图片原来的宽高比

      • UIViewContentModeScaleAspectFit
        • 担保刚好能收看图片的满贯
      • UIViewContentModeScaleAspectFill
        • 拉伸至图片的上升的幅度只怕中度跟imageView同样
    3. 从没scale单词的:图片绝对不会被拉伸,保持图片的原尺寸

      • UIViewContentModeCenter
      • UIViewContentModeTop
      • UIViewContentModeBottom
      • UIViewContentModeLeft
      • UIViewContentModeRight
      • UIViewContentModeTopLeft
      • UIViewContentModeTopRight
      • UIViewContentModeBottomLeft
      • UIViewContentModeBottomRight
  1. initWithImage:方法

    • 使用那一个方法创立出来的imageView的尺码和传播的图片尺寸同样
  2. 延迟调用方法

    [abc performSelector:@selector(stand:) withObject:@"123" afterDelay:10];
    
    //10s后自动调用abc的stand:方法,并且传递@"123"参数
    makeObjectsPerformSelector 让数组中所有对象都执行者个方法
    

UITableViewCell的创制格局1:纯代码

  • 设置如下:

  //设置indexPath.row行的cell内容
  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{    
     //设置重用标识
     static NSString *ID=@"cell";
     //根据重用标识从缓存池中查找可用cell
     UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:ID];
     //找不到可用cell,手动创建标识为ID的cell
      if (cell == nil) {
         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
    }
     //板式输出
     cell.textLabel.text = [NSString stringWithFormat:@"test--%zd",indexPath.row];
     //返回cell
     return cell; 
}

分界面搭建

缺点:开采每一个App的教导页都要求开展类似的操作,依据须求的页数得重复张开连锁代码的调动

应用UICollectionView完毕类似于‘图片轮播器’、‘图片浏览器’,系统已经帮大家管理好了cell的选定机制,全体应用起来也是很有利的政工。

UILabel


  1. 常见属性

    • @property(nonatomic,copy) NSString *text; 呈现的文字
    • @property(nonatomic,retain) UIFont *font; 字体
    • @property(nonatomic,retain) UIColor *textColor; 文字颜色
    • @property(nonatomic) NSTextAlignment textAlignment;
      对齐格局(例如左对齐、居中对齐、右对齐)
    • @property(nonatomic) NSInteger numberOfLines; 文字行数
    • @property(nonatomic) NSLineBreakMode lineBreakMode;
      换行方式
  2. UIFont 常用方法

    • + (UIFont *)systemFontOfSize:(CGFloat)fontSize; 系统暗许字体
    • + (UIFont *)boldSystemFontOfSize:(CGFloat)fontSize; 粗体
    • + (UIFont *)italicSystemFontOfSize:(CGFloat)fontSize; 斜体
  3. UILabel完结包裹内容

    • 安装宽度约束为 <= 固定值
    • 安装岗位约束
    • 不用去设置中度自律

UITableViewCell的创造方式2:storyboard

  • 安装如下:

4858.com 4

storyboard设置重用标志

在代码中落成以下措施:

  //设置indexPath.row行的cell内容
  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{    
     //设置重用标识
     static NSString *ID=@"cell";
     //根据重用标识从缓存池中查找可用cell,如果找不到,系统会根据storyboard中的cell标识创建cell.
     UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:ID];
     //板式输出
     cell.textLabel.text = [NSString stringWithFormat:@"test--%zd",indexPath.row];
     //返回cell
     return cell; 
}

导航栏内容

  • 标题视图懒加载

// MARK: - 懒加载

/// 顶部标题视图
private lazy var titleView: UILabel = {
    let label = UILabel()
    // 设置多行
    label.numberOfLines = 0
    // 字体大小
    label.font = UIFont.systemFontOfSize(14)
    // 文字居中
    label.textAlignment = NSTextAlignment.Center
    // 如果有用户昵称
    if let name = HMUserAccountViewModel.sharedInstance.userAccount?.name {
        // 初始化一个带有属性的文字
        var attr = NSMutableAttributedString(string: "发微博\n\(name)")
        // 获取到要添加的属性的范围
        let range = (attr.string as NSString).rangeOfString(name)
        // 添加属性
        attr.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(12), range: range)
        attr.addAttribute(NSForegroundColorAttributeName, value: UIColor.lightGrayColor() ,range: range)
        label.attributedText = attr
    }else{
        label.text = "发微博"
    }
    label.sizeToFit()
    return label
}()
  • 侧面开关懒加载

/// 右边按钮
private lazy var rightButton: UIButton = {
    let button = UIButton()

    // 添加点击事件
    button.addTarget(self, action: "send", forControlEvents: UIControlEvents.TouchUpInside)

    // 设置文字属性
    button.titleLabel?.font = UIFont.systemFontOfSize(13)
    button.setTitle("发送", forState: UIControlState.Normal)

    // 设置不同状态的文字
    button.setTitleColor(UIColor.grayColor(), forState: UIControlState.Disabled)
    button.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)

    // 设置不同状态的背景图片
    button.setBackgroundImage(UIImage(named: "common_button_white_disable"), forState: UIControlState.Disabled)
    button.setBackgroundImage(UIImage(named: "common_button_orange"), forState: UIControlState.Normal)
    button.setBackgroundImage(UIImage(named: "common_button_orange_highlighted"), forState: UIControlState.Highlighted)

    // 设置宽高
    button.height = 30
    button.width = 44

    return button
}()
  • 实现 send 方法

@objc private func send(){
    printLog("发送")
}
  • 设置导航栏内容

// 设置导航栏内容
private func setupNav(){
    // 设置左边 Item
    navigationItem.leftBarButtonItem = UIBarButtonItem.item(title: "返回", target: self, action: "back")
    // 设置中间 titleView
    navigationItem.titleView = titleView
    // 设置右边 Item
    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightButton)
    // 默认为不可用状态
    navigationItem.rightBarButtonItem?.enabled = false
}

运作测量试验

可取: 一句代码就能够调用,传入对应的图样数量,并在回调里面安装图片以及开关的titletitleColor

修改frame的3种方式


  1. 直白采取CGRectMake函数

    imageView.frame = CGRectMake(100, 100, 200, 200);
    
  2. 利用一时结构体变量

    CGRect tempFrame = imageView.frame;
    tempFrame.origin.x = 100;
    tempFrame.origin.y = 100;
    tempFrame.size.width = 200;
    tempFrame.size.height = 200;
    imageView.frame = tempFrame;
    
  3. 运用大括号{}情势

    imageView.frame = (CGRect){{100, 100}, {200, 200}};
    

UITableViewCell的创始方式3:register

兑现情势如下:

    //先在控制器中对cell的标识注册
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];

  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{    
     //设置重用标识
     static NSString *ID=@"cell";
     //根据重用标识从缓存池中查找可用cell,如果找不到,系统会根据注册的cell标识创建cell.
     UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:ID];
     //板式输出
     cell.textLabel.text = [NSString stringWithFormat:@"test--%zd",indexPath.row];
     //返回cell
     return cell; 
}

文字输入框

  1. 含蓄占位文字
  2. 能够像 UITextView 同样输入多行
  3. 自定义二个输入框承袭于 UITextView,向在那之中增加叁个 label
  • 代码完成

class HMTextView: UITextView {

    /// 重写的是指定构造函数
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)

        // 添加占位控件
        addSubview(placeholderLabel)

        // 添加约束
        placeholderLabel.snp_makeConstraints { (make) -> Void in
            make.width.lessThanOrEqualTo(self.snp_width).offset(-10)
            make.leading.equalTo(self.snp_leading).offset(5)
            make.top.equalTo(self.snp_top).offset(8)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // 占位文字控件
    private lazy var placeholderLabel: UILabel = {
        let label = UILabel()
        // 设置文字颜色以及大小
        label.font = UIFont.systemFontOfSize(12)
        label.textColor = UIColor.lightGrayColor()
        label.text = "请输入文字"

        // 多行
        label.numberOfLines = 0
        return label
    }()
}
  • 添加到 controller 中使用

// 懒加载控件
private lazy var textView: HMTextView = {
    let textView = HMTextView()
    return textView
}()

// setupUI 方法中添加子控件并设置约束

view.addSubview(textView)
textView.snp_makeConstraints { (make) -> Void in
    make.edges.equalTo(self.view.snp_edges)
}

运转测量试验

  • HMTextView 中提须求外部设置占位文字的性质

// 添加 placeholder 属性,代外界设置值
var placeholder: String? {
    didSet{
        placeholderLabel.text = placeholder
    }
}
  • 重写 font 属性,以让占位文字与输入的文字字体大小同样

override var font: UIFont? {
    didSet{
        placeholderLabel.font = font
    }
}
  • 外部设置文字大小

textView.font = UIFont.systemFontOfSize(16)

运营测量试验:占位文字与输入的文字同样大

  • 监听文字更换的时候,去施行占位控件的藏匿与呈现逻辑

// 监听文字改变的通知
NSNotificationCenter.defaultCenter().addObserver(self, selector: "textDidChange", name: UITextViewTextDidChangeNotification, object: self)
  • 文字改换之后调用的主意

/// 文字改变的时候会调用这个方法,当前如果有文字的话就隐藏占位 label
@objc private func textDidChange(){
    placeholderLabel.hidden = hasText()
}

运营测验。注:监听文字改造在这些地方不要接纳代理,因为自身相似不成为亲善的代办。

您必要什么控件

4858.com 5控件

图片的加载形式


  • 有缓存

    UIImage *image = [UIImage imageNamed:@"图片名"];
    
    • 采用场面:图片十分小、使用成效较高
    • 提出把须求缓存的图片直接放到Images.xcassets
  • 无缓存

    NSString *file = [[NSBundle mainBundle] pathForResource:@"图片名" ofType:@"图片的扩展名"];
    UIImage *image = [UIImage imageWithContentsOfFile:@"图片文件的全路径"];
    
    • 运用场馆:图片比十分的大、使用功能比较小
    • 不须求缓存的图形不能够放在Images.xcassets
    • 位居Images.xcassets里面包车型大巴图纸,只可以通过图片名去加载图片

UITableView及UITableViewCell的一对属性

UITableView承继自UIScrollView,即使形成了UITableView的代办,也就代表同样遵守了UIScrollView的代理公约,能够兑现UIScrollViewDelegate中的方法来监听UITableView的滚动事件.

在UITableView中每一种section的头顶或尾部,假使想设置图片,能够兑现以下方法.

  //在section的底部区域显示一个UIView控件
  - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{
    return expression;
}
  //在section的头部区域显示一个UIView控件
  - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
    return expression;
}

如果想监听点击某一行,能够兑现以下办法:

   - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

万一想设置tableView不突显分界线,能够安装以下属性

   self.tableView separatorStyle = UITableViewCellSeparatorStyleNone;

UITableViewCell中的控件其实都足够到了cell中的Content
View上边,在自定义控件增多时,必要调用
[cell.contentView addSubview:(nonnull UIView
*)]情势,加多到cell的.contentView中去,这也是苹果官方推荐加多的一种格局.

底部 ToolBar 初始化

//设置约束
        toolBar.snp_makeConstraints { (make) -> Void in
            make.left.right.bottom.equalTo(self.view)
        }
        var items = [UIBarButtonItem]()
        //添加 UIBarButtonItem类型的对象到数据源数组中
        let itemSettings = [["imageName": "compose_toolbar_picture","actionName": "selectPicture"],
            ["imageName": "compose_mentionbutton_background"],
            ["imageName": "compose_trendbutton_background"],
            ["imageName": "compose_emoticonbutton_background", "actionName": "selectEmoticon"],
            ["imageName": "compose_add_background"]]

        for item in itemSettings {
            let imageName = item["imageName"]
            let btn = UIButton()
            btn.setImage(UIImage(named: imageName!), forState: .Normal)
            btn.setImage(UIImage(named: imageName! + "_highlighted"), forState: .Highlighted)
            btn.sizeToFit()
            if let actionName = item["actionName"] {
                btn.addTarget(self, action: Selector(actionName), forControlEvents: .TouchUpInside)
            }

            let barItem = UIBarButtonItem(customView: btn)
            //添加到数组中
            items.append(barItem)
            //添加弹簧类型的item  FlexibleSpace: 可伸缩的弹簧
            let space = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target: nil, action: nil)
            items.append(space)
        }

        items.removeLast()
        toolBar.items = items
  • HMComposeViewController 中懒加载控件

/// composeToolBar
private lazy var composeToolBar: HMComposeToolBar = HMComposeToolBar(frame: CGRectZero)
  • HMComposeViewControllersetupUI 方法中增多控件与约束

view.addSubview(composeToolBar)

// 添加约束
composeToolBar.snp_makeConstraints { (make) -> Void in
    make.bottom.equalTo(self.view.snp_bottom)
    make.width.equalTo(self.view.snp_width)
    make.height.equalTo(44)
}

起来码代码

节奏播放


  1. 开创八个音频文件的U昂CoraL(UCRUISERL就是文本路线对象)
    • NSURL *url = [[NSBundle mainBundle]
      ULacrosseLForResource:@”音频文件名”
      withExtension:@”音频文件的恢宏名”];
  2. 开创播放器
    • self.player = [AVPlayer playerWithURL:url];
  3. 播放
    • [self.player play];
  4. init 内部会调用 initWithFrame方法

成立自定义等高cell方式1:storyboard

  1. 在storyboard中放入贰个UITableViewController(大概UIViewController中嵌套多少个UITableView),系统会暗中认可自带八个cell.

4858.com 6

2.退换cell的录取标志(identfier)

4858.com 7

3.给各类cell绑定三个tag,方便设置数据.

4858.com 8

4.在数据源方法中装置cell的来得内容

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID=@"cell";

    UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:ID];

    //获取模型数据
    DLShop *shop = self.shops[indexPath.row];
    //给cell赋值
    UIImageView *iconView = (UIImageView *)[cell viewWithTag:1];
    iconView.image = [UIImage imageNamed:shop.icon];

    UILabel *titleLabel = (UILabel *)[cell viewWithTag:2];
    titleLabel.text = shop.title;

    UILabel *priceLabel = (UILabel *)[cell viewWithTag:3];
    priceLabel.text = shop.price;

    UILabel *buyCount = (UILabel *)[cell viewWithTag:4];
    buyCount.text = shop.buyCount;

    return cell;
}

storyboard中自定义cell需要用到tag,开发中会比较不方便,不推荐这种自定义方法.

底层 ToolBar 跟随键盘移动

  • 监听键盘 frame 改换文告

// 监听键盘 frame 改变通知
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillChangeFrame:", name: UIKeyboardWillChangeFrameNotification, object: nil)
  • 撤回通告

deinit{
    NSNotificationCenter.defaultCenter().removeObserver(self)
}
  • 在键盘 frame 更动做立异约束的逻辑

/// 键盘 frame 改变通知调用的方法
@objc private func keyboardWillChangeFrame(noti: NSNotification){

    let endFrame = (noti.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()

    // 更新约束
    composeToolBar.snp_updateConstraints { (make) -> Void in
        make.bottom.equalTo(self.view.snp_bottom).offset(endFrame.origin.y - self.view.height)
    }

    UIView.animateWithDuration(0.25) { () -> Void in
        self.composeToolBar.layoutIfNeeded()
    }
}
  • 拖动 textView 的时候退下键盘:展开 textView
    垂直方向弹簧效果,并安装代理

textView.alwaysBounceVertical = true
textView.delegate = self
  • 金玉满堂合同,并贯彻协议方式

func scrollViewDidScroll(scrollView: UIScrollView) {
    self.view.endEditing(true)
}
  • 实现 textViewDidChange
    的艺术,当textView有文字输入的时候入手按键可用

func textViewDidChange(textView: UITextView) {
    //设置占位的文本的隐藏或者显示
        placeholderLabel.hidden = textView.hasText()
        //设置 发布按钮的 交互 和不可交互状态
        //有文本就允许交互
        navigationItem.rightBarButtonItem?.enabled = textView.hasText()
}
全面包车型客车男女恐怕开采系统的头文件属性的注解都以维系一种风格,为此作者属性表明也是仿照系统的品格

1.首先新建HHGuidePageViewController:UIViewController,用HHGuidePageViewController来落到实处因人而异页。2.然后增添多个属性collectionViewpageControlcount.3.紧接着新建壹个HHGuidePageCell:UICollectionViewCell,根据必要可见cell只必要安装2个属性就能够(imageViewfinishBtn).4.最终我们在HHGuidePageViewController自定义的开始化方法中增加控件,注册cell

HHGuidePageViewController中的早先化

/** 初始化方法 @param count 图片总数量 @param setupCellHandler cell的回调 回调参数cell indexPath @param finishBtnHandler 最后一个按钮的回调 @return self */-(instancetype)initWithPagesCount:(NSInteger)count setupCellHandler:(setupCellHandler)setupCellHandler finishHandler:(finishButtonHandler)finishBtnHandler{ if (self = [super initWithNibName:nil bundle:nil]) { _count = count; _setupCellHandler =[setupCellHandler copy]; _finishBtnHandller =[finishBtnHandler copy]; //使用懒加载初始化 [self.view addSubview: self.collectionView]; [self.view addSubview:self.pageControl]; //注册cell [self.collectionView registerClass:[HHGuidePageCell class] forCellWithReuseIdentifier:GuidePageCellID]; } return self;}

行使懒加载的法子开头化collectionViewpageControl设置有些主导属性以及frame,懒加载这里不显得具体代码,详细情形请下载查看具体代码。况兼达成四个必得求完结的数据源方法,供给注意的是在cellForItemAtIndexPath中在体现cell的时候大家要求加个判别,是或不是出示按键。

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ HHGuidePageCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:GuidePageCellID forIndexPath:indexPath]; if (indexPath.row == self.count -1) {//如果是最后一页,则添加响应事件 cell.finishBtn.hidden = NO; [cell.finishBtn addTarget:self action:@selector(finishBtnOnClick:) forControlEvents:UIControlEventTouchUpInside]; }else{ cell.finishBtn.hidden = YES; } if (self.setupCellHandler) { self.setupCellHandler(cell,indexPath); } return cell;}

进而落成UIScrollView代办中的scrollViewDidScroll方法,来更改pageControl的脚下页码

-scrollViewDidScroll:(UIScrollView *)scrollView{ NSInteger currentPage = scrollView.contentOffset.x / scrollView.bounds.size.width + 0.5; if (self.pageControl.currentPage != currentPage) { self.pageControl.currentPage = currentPage; }}

最后在AppDelegate代理方法中didFinishLaunchingWithOptions调用

 if (![[NSUserDefaults standardUserDefaults]boolForKey:@"firstStart"]) { [[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"firstStart"]; HHGuidePageViewController *guidePageVc = [[HHGuidePageViewController alloc]initWithPagesCount:3 setupCellHandler:^(HHGuidePageCell *cell, NSIndexPath *idnexPath) { NSString *imageNames =[NSString stringWithFormat:@"intro_%zd",idnexPath.row]; cell.imageView.image =[UIImage imageNamed:imageNames]; [cell.finishBtn setTitle:@"立即进入" forState:UIControlStateNormal]; [cell.finishBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; } finishHandler:^(UIButton *finishBtn) { NSLog(@"%@",finishBtn.titleLabel.text); self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController: [[ViewController alloc]init]]; }]; guidePageVc.pageControl.currentPageIndicatorTintColor = [UIColor whiteColor]; guidePageVc.pageControl.pageIndicatorTintColor = [UIColor orangeColor]; self.window.rootViewController = guidePageVc; }else{ self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController: [[ViewController alloc]init]]; }

附送demo下载地址

UIButton


  • 监听按键点击

    • [button addTarget:self action:@selector(buttonClick:)
      forControlEvents:UIControlEventTouchUpInside];
    • 凡是承袭自UIControl的控件,都能够因而addTarget:…方法来监听事件
  • 自定义开关:调治内部子控件的frame

    • 方法1:完毕titleRectForContentRect:和imageRectForContentRect:方法,分别重临titleLabel和imageView的frame
    • 艺术2:在layoutSubviews方法中装置
  • 内边距

    1. 安装按键内容的内边距(影响到imageView和titleLabel)
      • @property(nonatomic) UIEdgeInsets contentEdgeInsets;
    2. 设置titleLabel的内边距(影响到titleLabel)
      • @property(nonatomic) UIEdgeInsets titleEdgeInsets;
    3. 安装imageView的内边距(影响到imageView)
      • @property(nonatomic)UIEdgeInsets imageEdgeInsets;

开创自定义等高cell方式2:自定义cell

1.新建一个类

4858.com 9

2.在storyboard中选中cell,修改class为类名

4858.com 10

3.将cell中的控件拖线至新建类中

4858.com 11

4.在新建类.h文件中含有模型类,定义三个模子属性接收模型数据

#import <UIKit/UIKit.h>
@class DLShop;

@interface DLShopCell : UITableViewCell
// 定义模型属性,接收数据
@property(nonatomic,strong) DLShop *shop;
@end

5.在新建类.m文件中,导入模型头文件,重写模型属性的set方法,设置传入的模型数据到子控件>

@implementation DLShopCell

-(void)setShop:(DLShop *)shop{

    _shop = shop;

    self.icon.image = [UIImage imageNamed:shop.icon];
    self.title.text = shop.title;
    self.price.text = [NSString stringWithFormat:@"¥%@",shop.price];
    self.buyCount.text = [NSString stringWithFormat:@"%@人已购买",shop.buyCount];

}
@end

6.在调整器中贯彻数据源方法,设置数据

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID=@"cell";

    // 新建一个DLShopCell类型的cell,保证创建的cell是我们自定义的
    DLShopCell *cell=[self.tableView dequeueReusableCellWithIdentifier:ID];

    //获取模型数据
    cell.shop = self.shops[indexPath.row];

    return cell;
}

自定义类名创建cell比较方便,能很好的封装cell控件,设置数据也很方便.

分选照片

模型


  • 概念

    • 特意用来存放数据的对象
  • 特点

    • 相似间接接轨自NSObject
    • 在.h文件中扬言一些用来贮存数据的属性
  • 模型定义示例

    @interface Shop : NSObject
    /** 名字 */
    @property (nonatomic, strong) NSString *name;
    /** 图标 */
    @preperty (nonatomic, strong) NSString *icon;
    @end
    
  • 字典转模型示例

    -(instancetype)initWithDict:(NSDictionary *)dict
    {
        if (self == [super init]) {
    
        self.name = dict[@"name"];
        self.icon = dict[@"icon"];
        }
     return self;
    }
    

创办自定义等高cell情势3:xib自定义cell

1.新建一个xib文件,名称与自定义类名保持一致.

4858.com 12

2.修改xib的class为自定义类的称号,保证开创时能够找到xib文件

4858.com 13

3.安装xib内部cell的重用标记

4858.com 14

4.从xib拖线至自定义类中

4858.com 15

5.在自定义类.h文件中满含模型类,定义叁个模子属性接收模型数据,相同的时间提供三个类措施,将tableView传进去.

#import <UIKit/UIKit.h>
@class DLShop;

@interface DLShopCell : UITableViewCell
// 定义模型属性,接收数据
@property(nonatomic,strong) DLShop *shop;

// 外界传入tableView,返回DLShopCell类型的cell
+ (instancetype)cellWithTableView:(UITableView *)tableView;
@end

6.在.m文件中落到实处上述方法.

@implementation DLShopCell
//重写模型属性set方法设置传入模型数据到子控件
-(void)setShop:(DLShop *)shop{
    _shop = shop;

    self.icon.image = [UIImage imageNamed:shop.icon];
    self.title.text = shop.title;
    self.price.text = [NSString stringWithFormat:@"¥%@",shop.price];
    self.buyCount.text = [NSString stringWithFormat:@"%@人已购买",shop.buyCount]; 
}
//实现cellWithTableView:将外界传入tableView查找缓存池数据,如果没有,创建并返回DLShopCell类型的cell
+ (instancetype)cellWithTableView:(UITableView *)tableView{
    static NSString *ID = @"cell";
    DLShopCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    if (cell == nil) {
        cell = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([DLShopCell class])  owner:nil options:nil] lastObject];
    }
    return cell;
}
@end

xib自定义cell完美实现了控件的封装,将控件的所有属性都包装在自己内部,推荐使用xib自定义cell,方便后期维护及使用.

目标

  • 在独立的档期的顺序中付出独立的效劳,可能直接切换根调整器
  • 开辟完毕后再组成到现存项目中
  • 增进工效,静心开辟品质 
  • 慎选照片
  • 重新建立控件布局

Plist解析


  1. 得到Plist文件的全路线

    NSBundle *bundle = [NSBundle mainBundle];
    NSString *path = [bundle pathForResource:@"shops" ofType:@"plist"];
    
  2. 加载plist文件

    _shops = [NSArray arrayWithContentsOfFile:path];
    

懒加载

在调控器中新建数组属性保存模型数据时,一般会利用懒加载观念.步骤如下:

1.在调控器文件中定义三个数组,保存传入的模型数据.

@interface TestTableViewController ()
// 用来保存传入的模型数据
@property(nonatomic,strong) NSArray *shops;
@end

2.重写属性的get方法,用到时再调用该属性,以落到实处懒加载.

- (NSArray *)shops{

    if (_shops == nil) {
        NSString *path = [[NSBundle mainBundle] pathForResource:@"shops.plist" ofType:nil];
        //取出字典数据转为数组
        NSArray *dictArr = [NSArray arrayWithContentsOfFile:path];
        //创建一个空的可变数组接收模型数据
        NSMutableArray *dealArrM = [NSMutableArray array];
        //遍历数组取出字典数据
        for (NSDictionary *dict in dictArr) {
            //定义模型接收字典数据
            DLShop *shop = [DLShop shopWithDict:dict];
            //将数据添加到模型
            [dealArrM addObject:shop];
        }//传递模型到成员变量中
        _shops = dealArrM;
    }
    return _shops;
}

类型打算

  • 新建文件夹 PictureSelector
  • 新建 PictureSelectorViewController 继承自
    UICollectionViewController
  • AppDelegate 增多以下代码

window?.rootViewController = PictureSelectorViewController()

运营测试

透过代码自定义控件


  • 继续自系统自带的控件,写贰个属于自身的控件
  • 目标:封装控件内部的细节,不让外部关心
  • 步骤
    • 新建二个持续UIView的类
    • initWithFrame:方法中增添子控件
    • layoutSubviews主意中设置子控件的frame
      • 早晚要调用[super layoutSubviews];
    • 提供二个模型属性,重写模型属性的set方法
      • 在set方法中收取模型属性,给相应的子控件赋值

设置tableview有值时才展现分水线

- (void)viewDidLoad{
  [super ViewDidLoad];
  //设置tableview有值时才显示分割线
  self.tableView.tableFooterView = [UIView alloc] init];
}

代码实现

由此xib自定义控件


  • 新建三个接二连三UIView的类
  • 新建三个xib文件(xib的文件名最佳跟控件类名同样)
    • 增多子控件、设置子控件属性
    • 修改最外面那二个控件的class为控件类名
    • 将子控件实行连线
  • 提供模型属性,重写模型的set方法
    • 在set方法中给子控件设置数据
  • - (void)awakeFromNib; xib 加载成功之后调用

设置布局

  • 增多调控器构造函数,简化外界调用

/// 可重用标识符号
private let WBPictureSelectorViewCellID = "WBPictureSelectorViewCellID"

/// 照片选择控制器
class PictureSelectorViewController: UICollectionViewController {

    // MARK: - 构造函数
    init() {
        let layout = UICollectionViewFlowLayout()

        super.init(collectionViewLayout: layout)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // 注册可重用 Cell
        self.collectionView!.registerClass(UICollectionViewCell.self,
            forCellWithReuseIdentifier: WBPictureSelectorViewCellID)
    }
}
  • 设置背景颜色

collectionView?.backgroundColor = UIColor.lightGrayColor()

注意在 CollectionViewController 中,collectionView 不是 view

  • 修改数据源方法

// MARK: 数据源
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 10
}

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(WBPictureSelectorViewCellID, forIndexPath: indexPath)

    // Configure the cell
    cell.backgroundColor = UIColor.redColor()

    return cell
}
  • 设置 cell 尺寸

init() {
    let layout = UICollectionViewFlowLayout()
    // 屏幕越大,显示的内容应该越多
    layout.itemSize = CGSize(width: 80, height: 80)
    layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 0, right: 20)

    super.init(collectionViewLayout: layout)
}

从 黑莓 6 伊始,就必要怀想越大的显示屏突显更加多的源委

view 封装


  • 万一二个view内部的子控件非常多,一般会虚拟自定义贰个view,把它里面子控件的始建屏蔽起来,不让外部关切
  • 外部得以流传对应的模子数据给view,view获得模型数据后给个中的子控件设置相应的数额
  • 装进控件的基本步骤
    1. 在initWithFrame:方法中加多子控件,提供有利构造方法
    2. 在layoutSubviews方法中设置子控件的frame(绝对要调用super的layoutSubviews)
      追加模型属性,在模型属性set方法中装置数据到子控件上,只要 frame
      发生改作育能够调用本办法。

自定义 Cell

  • 加上资料

    • 自定义 Cell

/// 照片选择单元格
private class PictureSelectorViewCell: UICollectionViewCell {

    // MARK: - 构造函数
    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    /// 设置界面
    private func setupUI() {
        // 添加控件
        contentView.addSubview(addButton)
        contentView.addSubview(removeButton)

        // 自动布局
        addButton.snp_makeConstraints { (make) -> Void in
            make.edges.equalTo(contentView.snp_edges)
        }
        removeButton.snp_makeConstraints { (make) -> Void in
            make.top.equalTo(contentView.snp_top)
            make.right.equalTo(contentView.snp_right)
        }
    }

    // MARK: - 懒加载控件
    /// 添加按钮
    private var addButton = UIButton(imageName: "compose_pic_add", backImageName: nil)
    /// 删除按钮
    private var removeButton = UIButton(imageName: "compose_photo_close", backImageName: nil)
}
  • 修改注册的 Cell

// 注册可重用 Cell
collectionView!.registerClass(PictureSelectorViewCell.self,
    forCellWithReuseIdentifier: WBPictureSelectorViewCellID)
  • 修改数据源

let cell = collectionView.dequeueReusableCellWithReuseIdentifier(WBPictureSelectorViewCellID, forIndexPath: indexPath) as! PictureSelectorViewCell
  • 开关监听方法

// MARK: - 监听方法
/// 添加照片
@objc private func addPicture() {
    print("添加照片")
}

/// 删除照片
@objc private func removePicture() {
    print("删除照片")
}
  • 累加监听方法

// 监听方法
addButton.addTarget(self, action: "addPicture", forControlEvents: .TouchUpInside)
removeButton.addTarget(self, action: "removePicture", forControlEvents: .TouchUpInside)

控件伊始化调用的不二诀窍


  • 二个控件有2种创设方式
    1. 透过代码创制
      初步化时必然会调用initWithFrame:方法
    2. 通过xib\storyboard创建
      -最初化时不会调用initWithFrame:方法,只会调用initWithCoder:方法

      • 开始化实现后会调用awakeFromNib方法
    3. 神迹希望在控件初叶化时做一些早先化操作,比方加多子控件、设置基本属性
      • 这时候急需依靠控件的创始情势,来抉择在initWithFrame:、initWithCoder:、awakeFromNib的哪个方法中操作

行使代理传递开关点击事件

  • 概念协议传递音信

/// 照片选择单元格代理
private protocol PictureSelectorViewCellDelegate: NSObjectProtocol {
    /// 添加照片
    func pictureSelectorViewCellDidAdd(cell: PictureSelectorViewCell)
    /// 删除照片
    func pictureSelectorViewCellDidRemove(cell: PictureSelectorViewCell)
}
  • 设置代理

/// 照片选择代理
weak var pictureDelegate: PictureSelectorViewCellDelegate?
  • 修改监听方法

// MARK: - 监听方法
/// 添加照片
@objc private func addPicture() {
    pictureDelegate?.pictureSelectorViewCellDidAdd(self)
}

/// 删除照片
@objc private func removePicture() {
    pictureDelegate?.pictureSelectorViewCellDidRemove(self)
}
  • extension 中贯彻公约格局

// MARK: - PictureSelectorViewCellDelegate
extension PictureSelectorViewController: PictureSelectorViewCellDelegate {
    private func pictureSelectorViewCellDidAdd(cell: PictureSelectorViewCell) {
        print("添加照片")
    }

    private func pictureSelectorViewCellDidRemove(cell: PictureSelectorViewCell) {
        print("删除照片")
    }
}
  • 在数据源方法中安装代理

cell.pictureDelegate = self

在意:假设协商是私有的,那么公约格局也非得是个人的

耳熏目染动画

  • 方式1:头尾式

    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:2.0];
    
    /* 需要执行动画的代码 */
    
    [UIView commitAnimations];
    
  • 方式2:block式

        [UIView animateWithDuration:2.0 delay:1.0 options:kNilOptions animations:^{
         /*需要执行动画的代码 */
        } completion:nil]
        // 1s后,再执行动画(动画持续2s)
    

慎选照片

  • 推断是或不是援救访谈相册

// 添加照片
private func pictureSelectorViewCellDidAdd(cell: PictureSelectorViewCell) {
    // 判断是否支持照片选择
    if !UIImagePickerController.isSourceTypeAvailable(.PhotoLibrary) {
        print("无法访问照片库")
        return
    }
}
  • 做客相册

// 访问相册
let picker = UIImagePickerController()
presentViewController(picker, animated: true, completion: nil)
  • 安装代理

// 设置代理
picker.delegate = self
  • 遵从合同并落实方式

// MARK: - UIImagePickerControllerDelegate, UINavigationControllerDelegate
extension PictureSelectorViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    /// 选中媒体代理方法
    ///
    /// - parameter picker: 照片选择器
    /// - parameter info:   信息字典 allowsEditing = true 适合选择头像
    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
        print(info)

        dismissViewControllerAnimated(true, completion: nil)
    }
}

注意:一旦达成了代办方法,则须要用代码 dismiss 控制器

图表拉伸


  • iOS5之前
    • 只拉伸中间的1×1区域
      • - (UIImage *)stretchableImageWithLeftCapWidth: (NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight;
  • iOS5开始
    • - (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets;
    • - (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode; (拉伸还是平铺);

安装图片数据源

  • 概念照片数组

/// 照片数组
private lazy var pictures = [UIImage]()
  • 在代理方法中插入照片

let image = info[UIImagePickerControllerOriginalImage] as! UIImage

pictures.append(image)
collectionView?.reloadData()

dismissViewControllerAnimated(true, completion: nil)
  • 在 cell 中添加 image 属性

/// 照片
private var image: UIImage? {
    didSet {
        addButton.setImage(image, forState: .Normal)
    }
}
  • 修改数据源中的图像数据函数

override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return pictures.count + 1
}

担保末尾有四个加号按键增多照片

  • 在数据源方法中装置图像

cell.image = (indexPath.item == pictures.count) ? nil : pictures[indexPath.item]
  • 扩展 image 属性的 didSet 函数

/// 照片
private var image: UIImage? {
    didSet {
        addButton.setImage(image ?? UIImage(named: "compose_pic_add"), forState: .Normal)
        addButton.setImage(image ?? UIImage(named: "compose_pic_add_highlighted"), forState: .Highlighted)
    }
}

KVC


  • 万事俱备:Key Value Coding(键值编码)

  • 赋值

    • (void)setValue:(id)value forKey:(NSString *)key;
    • (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
    • (void)setValuesForKeysWithDictionary:(NSDictionary
      *)keyedValues;
  • 取值

    • 能获取私有成员变量的值
      • (id)valueForKey:(NSString *)key;
      • (id)valueForKeyPath:(NSString *)keyPath;
      • (NSDictionary *)dictionaryWithValuesForKeys:(NSArray
        *)keys;

细节管理

KVO


  • 万事俱备:Key Value Observing(键值监听)

  • 作用:监听模型的属性值改换

  • 步骤

    1. 加多监听器
    • 采纳b对象来监听a对象name属性的改造

    [a addObserver:b forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"test"];
    
    1. 在监听器中完成监听方法

      • 即使属性发生改动会自行调用上边方法

      - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change     context:(void *)context
      {
      NSLog(@"%@ %@ %@ %@", object, keyPath, change, context);
      }
      

记录顾客点击按钮的目录

  • 概念当前当选照片索引

/// 当前选中照片索引
private var currentIndex = 0
  • 在代理方法中著录当前客户点击 cell 的目录

// 记录当前用户选中索引
currentIndex = collectionView!.indexPathForCell(cell)!.item
  • 在照片选拔调节器的代办方法中装置相应的图像

if currentIndex < pictures.count {
    pictures[currentIndex] = image
} else {
    pictures.append(image)
}
collectionView?.reloadData()

怎么监听控件的表现


  • 通过addTarget:
    • 独有一而再自UIControl的控件,才有其一职能
    • UIControlEventTouchUpInside : 点击事件(UIButton)
    • UIControl伊芙ntValueChanged :
      值退换事件(UISwitch、UISegmentControl、UISlider)
    • UIControlEventEditingChanged : 文字更换事件(UITextField)
    • addTarget 调用的格局的参数可以把本身传入进去
  • 通过delegate
    • 唯有保有delegate属性的控件,才有其一成效

设置照片填充情势

// 设置照片填充模式
addButton.imageView?.contentMode = .ScaleAspectFill

UIScrollView


  • UIScrollView的主干用法
    1. 增加源委到UIScrollVive
    2. 安装 contentSize 属性,滚动范围
    3. 一经 UIScrollView 不可能滚动
      • 从不安装 contentSize
      • scrollEnabled = NO;
      • userInteractionEnabled = NO 未有顾客交互
        • 设置按钮的userInteractionEnabled并不能够让按键和其他控件达到这么些情形
    4. 注意点
      • 通过代码创制的scrollView未有子控件(滚动条)
      • 由此 storyBoard、xib 创制的会有滚动条
  • 常见属性
    1. contentOffset用来代表偏移量,内容左上角与 scrollview 的区间值
    2. contentSize 表示内容的尺码,滚动的界定
    3. contentInset 内间距,scrollview
      4周扩充额外的轮转区域,一般用来防止scrollView的剧情被其余控件挡住
    4. bounces 是不是须要弹簧效果
    5. 一直有弹簧效果(一般用于上/下拉刷新效率),固然没安装
      contengSize 也有成效

      • alwaysBounceVertical
      • alwaysBounceHorizontal
  • Delegate

    1. 设置代理为为调控器的靶子(监听 scrollView 的各个表现)
      scrollView.delegate = 控制器

    2. 在民用扩充里面(佚名分类)遵循公约

    3. 监听 scrollView 的滚动

      • - (void)scrollViewDidScroll 只要 scrollView 滚动就能够调用
      • - (void)scrollViewDidEndDragging...(BOOL)decelerate;
        甘休滚动有2种状: decelerate
        为0象征停止滚动,完全禁止,为1表示用户停止拖拽,由于 scrollview 惯性,会,会继续滚动,并且减速
      • - (void)scrollViewDidEndDecelerating... 表示甘休减速
    4. 缩放

      • 完成步骤
        • 设置UIScrollView的id<UIScrollViewDelegate>
          delegate代理对象
        • 安装minimumZoomScale :缩短的蝇头比例
        • 设置maximumZoomScale :放大的最大比例
        • 让代理对象实现上边包车型大巴办法,重临须要缩放的视图控件
        • - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
          顾客在UIScrollView身上使用捏合手势时,UIScrollView会调用代理的viewForZoomingInScrollView:方法,那些方式重临的控件正是索要进行缩放的控件
    5. 分页

      • UIScrollView 的 pageEnabled 设置 YES
      • @property(nonatomic) NSInteger numberOfPages; 一共有多少页
      • @property(nonatomic) NSInteger currentPage;当前展示的页码
      • @property(nonatomic) BOOL hidesForSinglePage;
        唯有一页时,是或不是要求遮蔽页码提醒器
      • @property(nonatomic,retain) UIColor *pageIndicatorTintColor;
        别的提醒器页码的颜色
      • @property(nonatomic,retain) UIColor*currentPageIndicatorTintColor;
        最近页码提示器的颜料

删除照片

  • 剔除照片操作

// 删除照片
private func pictureSelectorViewCellDidRemove(cell: PictureSelectorViewCell) {

    guard let indexPath = collectionView?.indexPathForCell(cell) else {
        return
    }

    pictures.removeAtIndex(indexPath.item)
    collectionView?.deleteItemsAtIndexPaths([indexPath])
}
  • 默许隐敝删除按键

/// 照片
private var image: UIImage? {
    didSet {
        addButton.setImage(image ?? UIImage(named: "compose_pic_add"), forState: .Normal)
        addButton.setImage(image ?? UIImage(named: "compose_pic_add_highlighted"), forState: .Highlighted)

        removeButton.hidden = (image == nil)
    }
}

NSTimer


  • NSTimer叫做“电火花计时器”,它的效应如下

    • 在钦定的年月施行钦定的天职
    • 每隔一段时间施行钦命的天职
  • 调用上面包车型地铁办法就能够开启三个定期职责

    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
    每隔ti秒,调用一次aTarget的aSelector方法,yesOrNo决定了是否重复执行这个任务
    
    • 因而invalidate方法能够告一段落机械漏刻的做事,一旦沙漏被截止了,就不能够重复实行职责。只好再成立二个新的反应计时器技能实行新的天职
      • - (void)invalidate;
    • 斩草除根放大计时器在主线程不坐班的题目

    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    

设置最多选取照片数量

  • 概念最多照片常量

/// 最大照片数量
private let WBPictureSelectorViewMaxPictureCount = 9
  • 修改数据源方法

// MARK: 数据源
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return pictures.count + (pictures.count < WBPictureSelectorViewMaxPictureCount ? 1 : 0)
}

Autolayout


  1. 代码创立

    • 动用NSLayoutConstraint的类格局

        +(id)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;
        view1 :要约束的控件
        attr1 :约束的类型(做怎样的约束)
        relation :与参照控件之间的关系
        view2 :参照的控件
        attr2 :约束的类型(做怎样的约束)
        multiplier :乘数
        c :常量
    
    • 拉长封锁对象到对应的view上

        - (void)addConstraint:(NSLayoutConstraint *)constraint;
        - (void)addConstraints:(NSArray *)constraints;
    
    • 代码完结Autolayout的注目点
      • 要先禁止autoresizing成效,设置view的上边属性为NO
        view.translatesAutoresizingMaskIntoConstraints = NO,
        增多封锁在此以前,必要求保险相关控件都早已在分级的父控件上无须再给view
        设置frame
    • 自律法则创立约束之后,要求将其加多到成效的view上
      • 对于五个同层级view之间的自律关系,增多到它们的父view上
      • 对于八个例外层级view之间的牢笼关系,加多到他们日前的协同父view上
      • 对于有档次关系的几个view之间的束缚关系,加多到等级次序较高的父view上
  2. VFL

  • 齐全Visual Format Lanuage 可视化格式语言, 是苹果公司为了简化
    autolayout 的编码而生产的肤浅语言

        H:[cancelButton(72)]-12-[acceptButton(50)]
        canelButton宽72,acceptButton宽50,它们之间间距12
    
        H:[wideView(>=60@700)]
        wideView宽度大于等于60point,该约束条件优先级为700(优先级最大值为1000,优先级越高的约束越先被满足)
    
        V:[redBox][yellowBox(==redBox)]
        竖直方向上,先有一个redBox,其下方紧接一个高度等于redBox高度的yellowBox
    
        H:|-10-[Find]-[FindNext]-[FindField(>=20)]-|
        水平方向上,Find距离父view左边缘默认间隔宽度,
        之后是FindNext距离Find间隔默认宽度;再之后是宽度不小于20的FindField它和FindNext以及父view右边缘的间距都是默认宽度。
        竖线“|”表示superview的边缘
    
  • 使用VFL来创设约束数组

    + (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics 
    

views:(NSDictionary *)views;
format :VFL语句
opts :约束类型
metrics :VFL语句中用到的现实数值
views :VFL语句中用到的控件

    - 创建一个字典(内部包含VFL语句中用到的控件)的快捷宏定义
`NSDictionaryOfVariableBindings(...)`

内部存款和储蓄器管理

  • 缩放图片

extension UIImage {

    /// 将图像缩放到指定宽度
    ///
    /// - parameter width: 指定宽度,如果图片尺寸比指定宽度小,直接返回
    ///
    /// - returns: 等比例缩放后的图像
    func scaleImage(width: CGFloat) -> UIImage {

        // 1. 判断图像尺寸
        if size.width < width {
            return self
        }

        // 2. 计算比例
        let height = size.height * width / size.width
        let rect = CGRect(x: 0, y: 0, width: width, height: height)

        // 3. 核心绘图
        // 1> 开启上下文
        UIGraphicsBeginImageContext(rect.size)

        // 2> 绘图
        drawInRect(rect)

        // 3> 获得结果
        let result = UIGraphicsGetImageFromCurrentImageContext()

        // 4> 关闭上下文
        UIGraphicsEndImageContext()

        // 5> 返回结果
        return result
    }
}
  • 修改照片选拔调整器代理方法

/// 选中媒体代理方法
///
/// - parameter picker: 照片选择器
/// - parameter info:   信息字典 allowsEditing = true 适合选择头像
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {

    let image = info[UIImagePickerControllerOriginalImage] as! UIImage
    let scaleImage = image.scaleImage(300)

    if currentIndex < pictures.count {
        pictures[currentIndex] = scaleImage
    } else {
        pictures.append(scaleImage)
    }
    collectionView?.reloadData()

    dismissViewControllerAnimated(true, completion: nil)
}

整合照片接纳调节器

准备 文件

  • PhotoSelector 拖拽至项目
  • UIImage+Extension.swift 拖拽至项目

组成照片选用调节器

  • 概念调控器属性

/// 照片选择控制器
private lazy var pictureSelectorViewController = PictureSelectorViewController()
  • 图谋照片视图

/// 准备照片视图
private func preparePictureView() {
    // 添加视图
    view.addSubview(pictureSelectorViewController.view)

    // 自动布局
    pictureSelectorViewController.view.snp_makeConstraints { (make) -> Void in
        make.bottom.equalTo(view.snp_bottom)
        make.left.equalTo(view.snp_left)
        make.right.equalTo(view.snp_right)
        make.height.equalTo(view.snp_height).multipliedBy(0.6)
    }
}

运作测量检验,发掘选中照片结束后,提示错误:

Presenting view controllers on detached view controllers is discouraged

  • 加多子调整器

// 添加子控制器
addChildViewController(pictureSelectorViewController)
  • 修改照片选择视图档期的顺序

// 添加视图
view.insertSubview(pictureSelectorViewController.view, belowSubview: toolbar)

运转会开采照片选拔视图跑到了 textView 和 toolBar 的末端

重新建设构造控件布局

  • 修改照片选用视图的惊人

make.height.equalTo(0)
  • 在增选照片监听方法中重新建设构造控件索引

// 选择照片
@objc private func selectPhoto() {

    if pictureSelectorViewController.view.bounds.height == 0 {
        // 修改布局高度
        pictureSelectorViewController.view.snp_remakeConstraints { (make) -> Void in
            make.bottom.equalTo(view.snp_bottom)
            make.left.equalTo(view.snp_left)
            make.right.equalTo(view.snp_right)
            make.height.equalTo(view.snp_height).multipliedBy(0.6)
        }
        // 修改文本视图的底部约束
        textView.snp_remakeConstraints { (make) -> Void in
            make.top.equalTo(view.snp_top)
            make.left.equalTo(view.snp_left)
            make.right.equalTo(view.snp_right)
            make.bottom.equalTo(pictureSelectorViewController.view.snp_top)
        }

        UIView.animateWithDuration(0.25, animations: { () -> Void in
            self.view.layoutIfNeeded()
        })
    }
}
  • 关闭键盘

textView.resignFirstResponder()

运转测验

  • 调整 viewDidAppear 要是已经显得照片选取视图,则不再激活键盘

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    if pictureSelectorViewController.imageList.count == 0 {
        textView.becomeFirstResponder()
    }
}

颁发乐乎

表露文字新浪

接口定义

  • 文书档案地址

    • http://open.weibo.com/wiki/2/statuses/update
  • 接口地址

    • https://api.weibo.com/2/statuses/update.json
  • HTTP 央浼格局

    • POST
  • 伸手参数

参数 说明
access_token 采用OAuth授权方式为必填参数,其他授权方式不需要此参数,OAuth授权后获得
status 要发布的微博文本内容,必须做URLencode,内容不超过140个汉字

三回九转五次发布的新浪无法重复

  • HMNetworkTools 中添加 update 方法

/// 发布文字微博
func update(accessToken: String, text: String, finished: HMRequestCallBack){
    // 请求地址
    let urlString = "https://api.weibo.com/2/statuses/update.json"
    // 请求参数
    let params = [
        "access_token": accessToken,
        "status": text
    ]
    request(.POST, url: urlString, params: params, finished: finished)
}
  • HMComposeViewController 中调用

/// 发送文字微博
private func update(){
    HMNetworkTools.shareTools.update(HMUserAccountViewModel.sharedUserAccount.accessToken!, text: textView.emoticonText) { (result, error) -> () in
        if error != nil {
            print(error)
            SVProgressHUD.showErrorWithStatus("发表失败")
            return
        }
        print(result)
        SVProgressHUD.showSuccessWithStatus("发表成功")
    }
}

发布图片网易

接口定义

文书档案地址

http://open.weibo.com/wiki/2/statuses/upload

接口地址

https://upload.api.weibo.com/2/statuses/upload.json

HTTP 供给格局

  • POST

央浼参数

参数 说明
access_token 采用OAuth授权方式为必填参数,其他授权方式不需要此参数,OAuth授权后获得
status 要发布的微博文本内容,必须做URLencode,内容不超过140个汉字
pic 要上传的图片,仅支持JPEG、GIF、PNG格式,图片大小小于5M

呼吁必需用POST情势提交,何况注意运用multipart/form-data编码方式

代码落成

  • HMNetworkTools 中增加上传图片的不二等秘书技

func upload(accessToken: String, text: String, image: UIImage, finished: HMRequestCallBack){
    // url
    let url = "https://upload.api.weibo.com/2/statuses/upload.json"

    let params = [
        "access_token": accessToken,
        "status": text
    ]

    POST(url, parameters: params, constructingBodyWithBlock: { (formData) -> Void in
        let data = UIImagePNGRepresentation(image)!
        /**
            1. data: 二进制数据
            2. name: 服务器定义的字段名称
            3. fileName: 保存在服务器的文件名,通常可以乱写,服务器自己会做处理
            4. mimeType: 告诉服务器文件类型
                - 大类型 / 小类型
                    image/jepg, image/png
                    text/plain, text/html
                - 如果不想告诉服务器准确类型:
                    application/octet-stream

        */
        formData.appendPartWithFileData(data, name: "pic", fileName: "xxaaa", mimeType: "image/jpeg")
        }, success: { (_, response) -> Void in
            guard let dict = response as? [String: AnyObject] else {
                // 如果不是字典,返回错误
                let error = NSError(domain: "com.itheima.error", code: -1001, userInfo: ["message": "The response data type isn't a [String: AnyObject]"])
                finished(result: nil, error: error)
                return
            }
            finished(result: dict, error: nil)
        }) { (_, error) -> Void in
            finished(result: nil, error: error)
    }
}

表情键盘

在骨子里支出中对于比较独立的模块,能够平昔新建二个种类,在新类型上演习,测量试验,等待模块开垦达成之后再移植到品种中,方便项目标测验

贯彻效果与利益

4858.com 16

表情键盘效果图.png.jpeg

达成思路

  1. 从最轻便易行的 View 起首做起
  2. 底层切换表情类型的 View 能够动用 UIStackView 来实现
  3. 表情显得的 View 可以应用 UICollectionView 实现
  4. 每一页表情对应多个 Cell 来表示
  5. 种种表情对应 UICollectionView 中的一组

自定义 HMEmoticonKeyboard

  • 自定义 HMEmoticonKeyboard 继承于 UIView

class HMEmoticonKeyboard: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    private func setupUI(){
        // 设置背景颜色
        backgroundColor = UIColor(patternImage: UIImage(named: "emoticon_keyboard_background")!)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
  • HMComposeViewController 中加多切换键盘的章程 switchKeyboard

/// 切换键盘
private func switchKeyboard(){
}
  • 在点击 HMComposeToolBar 上的神采开关的时候调用方法

// MARK: - HMComposeToolBarDelegate
func composeToolBarButtonDidSelected(type: ComposeToolBarButtonType) {
    switch type {
    case ...
    case .Emoticon:
        switchKeyboard()
    }
}
  • 懒加载键盘

/// 键盘
private lazy var emoticonKeyboard: HMEmoticonKeyboard = {
    let keyboard = HMEmoticonKeyboard()
    keyboard.size = CGSizeMake(SCREENW, 216)
    return keyboard
}()

4858.com ,表情类型切换视图

  • 自定义 HMEmoticonToolBar

class HMEmoticonToolBar: UIStackView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        // 设置布局方向
        axis = UILayoutConstraintAxis.Horizontal
        // 设置子控件的分布方式 -> 填充,大小相等
        distribution = UIStackViewDistribution.FillEqually

        setupUI()
    }

    private func setupUI(){
        // 添加 4 个按钮
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
  • 提供增多 3 个按键的点子

private func addChildItem(title: String, bgImageName: String) {
    let button = UIButton()

    // 设置文字以及字体大小
    button.titleLabel?.font = UIFont.systemFontOfSize(14)
    button.setTitle(title, forState: UIControlState.Normal)

    // 设置不同状态的背景图片
    button.setBackgroundImage(UIImage(named: "\(bgImageName)_normal"), forState: UIControlState.Normal)
    button.setBackgroundImage(UIImage(named: "\(bgImageName)_selected"), forState: UIControlState.Selected)

    // 设置不同状态的文字颜色
    button.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
    button.setTitleColor(UIColor.grayColor(), forState: UIControlState.Selected)

    addArrangedSubview(button)
}
  • setupUI 中增多按键

private func setupUI(){
    // 添加 3 个按钮
    addChildItem("默认", bgImageName: "compose_emotion_table_right")
    addChildItem("Emoji", bgImageName: "compose_emotion_table_mid")
    addChildItem("浪小花", bgImageName: "compose_emotion_table_right")
}
  • HMEmoiticonKeyboard 中添加 HMEmoticonToolBar

// 懒加载控件
/// 底部切换表情类型的toolBar
private lazy var emoticonToolBar: HMEmoticonToolBar = HMEmoticonToolBar(frame: CGRectZero)
  • setupUI 方法中增多控件以及约束

// 添加子控件
addSubview(emoticonToolBar)

// 添加约束
emoticonToolBar.snp_makeConstraints { (make) -> Void in
    make.bottom.equalTo(self.snp_bottom)
    make.leading.equalTo(self.snp_leading)
    make.right.equalTo(self.snp_right)
    make.height.equalTo(37)
}

运营测量试验:开关背景图片未有拉伸情势相当

  • 转移拉伸方式:点击Assets.xcassets –> 选中对应的背景图片 –>
    查看侧面属性面板 –> 在 Slicing 区设置 Slices
    Horizontal,设置 centerStretches

    • 多少情状下 Xcode 会出 Bug,须求设置 Slices
      Horizontal And Vertical
  • 监听子按键点击

private func addChildItem(title: String, bgImageName: String) {
    let button = UIButton()
    // 添加点击事件
    button.addTarget(self, action: "childButtonClick:", forControlEvents: UIControlEvents.TouchUpInside)
    ...
}
  • 兑现响应措施

/// 子控件点击
///
/// - parameter button: 当前点击的 button
@objc private func childButtonClick(button: UIButton){
    // 按钮点击方法
}
  • 落到实处选中二个按键的时候撤销选中在此之前的按键

    • 记录当前相中的按键
    • 当点击下三个开关的时候撤废选中记录的按键,选中当前开关
    • 双重记录当前入选的开关
  • 定义 currentSelectedButton 属性记录当前选中的按键

/// 当前选中的按钮
var currentSelectedButton: UIButton?
  • childButtonClick 完毕开关点击逻辑

/// 子按钮点击
///
/// - parameter button: 当前点击的 button
@objc private func childButtonClick(button: UIButton){

    // 如果当前选中的 button 与即将要选中的button相同,则直接返回
    if button == currentSelectedButton {
        return
    }
    // 取消选中之前的
    currentSelectedButton?.selected = false
    // 选中现在点击的
    button.selected = true
    // 再次记录现在选的按钮
    currentSelectedButton = button
}

运维测验

  • 开关点击的时候须要让 HMEmoticonKeyboard 知道哪三个按键点击了
    • 给按键增添tag
    • 增加协商,在开关点击的时候调用公约议程

  • 更新 setupUI 方法中调用情势

private func setupUI(){
    // 添加 3 个按钮
    addChildItem("默认", bgImageName: "compose_emotion_table_left", index: 0)
    addChildItem("Emoji", bgImageName: "compose_emotion_table_mid", index: 1)
    addChildItem("浪小花", bgImageName: "compose_emotion_table_right", index: 2)
}
  • 概念左券

protocol HMEmoticonToolBarDelegate: NSObjectProtocol {
    func emoticonToolBarButtonDidSelected(index: Int)
}
  • 拉长代理属性

/// 代理
weak var delegate: HMEmoticonToolBarDelegate?
  • 在开关点击的时候调用代理身上的艺术

/// 子按钮点击
///
/// - parameter button: 当前点击的 button
@objc private func childButtonClick(button: UIButton){
    ...
    // 调用代理方法
    delegate?.emoticonToolBarButtonDidSelected(button.tag)
}
  • HMEmoticonKeyboard 继承 HMEmoticonToolBarDelegate 协议

class HMEmoticonKeyboard: UIView, HMEmoticonToolBarDelegate {
    ...
}
  • HMEmoticonKeyboard 中设置 HMEmoticonToolBar 的代办为团结

/// 底部切换表情类型的toolBar
private lazy var emoticonToolBar: HMEmoticonToolBar = {
    let toolBar = HMEmoticonToolBar(frame: CGRectZero)
    toolBar.delegate = self
    return toolBar
}()
  • 兑当代理方法

// MARK: - HMEmoticonToolBarDelegate
func emoticonToolBarButtonDidSelected(index: Int) {
    print(index)
}

运转测量试验

表情显得视图

  • HMEmoticonKeyboard 中添加 UICollectionView

/// 懒加载控件
/// 显示表情的视图
private lazy var emoticonCollectionView: UICollectionView = {
    let collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: UICollectionViewFlowLayout())
    collectionView.backgroundColor = RandomColor()
    return collectionView
}()
  • 增多控件与约束

// 添加子控件
addSubview(emoticonCollectionView)
// 添加约束
emoticonCollectionView.snp_makeConstraints { (make) -> Void in
    make.width.equalTo(self.snp_width)
    make.top.equalTo(self.snp_top)
    make.bottom.equalTo(self.emoticonToolBar.snp_top)
    make.leading.equalTo(self)
}

运营测验

  • 设置 emoticonCollectionView 的数据源以及登记 cell

/// 显示表情的视图
private lazy var emoticonCollectionView: UICollectionView = {
    let collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: UICollectionViewFlowLayout())
    collectionView.backgroundColor = RandomColor()
    // 设置数据源
    collectionView.dataSource = self
    // 注册 cell
    collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: HMEmoticonKeyboardCellId)
    return collectionView
}()
  • 继续合同

class HMEmoticonKeyboard: UIView, HMEmoticonToolBarDelegate, UICollectionViewDataSource {
    ...
}
  • 落实协议格局

extension HMEmoticonKeyboard {

    /// 返回表情一共有多少页
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // 为了测试,先默认返回10个
        return 10
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(HMEmoticonKeyboardCellId, forIndexPath: indexPath)
        // 测试返回随机颜色
        cell.backgroundColor = RandomColor()
        return cell
    }
}

运营测验:

  • 调动每多少个 cell 的大小
    • 因为各个 cell 的分寸与 collectionView 一样大
    • 而调治完 collectionView 大小要调用的方法便是 layoutSubviews
    • 所以在 layoutSubviews 调度每一个 cell 的轻重缓急

override func layoutSubviews() {
    super.layoutSubviews()

    // 设置每一个 cell 的大小
    let layout = emoticonCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
    layout.itemSize = emoticonCollectionView.size
}

运作测量试验:每一行之间有距离,并且滚动方向不对

  • 在开头化 emoticonCollectionView 的时候设置滚动方向以及 cell 间距
    UICollectionViewFlowLayout 身上的性质)

/// 显示表情的视图
private lazy var emoticonCollectionView: UICollectionView = {
    let layout = UICollectionViewFlowLayout()
    // 设置滚动方向:水平滚动
    layout.scrollDirection = UICollectionViewScrollDirection.Horizontal
    // 设置每一个 cell 之间的间距
    layout.minimumLineSpacing = 0

    let collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: layout)
    collectionView.backgroundColor = RandomColor()
    // 设置数据源
    collectionView.dataSource = self
    // 注册 cell
    collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: HMEmoticonKeyboardCellId)
    return collectionView
}()
  • 拉开分页 & 隐敝水平滚动条 & 关闭弹簧效果 (UIScrollView 身上的性质)

// 开启分页 & 隐藏水平滚动条
collectionView.pagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
// 关闭弹簧效果
collectionView.bounces = false
  • 自定义 HMEmoticonPageCell 为表情键盘的 Cell

class HMEmoticonPageCell: UICollectionViewCell {

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupUI(){
        backgroundColor = RandomColor()
    }
}
  • 轮换注册的 cell

// 注册 cell
collectionView.registerClass(HMEmoticonPageCell.self, forCellWithReuseIdentifier: HMEmoticonKeyboardCellId)
  • 为了测量检验效果,在 HMEmoticonPageCell中增添一个测量检验的 label

class HMEmoticonPageCell: UICollectionViewCell {

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    private func setupUI(){

        contentView.addSubview(label)

        label.snp_makeConstraints { (make) -> Void in
            make.center.equalTo(contentView.snp_center)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    /// 测试用的 label
    private lazy var label: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFontOfSize(35)
        return label
    }()
}
  • 提供 indexPath: NSIndexPath 属性,展现当前滚动到哪个地方

var indexPath: NSIndexPath? {
    didSet{
        label.text = "第\(indexPath!.section)组,第\(indexPath!.item)页"
    }
}
  • 在重回 cell 的时候设置 indexPath

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(HMEmoticonKeyboardCellId, forIndexPath: indexPath) as! HMEmoticonPageCell
    // 测试返回随机颜色
    cell.backgroundColor = RandomColor()
    cell.indexPath = indexPath
    return cell
}

运转测量试验

读取表情数据

  • 在iTunesStore中下载最新版本的和讯新浪安装包,获取素材文件
  • 扶助iOS6.0的花色的材质是足以平昔拿走的可是只要不扶助iOS6.0的设置是无力回天获得素材的,提出保留一些有个别App的材质,超过四分之二是向来不版权的

两种文件夹的分别

  • 土褐文件夹: 编写翻译后,财富文件在 mainBundle
    中,源代码程序须求通过这种形式拖拽增多, 功用高
  • 深褐文件夹:编写翻译后,财富文件在 mainBundle
    中的对应文件夹中,游戏文件的资料一般通过这种方法拖拽增加,用于换肤应用,游戏场景,
    分化路子下的同样文件名

*松赫色 Bundle:编写翻译后,财富文件在 mainBundle
中如故以包的花样存在,能够路线方式拜见,拖拽文件更简短,主要用以第三方框架包装资源质感

  • 新键 HMEmoticonManager
    类,里面加载表情数据,对外提供表情数据,和一部遍及局音信

class HMEmoticonManager: NSObject {
static let shareEmoticonManager: EmoticonManager = EmoticonManager()

    lazy var packages: [EmoticonPackages] = [EmoticonPackages]()

    private override init() {
        super.init()
        loadEmoticons()
    }

    func loadEmoticons() {
        let path = NSBundle.mainBundle().pathForResource("emoticons.plist", ofType: nil, inDirectory: "Emoticons.bundle")!
        let dict = NSDictionary(contentsOfFile: path) as! [String : AnyObject]

        let array = dict["packages"] as! [[String : AnyObject]]

        for item in array {
            //获取id
            let id = item["id"] as! String
            loadPackages(id)
        }
    }

    private func loadPackages(id: String) {
        //通过id获取 分组名称
        let path = NSBundle.mainBundle().pathForResource("info.plist", ofType: nil, inDirectory: "Emoticons.bundle/" + id)!
        //通过分组名称加载分组中的 info.plist 文件
        let dict = NSDictionary(contentsOfFile: path)!
        let group_name_cn = dict["group_name_cn"] as! String
        //获取表情数据
        let array = dict["emoticons"] as! [[String : String]]

        let p = EmoticonPackages(id: id, title: group_name_cn,array: array)
        packages.append(p)
    }
}
  • 概念表情模型

class HMEmoticon: NSObject {

    /// 表情文字描述
    var chs: String?
    /// 表情图片名字 (仅对图片表情有效)
    var png: String?


    /// Emoji表情的 code
    var code: String?
    /// 是否是Emoji表情
    var isEmoji: Bool = false

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

    override func setValue(value: AnyObject?, forUndefinedKey key: String) {}
}
  • 与分界面临应必要提高收取三个package对应的模型

  • title toolBar中展现的title
  • sectionEmoticon toolBar每种按键对应的大数组

class EmoticonPackages: NSObject {
    var title: String?
    lazy var sectionEmoticon = [[Emoticon]]()
}
  • EmoticonPackages增添构造方法

init(id: String, title: String, array: [[String : String]]) {
        super.init()
        self.title = title

        //遍历数组 转换为模型 再将模型转换为

        var emoticonArray: [Emoticon] = [Emoticon]()
        for item in array {
            let e = Emoticon(id: id, dict: item)
            emoticonArray.append(e)
        }
    }
  • 拍卖数据, 将模型数组[HMEmoticon]品种管理为 [[HMEmoticon]]

private func sectionEmoticonArray(array: [Emoticon]) -> [[Emoticon]]{
        //获取表情数量 这些数组每页21个 能装多少组
        let pageCount = (array.count - 1 ) / 21 + 1

        var sectionEm = [[Emoticon]]()
        for i in 0..<pageCount {
            //每页截取从大数组中截取21个表情   不足21个的会造成数组索引越界
            let loc = i * SectionEmoticonCount
            var length = SectionEmoticonCount
            if loc + length > array.count {
                length = array.count - loc
            }
            let subArray = (array as NSArray).subarrayWithRange(NSRange(location: loc, length: length))
            sectionEm.append(subArray as! [Emoticon])
        }
        return sectionEm
    }
  • 返回 HMEmoticonKeyboardcollectionView 所急需的数量
    • 数据结构剖判如下

4858.com 17

表情数据结构分析.png

运营测验

底部 HMEmoticonToolBar 与 展现表情的 collectionView 联动

点击尾部表情类型开关,切换来相应表情

  • HMEmoticonToolBar 的象征办法中采纳 collectionView
    滚动到对应的组

// MARK: - HMEmoticonToolBarDelegate
func emoticonToolBarButtonDidSelected(index: Int) {
    print(index)
            let indexPath = NSIndexPath(forItem: 0, inSection: index)
            self.collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Left, animated: false)
}

运转测量检验

当滚动到某种表情页时,选中对应表情开关

兑现思路

  • 贯彻监听 collectionView 滚动的岗位
    • scrollView 的代理方法 scrollViewDidScroll
  • 获取到 collectionView 的中心
  • 固化滚动到相应cell的核心点
  • 剖断当前显示器中显得的五个cell 哪个人的frame包含了目的核心点
  • 循环当前显示器中显得的cell对应的item(最多八个)
  • 当cell的frame 包涵该了大旨点的时候 就更新页面音信

完结代码

 func scrollViewDidScroll(scrollView: UIScrollView) {
        //确定cell的中心点
        var center = collectionView.center
        center.x = center.x + collectionView.contentOffset.x
        let indexPaths = collectionView.indexPathsForVisibleItems()

        for indexPath in indexPaths {
            //最多两个  最少一个
            let cell = collectionView.cellForItemAtIndexPath(indexPath)!
            if cell.frame.contains(center) {
                toolBar.setBtnSelected(indexPath.section)
                updatePageControlData(indexPath)
            }
        }
    }

运维测验

  • HMEmoticonToolBar 中提示 selectButtonWithSection
    方法供选中按键方法

/// 通过 section 选中某一个按钮
func selectButtonWithSection(section: Int) {
    // 通过 section 获取到对应的 button,让其选中
    let button = viewWithTag(section)! as! UIButton
    childButtonClick(button)
}
  • HMEmoticonKeyboardscrollViewDidScroll 方法中调用此格局

func scrollViewDidScroll(scrollView: UIScrollView) {
    ...
    emoticonToolBar.selectButtonWithSection(section)
}

运维测量试验:崩溃 Could not cast value of type ‘WeiBo.HMEmoticonToolBar’
(0x10bad2dc0) to ‘UIButton’ (0x10dcc2320). 原因是如今 section 为
0,调用 viewWithTag 方法取到的是 toolBar
自个儿,强转出错,所以把每三个开关对应的枚举值给定叁个基数

  • 在调用 viewWithTag 方法的时候增加三个基数

/// 通过 section 选中某一个按钮
func selectButtonWithSection(section: Int) {
    // 通过 section 获取到对应的 button,让其选中
    let button = viewWithTag(section + 1000)! as! UIButton
    childButtonClick(button)
}

运作测量检验:在从第0组滑动过八分之四的时候,很便捷的切换成第1组表情去了,原因便是调用
childButtonClick 方法会实行代理方法,代理方法会回调滚动
collectionView,所以在那么些地方只须求切换 button 选中状态

  • 领到按键切换状态的办法 changeButtonState

/// 改变按钮状态,把当前选中的 button 取消选中,把传入的 button 设置选中
private func changeButtonState(button: UIButton){
    // 如果当前选中的 button 与即将要选中的button相同,则直接返回
    if button == currentSelectedButton {
        return
    }
    // 取消选中之前的
    currentSelectedButton?.selected = false
    // 选中现在点击的
    button.selected = true
    // 再次记录现在选的按钮
    currentSelectedButton = button
}
  • selectButtonWithSection 调用 changeButtonState 方法

/// 通过 section 选中某一个按钮
func selectButtonWithSection(section: Int) {
    // 通过 section 获取到对应的 button,让其选中
    let button = viewWithTag(section + 1000)! as! UIButton
    // 更改按钮选中状态
    changeButtonState(button)
}
  • 替换 childButtonClick 方法内完结

/// 子按钮点击
///
/// - parameter button: 当前点击的 button
@objc private func childButtonClick(button: UIButton){
    // 如果当前选中的 button 与即将要选中的button相同,则直接返回
    if button == currentSelectedButton {
        return
    }
    // 改变按钮状态
    changeButtonState(button)

    // 调用代理方法
    delegate?.emoticonToolBarButtonDidSelected(HMEmoticonType(rawValue: button.tag)!)
}

运行测量检验

表情显得

设置子控件

  • HMEmoticonPageCell 中加多 20 个开关表情按键

/// 添加表情按钮
private func addEmoticonButtons(){
    et leftMargin: CGFloat = 5
        let bottomMargin: CGFloat = 30
        let bW = (UIScreen.mainScreen().bounds.width - 2 * leftMargin) / CGFloat(EmoticonColCount)
        let bH = (bounds.height - bottomMargin) / CGFloat(EmoticonRowCount)
        for i in 0..<SectionEmoticonCount {
            let btn = EmoticonButton()
            btn.addTarget(self, action: "btnDidClick:", forControlEvents: .TouchUpInside)
            btn.titleLabel?.font = UIFont.systemFontOfSize(32)
            let row = i / EmoticonColCount
            let col = i % EmoticonColCount
            let x = leftMargin + CGFloat(col) * bW
            let y = bH * CGFloat(row)
            btn.frame = CGRect(x: x, y: y, width: bW, height: bH)
            contentView.addSubview(btn)
            buttonArray.append(btn)
        }
}
  • setupUI 方法中调用此措施

private func setupUI(){
    // 添加子控件
    addEmoticonButtons()
    ...
}


> 运行测试

### 显示图片表情数据

- 在 `HMEmoticonPageCell` 中提供 `emoticons` 属性,供外界设置表情数据

```swift
var emoticons: [HMEmoticon]?
  • HMEmoticonKeyboard 中的 collectionView 数据源方法里面给 cell
    设置数据

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(HMEmoticonKeyboardCellId, forIndexPath: indexPath) as! HMEmoticonPageCell
    cell.indexPath = indexPath
    // 设置表情数据
    cell.emoticons = HMEmoticonTools.allEmoticons()[indexPath.section][indexPath.row]
    return cell
}
  • emoticonsdidSet 方法中展现表情

/// 当前页显示的表情数据
var emoticons: [HMEmoticon]? {
    didSet{
        // 遍历当前设置的表情数据
        for (index,value) in emoticons!.enumerate() {
            let button = emoticonButtons[index]
            if !value.isEmoji {
                let image = UIImage(named: value.png!)
                button.setImage(image, forState: UIControlState.Normal)
            }
        }
    }
}

运作测验:表情未有显得出来,加载表情的图样地址不得法,因为表情图片是放在
Emoticons.bundle
中的,所以必要拼接前面包车型大巴路径,而那前边的路子正是神色所对应的
info.plist 文件所在的路线

  • HMEmoticon 中添加 path 属性

var png: String? {
        didSet {
            imagePath = Emoticon.bundlePath + "/Emoticons.bundle/" + (id ?? "") + "/\(png ?? "")"
        }
    }

    var imagePath: String?
  • 更新 HMEmoticonPageCellemoticonsdidSet 方法

/// 当前页显示的表情数据
var emoticons: [HMEmoticon]? {
    didSet{
        // 遍历当前设置的表情数据
        for (index,value) in emoticons!.enumerate() {
            let button = emoticonButtons[index]
            if !value.isEmoji {
                let image = UIImage(named: "\(value.path!)/\(value.png!)")
                button.setImage(image, forState: UIControlState.Normal)
            }
        }
    }
}

运营测量检验:图片表情显得出来了,不过 cell 复用
导致未有表情的页面也出示过表情,所以在遍历设置表情之后须求先将具备的
显示表情的button 隐藏掉

  • 先掩盖全体展现表情的 button,遍历多少个表情显得几个

/// 当前页显示的表情数据
var emoticons: [HMEmoticon]? {
    didSet{

        // 先隐藏所有的表情按钮
        for value in emoticonButtons {
            value.hidden = true
        }

        // 遍历当前设置的表情数据
        for (index,value) in emoticons!.enumerate() {
            let button = emoticonButtons[index]
            // 显示当前遍历到的表情按钮
            button.hidden = false
            if !value.isEmoji {
                let image = UIImage(named: "\(value.path!)/\(value.png!)")
                button.setImage(image, forState: UIControlState.Normal)
            }
        }
    }
}

展示 Emoji 表情数据

  1. 演练Emoji表情, 拖入 String+Emoji 分类到品种中,
  2. Emoji 表情其实正是字符串
  • 设置 Emoji 表情数据

    button.setTitle(em.codeStr(), forState: UIControlState.Normal)
}
  • 运营测量试验:Emoji 表情显得太小,调度 button 的文字大小就可以解决

/// 添加表情按钮
private func addEmoticonButtons(){
    for _ in 0..<HMEmoticonPageNum {
        let button = UIButton()
        button.titleLabel?.font = UIFont.systemFontOfSize(36)
        contentView.addSubview(button)
        emoticonButtons.append(button)
    }
}

运维测量试验

  • 更改 HMEmoticonKeyboard 中的 collectionView 的背景颜色为透明色

/// 显示表情的视图
private lazy var emoticonCollectionView: UICollectionView = {
    ...
    collectionView.backgroundColor = UIColor.clearColor()
    ...
    return collectionView
}()
  • 去掉 HMEmoticonPageCell 中显示 section 的 label

运作测量试验

  • 升高表情数据
  • 每页的末段叁个拉长壹个剔除开关
  • 每页不足18个表情须求补足空白表情
  • 空荡荡表情的最后叁个应当是去除表情

在HMEmoticonPackages升高数据

init(id: String, title: String, array: [[String : String]]) {
        super.init()
        self.title = title

        //遍历数组 转换为模型 再将模型转换为

        var emoticonArray: [Emoticon] = [Emoticon]()
        var index = 0
        for item in array {
            let e = Emoticon(id: id, dict: item)
            emoticonArray.append(e)
            index++
            if index == 20 {
                //每页的最后一个添加一个删除表情
                let delete = Emoticon(isDelete: true)
                emoticonArray.append(delete)
                index = 0
            }
        }


        //不足21个的补空白表情
        let delta = emoticonArray.count % 21
        if delta != 0 {
            for _ in delta..<20 {
                let empty = Emoticon(isEmpty: true)
                emoticonArray.append(empty)
            }
            emoticonArray.append(Emoticon(isDelete: true))
        }
        //将模型数组处理成 分组的模型数组
        //分组规则 21 个表情为一页
        self.sectionEmoticon = sectionEmoticonArray(emoticonArray)
    }
  • 在HMEmoticon模型中增添空表情和删除表情的构造方法和相应的符号

运营测量试验 化解页面复用的标题

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 美高梅手机版4858 版权所有