美文网首页iOS Developer - CALayeriOS Developer
Core Graphics 教程:Lines、Rectangle

Core Graphics 教程:Lines、Rectangle

作者: 董二千 | 来源:发表于2016-05-05 13:27 被阅读135次

    Core Graphics 是非常棒的iOSApI,我们可以用它来自定义一些很酷的UI,而不必依赖图片。
    但是对于大部分开发者而言,它是令人畏惧的。因为它的PAI很多,有很多的东西需要去理解。
    这篇文章会通过画一个tableview,来为我们一步一步的揭开Core Graphics的神秘面纱。
    看起来像这样

    CoreGraphics101.jpg

    在这一篇教程中我们会初步的使用Core Graphics。实现像,绘制一个矩形,绘制一个渐变,还有如何处理1像素的线的问题。
    在下一篇教程中我们将完成这个app的剩余部分,tableview的header,footer还有触摸事件。

    Getting Started

    新建一个项目选择Single View Application,输入CoolTable作为项目名称,勾选Use StoryboardsUse Automatic Reference Counting,创建项目。然后删除ViewController.hViewController.mUITableViewController来替代。

    project-selection-475x320.png project-settings-474x320.png

    创建一个新类继承UITableViewController命名为CoolTableViewController

    class_creation_dialog-475x320.png

    选中默认的the starting viewcontroller,并删除它。从object library里面拉一个导航栏出来,

    navigation-controller-480x293.png
    UITableViewController的class改为你自定义的class, identity_inspector.png

    删除导航栏bar的title,

    root-view-controller-e1360876556953.png

    最后为cell准备一个reuse identify,使用cell,

    attributes-inspector-425x320 (1).png

    运行app,

    BlankTableView.jpg

    这是一个空白的tableview,让我们添加一些数据。选中CoolTableViewController.m 文件,添加如下code

    @interface CoolTableViewController () 
    @property (copy) NSMutableArray *thingsToLearn;
    @property (copy) NSMutableArray *thingsLearned; 
    @end
    

    这两个数组里面的数据源是填充tableview的两个section的,注意这两个数组是在私有的interface 里面声明的因为它不需要让外界知道。
    继续加入下列code

    - (void)viewDidLoad{
     [super viewDidLoad]; 
    self.title = @"Core Graphics 101";   
    self.thingsToLearn = [@[@"Drawing Rects", @"Drawing Gradients", @"Drawing Arcs"] mutableCopy]; 
    self.thingsLearned = [@[@"Table Views", @"UIKit", @"Objective-C"] mutableCopy];
    } 
    #pragma mark - Table view data source 
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ 
    // Return the number of sections. 
        return 2;
    }
     - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 
        if (section == 0) { 
               return self.thingsToLearn.count;
            } 
            else
            { 
               return self.thingsLearned.count;
            }
    }
     - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 
          static NSString * CellIdentifier = @"Cell"; 
          UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; NSString entry;  
          if (indexPath.section == 0) { 
            entry = self.thingsToLearn[indexPath.row];
           } 
          else 
          { 
            entry = self.thingsLearned[indexPath.row]; 
          }
           cell.textLabel.text = entry;  
           return cell;
    } 
    -(NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 
          if (section == 0) { 
            return @"Things We'll Learn"; 
          } 
          else
          { 
            return @"Things Already Covered";
           }
    }
    

    现在继续运行

    TableViewPlain.jpg

    当你滑动的时候会发现第一个section header会黏在顶部

    TableViewPlainHeader.jpg
    因为你使用的是tableview的plain模式,你可以用grouped模式来避免这种情况的发生。 grouped_table-480x268.png

    Table View Style Analyzed

    我们会通过三个部分来绘制tableview:cell,header,footer。

    TableViewAnalyzed.jpg

    在这篇文章里我们先绘制cell,让我们仔细观察一下

    TableViewCellsZoomed.jpg

    我们发现了以下几点:

    • cell是渐变的从white到light gray
    • 每个cell都有白色的轮廓,除了最后一个只有一边有
    • 每个cell通过light gray颜色的线分割,除了最后一个
    • cell的边缘有锯齿状

    Hellow Core Graphics!

    我们的code要写在UIViewdrawRect方法里。创建一个view命名为CustomCellBackground,然后切换到CustomCellBackground.m添加code

    -(void)drawRect:(CGRect)rect {
        CGContextRef context = UIGraphicsGetCurrentContext();
        UIColor *redColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];
        CGContextSetFillColorWithColor(context, redColor.CGColor);
        CGContextFillRect(context, self.bounds);
    }
    

    在第一行我们调用UIGraphicsGetCurrentContext()方法获得一个Core Graphics Context
    在下面的方法中会用到它。

    我们可以把context看做是一个画布‘canvas’,我们可以在上面绘制。在这种情况下‘canvas’是view,还有其他的画布,例如offscreen buffer,它可以变成一个图片,在将来的某个时候。
    关于context的第一个有趣的东西是stateful,当处于stateful意味着我们可以改变一些东西,像填充颜色。这个填充的颜色将会被保留下来用作填充颜色,除非你在后面把它改为不同的值。

    在第三行使用了CGContextSetFillColorWithColor这个方法,来把填充色设置为red。你可以在任何时候使用这个方法来填充图形。

    你可能会注意到,你不能直接调用UIColor,你必须用CGColorRef这个类来替代,幸运的是它们之间的转化非常简单。

    最后你调用一个方法来填充矩形,你需要传入矩形的bounds。

    现在你已经有了一个red view,你将会把它设为cell的background view。
    CoolTableViewController.m的上面导入
    #import "CustomCellBackground.h",然后修改tableView:cellForRowAtIndexPath

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString * CellIdentifier = @"Cell";
        UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        NSString * entry;
        
        // START NEW
        if (![cell.backgroundView isKindOfClass:[CustomCellBackground class]]) {
            cell.backgroundView = [[CustomCellBackground alloc] init];
        }
        
        if (![cell.selectedBackgroundView isKindOfClass:[CustomCellBackground class]]) {
            cell.selectedBackgroundView = [[CustomCellBackground alloc] init];
        }
        // END NEW
        
        if (indexPath.section == 0) {
            entry = self.thingsToLearn[indexPath.row];
        } else {
            entry = self.thingsLearned[indexPath.row];
        }
        cell.textLabel.text = entry;
        
        cell.textLabel.backgroundColor = [UIColor clearColor]; // NEW
        
        return cell;
    }
    

    run

    HelloCoreGraphics.jpg

    Drawing Gradients

    现在我们将会在项目中绘制许多渐变,把你的渐变code放在helper类里面方便以后在不同的项目中使用。
    新建一个NSObject的子类,命名为Common删除Common.h里面的所有内容
    添加如下code

    #import <Foundation/Foundation.h>
    
    void drawLinerGradient(CGContextRef context,CGRect rect, CGColorRef startColor, CGColorRef endColor);
    

    你不是正真的创建了一个类,因为你不需要任何状态,只需要一个全局的方法。切换到Common.m添加code

    #import "Common.h"
    
    void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor)
    {
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGFloat locations[] = {0.0,1.0};
        NSArray *colors = @[(__bridge id)startColor,(__bridge id)endColor];
        
        CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
        // More coming... 
    }
    

    这里有很多的方法。
    第一件事,你需要有一个color space来绘制渐变color,通过color space你可以做很多事情。大部分时候你只需要用到RGB类型的color space,所以你只需要使用CGColorSpaceCreateDeviceRGB方法来获得你需要的引用(RGB)。
    设置一个数组,在渐变范围你每种颜色的位置。0意味着开始渐变,1意味着渐变结束。你只需要两个颜色,一个用来开始渐变,一个用来结束渐变,所以你只要传入0和1。
    注意,如果你想要的话你可以设置更多的渐变颜色,你要设置每种颜色开始渐变的位置。用这个方法可以实现很炫的效果哦。

    想了解更多关于bridge和memory management,请看这篇教程Automatic Reference Counting.

    这样你就用CGGradientCreateWithColors创建了一个渐变,传入了color space、color array、locations(颜色的位置)。
    现在你有了一个渐变引用,但是不是一个真正的渐变图像。现在把下面code添加到More coming的注释下面

        CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
        CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
        
        CGContextSaveGState(context);
        CGContextAddRect(context, rect);
        CGContextClip(context);
        CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
        CGContextRestoreGState(context);
        
        CGGradientRelease(gradient);
        CGColorSpaceRelease(colorSpace);
    

    第一件事是计算开始和结束的点,剩下的code是帮你在rectage里面绘制渐变。主要的方法是CGContextDrawLinearGradient。这个方法很奇怪,因为它用渐变填补了画布的整个区域,也就是说,它没办法填补某个区域。Clip是Core Graphics是一个很棒的特点,你可以用它来绘制任意的图形,你要做的仅仅是把图形添加到context里面。和一起不同的是,你只需要调用CGContextClip,这样所有的绘制内容就会限制在该区域。

    所以这里你添加了一个矩形到context里面,裁剪它,然后调用CGContextDrawLinearGradient传入你之前准备好的所有变量。

    CGContextSaveCGState/CGContextRestoreCGState这个方法做了什么呢?记住Core Graphics有一种状态机制。只要你设置了它的状态,它就会一直保持,直到你去改变它。这里就用到了这两个方法,保存你当前context的设置到stack中。将来你想要恢复state的时候,就从stack中pop出来。
    最后一件事,你需要释放memory,通过调用CGGradientRelease方法来释放CGGradientCreateWithColors方法创建的对象。

    回到CustomCellBackground.m,导入#import "Common.h",替代drawRect方法里的code

        CGContextRef context = UIGraphicsGetCurrentContext();
        
        UIColor * whiteColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
        UIColor * lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 blue:230.0/255.0 alpha:1.0];
        
        CGRect paperRect = self.bounds;
        
        drawLinearGradient(context, paperRect, whiteColor.CGColor, lightGrayColor.CGColor);
    
    CellsWithGradient.jpg

    Stroking Paths

    我们要在cell四周绘制一个白色的矩形,并在cell之间绘制灰色的分割线。我们已经填充了一个矩形,划线也是很简单的。修改CustomCellBackground.m的drawRect:方法

        CGContextRef context = UIGraphicsGetCurrentContext();
        
        UIColor * whiteColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
        UIColor * lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 blue:230.0/255.0 alpha:1.0];
        UIColor *redColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];
        
        CGRect paperRect = self.bounds;
        
        drawLinearGradient(context, paperRect, whiteColor.CGColor, lightGrayColor.CGColor);
        
        CGRect storeRect = CGRectInset(paperRect, 5.0, 5.0);
        CGContextSetStrokeColorWithColor(context, redColor.CGColor);
        CGContextSetLineWidth(context, 1.0);
        CGContextStrokeRect(context, storeRect);
    

    为了让这些改变容易看出来,我们在cell的中间画了一个红色的矩形。CGRectInset这个方法是返回一个矩形,该矩形的rect是原参数矩形的基础上,上下都减少了Y,左右都减了X。然后返回一个新的矩形给你。设置线宽为1point(在retain屏幕上是2pixels,非retain屏是1pixel),颜色为红色。调用CGContextStrokeRect方法来绘制矩形。

    FuzzyLines.jpg

    它看起来不错,但是仔细看会觉得有点模糊和怪异,如果放大了就能看清楚哪里不对劲。

    FuzzyLines2.jpg

    你希望画1point的线,但是你可以看到像素重合了,那怎么办呢?

    1 Point Lines and Pixel Boundaries

    这件事证明了,用Core Graphics描一个路径,描边是以路径为中间线。
    我们希望填充矩形的路径边缘,当我们沿着边缘画1pixel,一半的线(0.5pixel)在矩形里面,一半的线在矩形的外面。
    因为没有办法画0.5pixel的线,所以Core Graphics用锯齿来替代。
    但是我们不想要锯齿,我们需要的是1pixel的线,有下面几种办法来解决:

    • 裁剪掉不想要的像素
    • 使锯齿无效,修改矩形的边缘,确保达到你想要的效果
    • 修改绘制路径,把0.5pixel的影响考虑进去

    打开Common.h文件,添加下列方法 CGRect rectFor1PxStroke(CGRect rect);
    Common.m里面

    CGRect rectFor1PxStroke(CGRect rect)
    {
        return CGRectMake(rect.origin.x + 0.5, rect.origin.y + 0.5, rect.size.width - 1, rect.size.height - 1);
    }
    

    路径(是描边的中线)向上移了1pixel,向右移了1pixel

    回到CustomCellBackground.m

    CGRect strokeRect = rectFor1PxStroke(CGRectInset(paperRect, 5.0, 5.0));
    

    替代以前的code,run

    1PxSharpLines.jpg

    现在我们加上正确的颜色和位置

        CGContextRef context = UIGraphicsGetCurrentContext();
        
        UIColor * whiteColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
        UIColor * lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 blue:230.0/255.0 alpha:1.0];
    //    UIColor *redColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];
        
        CGRect paperRect = self.bounds;
        
        drawLinearGradient(context, paperRect, whiteColor.CGColor, lightGrayColor.CGColor);
        
    //    CGRect storeRect = CGRectInset(paperRect, 5.0, 5.0);
    //    CGRect storeRect = rectFor1PxStroke(CGRectInset(paperRect, 5.0, 5.0));
        CGRect stroRect = paperRect;
        stroRect.size.height -= 1;
        stroRect = rectFor1PxStroke(stroRect);
        CGContextSetStrokeColorWithColor(context, whiteColor.CGColor);
        
        CGContextSetLineWidth(context, 1.0);
        CGContextStrokeRect(context, stroRect);
    

    这里我们减少一个高度来做分割,并把描边换成白色,这样在cell之间就有一个细微的白色,run

    CustomCellsWhiteBorder.jpg

    Drawing Lines

    因为你已经在项目里面花了不少的线,我们要把它抽出来。添加到Common.h类里面

    void draw1PxStroke(CGContextRef context, CGPoint startPoint, CGPoint endPoint, CGColorRef color);
    

    Common.m里面

    void draw1PxStroke(CGContextRef context, CGPoint startPoint, CGPoint endPoint,CGColorRef color)
    {
        CGContextSaveGState(context);
        CGContextSetLineCap(context, kCGLineCapSquare);
        CGContextSetStrokeColorWithColor(context, color);
        CGContextSetLineWidth(context, 1.0);
        CGContextMoveToPoint(context, startPoint.x + 0.5, startPoint.y + 0.5);
        CGContextAddLineToPoint(context, endPoint.x + 0.5, endPoint.y + 0.5);
        CGContextStrokePath(context);
        CGContextRestoreGState(context);
    }
    

    在方法的开始,我们使用了save/restore,这样我们在画线的时候就不会对画布周围造成影响。
    我们的线以cap的模式结束。这样可以在一定程度上达到抗锯齿的效果。
    把点移动到A,画A到B的线。
    改变CustomCellBackground.m里的code

        CGContextRef context = UIGraphicsGetCurrentContext();
        
        UIColor * whiteColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
        UIColor * lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 blue:230.0/255.0 alpha:1.0];
        UIColor * separatorColor = [UIColor colorWithRed:208.0/255.0 green:208.0/255.0 blue:208.0/255.0 alpha:1.0];
    //    UIColor *redColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];
        
        CGRect paperRect = self.bounds;
        
        drawLinearGradient(context, paperRect, whiteColor.CGColor, lightGrayColor.CGColor);
        
    //    CGRect storeRect = CGRectInset(paperRect, 5.0, 5.0);
    //    CGRect storeRect = rectFor1PxStroke(CGRectInset(paperRect, 5.0, 5.0));
        CGRect stroRect = paperRect;
        stroRect.size.height -= 1;
        stroRect = rectFor1PxStroke(stroRect);
        CGContextSetStrokeColorWithColor(context, whiteColor.CGColor);
        
        CGContextSetLineWidth(context, 1.0);
        CGContextStrokeRect(context, stroRect);
        
        
        
        CGPoint startPoint = CGPointMake(paperRect.origin.x, paperRect.origin.y + paperRect.size.height - 1);
        CGPoint endPoint = CGPointMake(paperRect.origin.x + paperRect.size.width - 1, paperRect.origin.y + paperRect.size.height - 1);
        draw1PxStroke(context, startPoint, endPoint, separatorColor.CGColor);
    

    Run

    CustomCellsWithSeparator.jpg

    原文地址
    源码地址

    相关文章

      网友评论

      本文标题:Core Graphics 教程:Lines、Rectangle

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