iOS Developer - CALayeriOS Developer

Core Graphics 教程:Lines、Rectangle

2016-05-05  本文已影响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

我们发现了以下几点:

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的线,有下面几种办法来解决:

打开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

原文地址
源码地址

上一篇下一篇

猜你喜欢

热点阅读