Core Graphics 教程:Lines、Rectangle、Gradient

Core Graphics 是非常棒的iOSApI,我們可以用它來自定義一些很酷的UI,而不必依賴圖片。
但是對于大部分開發(fā)者而言,它是令人畏懼的。因為它的PAI很多,有很多的東西需要去理解。
這篇文章會通過畫一個tableview,來為我們一步一步的揭開Core Graphics的神秘面紗。
看起來像這樣

CoreGraphics101.jpg

在這一篇教程中我們會初步的使用Core Graphics。實現(xiàn)像,繪制一個矩形,繪制一個漸變,還有如何處理1像素的線的問題。
在下一篇教程中我們將完成這個app的剩余部分,tableview的header,footer還有觸摸事件。

Getting Started

新建一個項目選擇Single View Application,輸入CoolTable作為項目名稱,勾選Use Storyboards、Use Automatic Reference Counting,創(chuàng)建項目。然后刪除ViewController.hViewController.mUITableViewController來替代。

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

創(chuàng)建一個新類繼承UITableViewController命名為CoolTableViewController

class_creation_dialog-475x320.png

選中默認的the starting viewcontroller,并刪除它。從object library里面拉一個導(dǎo)航欄出來,

navigation-controller-480x293.png

UITableViewController的class改為你自定義的class,

identity_inspector.png

刪除導(dǎo)航欄bar的title,

root-view-controller-e1360876556953.png

最后為cell準備一個reuse identify,使用cell,

attributes-inspector-425x320 (1).png

運行app,

BlankTableView.jpg

這是一個空白的tableview,讓我們添加一些數(shù)據(jù)。選中CoolTableViewController.m 文件,添加如下code

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

這兩個數(shù)組里面的數(shù)據(jù)源是填充tableview的兩個section的,注意這兩個數(shù)組是在私有的interface 里面聲明的因為它不需要讓外界知道。
繼續(xù)加入下列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";
       }
}

現(xiàn)在繼續(xù)運行

TableViewPlain.jpg

當你滑動的時候會發(fā)現(xiàn)第一個section header會黏在頂部

TableViewPlainHeader.jpg

因為你使用的是tableview的plain模式,你可以用grouped模式來避免這種情況的發(fā)生。

grouped_table-480x268.png

Table View Style Analyzed

我們會通過三個部分來繪制tableview:cell,header,footer。

TableViewAnalyzed.jpg

在這篇文章里我們先繪制cell,讓我們仔細觀察一下

TableViewCellsZoomed.jpg

我們發(fā)現(xiàn)了以下幾點:

  • cell是漸變的從white到light gray
  • 每個cell都有白色的輪廓,除了最后一個只有一邊有
  • 每個cell通過light gray顏色的線分割,除了最后一個
  • cell的邊緣有鋸齒狀

Hellow Core Graphics!

我們的code要寫在UIViewdrawRect方法里。創(chuàng)建一個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);
}

在第一行我們調(diào)用UIGraphicsGetCurrentContext()方法獲得一個Core Graphics Context
在下面的方法中會用到它。

我們可以把context看做是一個畫布‘canvas’,我們可以在上面繪制。在這種情況下‘canvas’是view,還有其他的畫布,例如offscreen buffer,它可以變成一個圖片,在將來的某個時候。
關(guān)于context的第一個有趣的東西是stateful,當處于stateful意味著我們可以改變一些東西,像填充顏色。這個填充的顏色將會被保留下來用作填充顏色,除非你在后面把它改為不同的值。

在第三行使用了CGContextSetFillColorWithColor這個方法,來把填充色設(shè)置為red。你可以在任何時候使用這個方法來填充圖形。

你可能會注意到,你不能直接調(diào)用UIColor,你必須用CGColorRef這個類來替代,幸運的是它們之間的轉(zhuǎn)化非常簡單。

最后你調(diào)用一個方法來填充矩形,你需要傳入矩形的bounds。

現(xiàn)在你已經(jīng)有了一個red view,你將會把它設(shè)為cell的background view。
CoolTableViewController.m的上面導(dǎo)入
#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

現(xiàn)在我們將會在項目中繪制許多漸變,把你的漸變code放在helper類里面方便以后在不同的項目中使用。
新建一個NSObject的子類,命名為Common刪除Common.h里面的所有內(nèi)容
添加如下code

#import <Foundation/Foundation.h>

void drawLinerGradient(CGContextRef context,CGRect rect, CGColorRef startColor, CGColorRef endColor);

你不是正真的創(chuàng)建了一個類,因為你不需要任何狀態(tài),只需要一個全局的方法。切換到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)。
設(shè)置一個數(shù)組,在漸變范圍你每種顏色的位置。0意味著開始漸變,1意味著漸變結(jié)束。你只需要兩個顏色,一個用來開始漸變,一個用來結(jié)束漸變,所以你只要傳入0和1。
注意,如果你想要的話你可以設(shè)置更多的漸變顏色,你要設(shè)置每種顏色開始漸變的位置。用這個方法可以實現(xiàn)很炫的效果哦。

想了解更多關(guān)于bridge和memory management,請看這篇教程Automatic Reference Counting.

這樣你就用CGGradientCreateWithColors創(chuàng)建了一個漸變,傳入了color space、color array、locations(顏色的位置)。
現(xiàn)在你有了一個漸變引用,但是不是一個真正的漸變圖像?,F(xiàn)在把下面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);

第一件事是計算開始和結(jié)束的點,剩下的code是幫你在rectage里面繪制漸變。主要的方法是CGContextDrawLinearGradient。這個方法很奇怪,因為它用漸變填補了畫布的整個區(qū)域,也就是說,它沒辦法填補某個區(qū)域。Clip是Core Graphics是一個很棒的特點,你可以用它來繪制任意的圖形,你要做的僅僅是把圖形添加到context里面。和一起不同的是,你只需要調(diào)用CGContextClip,這樣所有的繪制內(nèi)容就會限制在該區(qū)域。

所以這里你添加了一個矩形到context里面,裁剪它,然后調(diào)用CGContextDrawLinearGradient傳入你之前準備好的所有變量。

CGContextSaveCGState/CGContextRestoreCGState這個方法做了什么呢?記住Core Graphics有一種狀態(tài)機制。只要你設(shè)置了它的狀態(tài),它就會一直保持,直到你去改變它。這里就用到了這兩個方法,保存你當前context的設(shè)置到stack中。將來你想要恢復(fù)state的時候,就從stack中pop出來。
最后一件事,你需要釋放memory,通過調(diào)用CGGradientRelease方法來釋放CGGradientCreateWithColors方法創(chuàng)建的對象。

回到CustomCellBackground.m,導(dǎo)入#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之間繪制灰色的分割線。我們已經(jīng)填充了一個矩形,劃線也是很簡單的。修改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);

為了讓這些改變?nèi)菀卓闯鰜?,我們在cell的中間畫了一個紅色的矩形。CGRectInset這個方法是返回一個矩形,該矩形的rect是原參數(shù)矩形的基礎(chǔ)上,上下都減少了Y,左右都減了X。然后返回一個新的矩形給你。設(shè)置線寬為1point(在retain屏幕上是2pixels,非retain屏是1pixel),顏色為紅色。調(diào)用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

現(xiàn)在我們加上正確的顏色和位置

    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

因為你已經(jīng)在項目里面花了不少的線,我們要把它抽出來。添加到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的模式結(jié)束。這樣可以在一定程度上達到抗鋸齒的效果。
把點移動到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

原文地址
源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容