2014-01-23 137 views
46

我在運行時在代碼中添加了UIView在代碼生成UIView上繪製UIBezierPath

我想繪製一個UIBezierPath它,但這是否意味着我必須覆蓋UIView的drawRect

或者還有另一種方法在定製的UIView上繪製它?

這裏是產生UIView代碼:

UIView* shapeView = [[UIView alloc]initWithFrame:CGRectMake(xOrigin,yOrigin+(i*MENU_BLOCK_FRAME_HEIGHT), self.shapeScroll.frame.size.width, MENU_BLOCK_FRAME_HEIGHT)]; 
shapeView.clipsToBounds = YES; 

這裏是創建和返回功能的UIBezierPath

- (UIBezierPath*)createPath 
{ 
    UIBezierPath* path = [[UIBezierPath alloc]init]; 
    [path moveToPoint:CGPointMake(100.0, 50.0)]; 
    [path addLineToPoint:CGPointMake(200.0,50.0)]; 
    [path addLineToPoint:CGPointMake(200.0, 200.0)]; 
    [path addLineToPoint:CGPointMake(100.0, 200.0)]; 
    [path closePath]; 
    return path; 
} 

回答

161

這是不久前我甚至不知道如何發音貝塞爾,更不用說知道如何使用Bézier路徑來製作自定義形狀。以下是我所學到的。事實證明,他們並不像起初看起來那樣可怕。

如何在自定義視圖

繪製Bézier path這些是主要的步驟:

  1. 設計你想要的形狀的輪廓。
  2. 將輪廓路徑劃分爲直線,圓弧和曲線的線段。
  3. 以編程方式構建該路徑。
  4. drawRect或使用CAShapeLayer繪製路徑。

設計形狀輪廓

你可以做任何事情,但作爲一個例子,我選擇了下面的形狀。它可以是鍵盤上的彈出鍵。

enter image description here

分成段的路徑

回頭看你的外形設計,並把它分解成線(用於直線),弧線的簡單元素(圓和圓角),和曲線(對於其他任何東西)。

這裏是我們的榜樣設計會是什麼樣子:

enter image description here

  • 黑色是線段
  • 淡藍色的弧段
  • 紅色的曲線
  • 橙色小點是控制點的曲線
  • 綠點是路徑之間的點s egments
  • 虛線顯示邊框的順序段,他們將被添加
  • 深藍色的數字編程

構建路徑編程

我們將在底部任意啓動左角並順時針旋轉。我將使用圖像中的網格來獲取點的x和y值。我會在這裏硬編碼一切,但當然你不會在真正的項目中這樣做。

的基本過程是:

  1. 創建一個新的UIBezierPath
  2. 選擇路徑上的起點與moveToPoint
  3. 段添加到路徑
    • 行:addLineToPoint
    • 弧:addArcWithCenter
    • 曲線:addCurveToPoint
  4. 關閉與closePath

這裏的路徑是使上述圖像中的路徑的代碼。

func createBezierPath() -> UIBezierPath { 

    // create a new path 
    let path = UIBezierPath() 

    // starting point for the path (bottom left) 
    path.move(to: CGPoint(x: 2, y: 26)) 

    // ********************* 
    // ***** Left side ***** 
    // ********************* 

    // segment 1: line 
    path.addLine(to: CGPoint(x: 2, y: 15)) 

    // segment 2: curve 
    path.addCurve(to: CGPoint(x: 0, y: 12), // ending point 
     controlPoint1: CGPoint(x: 2, y: 14), 
     controlPoint2: CGPoint(x: 0, y: 14)) 

    // segment 3: line 
    path.addLine(to: CGPoint(x: 0, y: 2)) 

    // ********************* 
    // ****** Top side ***** 
    // ********************* 

    // segment 4: arc 
    path.addArc(withCenter: CGPoint(x: 2, y: 2), // center point of circle 
     radius: 2, // this will make it meet our path line 
     startAngle: CGFloat(M_PI), // π radians = 180 degrees = straight left 
     endAngle: CGFloat(3*M_PI_2), // 3π/2 radians = 270 degrees = straight up 
     clockwise: true) // startAngle to endAngle goes in a clockwise direction 

    // segment 5: line 
    path.addLine(to: CGPoint(x: 8, y: 0)) 

    // segment 6: arc 
    path.addArc(withCenter: CGPoint(x: 8, y: 2), 
          radius: 2, 
          startAngle: CGFloat(3*M_PI_2), // straight up 
     endAngle: CGFloat(0), // 0 radians = straight right 
     clockwise: true) 

    // ********************* 
    // ***** Right side **** 
    // ********************* 

    // segment 7: line 
    path.addLine(to: CGPoint(x: 10, y: 12)) 

    // segment 8: curve 
    path.addCurve(to: CGPoint(x: 8, y: 15), // ending point 
     controlPoint1: CGPoint(x: 10, y: 14), 
     controlPoint2: CGPoint(x: 8, y: 14)) 

    // segment 9: line 
    path.addLine(to: CGPoint(x: 8, y: 26)) 

    // ********************* 
    // **** Bottom side **** 
    // ********************* 

    // segment 10: line 
    path.close() // draws the final line to close the path 

    return path 
} 

注意:某些上面的代碼可以通過加入在一個命令的線和弧被減小(因爲電弧具有一個隱含的起點)。有關更多詳細信息,請參閱here

繪製路徑

我們可以借鑑的路徑或者在一個層或drawRect

方法1:在層

我們的定製類看起來像這樣繪製路徑。我們當視圖初始化添加我們的貝塞爾曲線路徑到一個新的CAShapeLayer

import UIKit 
class MyCustomView: UIView { 

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

    required init?(coder aDecoder: NSCoder) { 
     super.init(coder: aDecoder) 
     setup() 
    } 

    func setup() { 

     // Create a CAShapeLayer 
     let shapeLayer = CAShapeLayer() 

     // The Bezier path that we made needs to be converted to 
     // a CGPath before it can be used on a layer. 
     shapeLayer.path = createBezierPath().cgPath 

     // apply other properties related to the path 
     shapeLayer.strokeColor = UIColor.blue.cgColor 
     shapeLayer.fillColor = UIColor.white.cgColor 
     shapeLayer.lineWidth = 1.0 
     shapeLayer.position = CGPoint(x: 10, y: 10) 

     // add the new layer to our custom view 
     self.layer.addSublayer(shapeLayer) 
    } 

    func createBezierPath() -> UIBezierPath { 

     // see previous code for creating the Bezier path 
    } 
} 

,創造我們的視圖控制器視圖這樣

override func viewDidLoad() { 
    super.viewDidLoad() 

    // create a new UIView and add it to the view controller 
    let myView = MyCustomView() 
    myView.frame = CGRect(x: 100, y: 100, width: 50, height: 50) 
    myView.backgroundColor = UIColor.yellow 
    view.addSubview(myView) 

} 

我們得到...

enter image description here

嗯,這是一個有點小,因爲我存的所有號碼在我可以擴展的路徑大小了,雖然,這樣的:

let path = createBezierPath() 
let scale = CGAffineTransform(scaleX: 2, y: 2) 
path.apply(scale) 
shapeLayer.path = path.cgPath 

enter image description here

方法2:draw

繪製路徑使用draw比繪圖層慢,所以這不是推薦的方法,如果你不需要它。

這裏是我們的自定義視圖修改後的代碼:

import UIKit 
class MyCustomView: UIView { 

    override func draw(_ rect: CGRect) { 

     // create path (see previous code) 
     let path = createBezierPath() 

     // fill 
     let fillColor = UIColor.white 
     fillColor.setFill() 

     // stroke 
     path.lineWidth = 1.0 
     let strokeColor = UIColor.blue 
     strokeColor.setStroke() 

     // Move the path to a new location 
     path.apply(CGAffineTransform(translationX: 10, y: 10)) 

     // fill and stroke the path (always do these last) 
     path.fill() 
     path.stroke() 

    } 

    func createBezierPath() -> UIBezierPath { 

     // see previous code for creating the Bezier path 
    } 
} 

這給了我們同樣的結果...

enter image description here

進一步研究

我真的建議看着下面的材料。他們最終使Bézier路徑對我來說是可以理解的。 (教我如何發音:/bɛ紫eɪ/)

+0

如果視圖的「框架」發生變化,該怎麼辦?發生方向變化時,我們如何調整形狀? – ozgur

+1

@ozgur,至少有兩個選項。我會在上面的示例中展示一個縮放(可能是翻譯)轉換。另一種選擇是根據新框架重新計算貝塞爾路徑。在上面的例子中,我將所有數字硬編碼到Bezier路徑中。但是,當我在實際項目中使用貝塞爾路徑時,我根據幀大小確定貝塞爾值。當框架(或更可能的邊界)改變時,我重新計算貝塞爾路徑。 – Suragch

+0

我之前選擇了後者。我重新計算了bezier路徑並使用'layoutSubviews'方法更新了有問題的圖層,但我不確定這是否是正確的位置。當視圖的框架發生變化時,你會在哪裏更新路徑? – ozgur

54

這將是更容易,如果你可以使用一個CAShapeLayer,像這樣:

CAShapeLayer *shapeView = [[CAShapeLayer alloc] init]; 

並設置我TS path

[shapeView setPath:[self createPath].CGPath]; 

最後添加:

[[self.view layer] addSublayer:shapeView]; 
15

可以使用CAShapeLayer做到這一點。

就像這個...

CAShapeLayer *shapeLayer = [CAShapeLayer layer]; 
shapeLayer.path = [self createPath].CGPath; 
shapeLayer.strokeColor = [UIColor redColor].CGColor; //etc... 
shapeLayer.lineWidth = 2.0; //etc... 
shapeLayer.position = CGPointMake(100, 100); //etc... 
[self.layer addSublayer:shapeLayer]; 

這會再加入並繪製路徑,而不必重寫drawRect

1

是的,你如果你想畫一個anything.Creating UIBezierPath可以在任何地方進行重寫的drawRect,但要吸引你必須這樣做的drawrect方法

你應該打電話setNeedsDisplay如果裏面的東西你可以在UIView的子類中重寫drawRect,它基本上是一個自定義視圖,在屏幕上繪製某些東西,如線條,圖像,矩形。

2

正如其他海報指出的那樣,使用形狀圖層是一個好方法。

形狀圖層a比重寫drawRect可能會給你更好的性能。

如果你想自己繪製路徑,那麼是的,你需要覆蓋你的自定義視圖類的drawRect。

5

有多種方法可以完成你的願望。我見過的最多的是:覆蓋drawRect,將你的形狀繪製成CAShapeLayer,然後將它作爲子圖層添加到你的視圖或draw your path onto another context,將其保存爲圖像,然後將其添加到視圖中。

所有這些都是合理的選擇,哪一個是最好取決於許多其他因素,比如你要被不斷添加的形狀,它是如何通常被稱爲等