iOS
C4
Swift
More than 1 year has passed since last update.

はじめに

Swift Advent Calendar 2016 14日目です。

Swift Advent Calendar 2016ということで、Swiftに関する話を書こうと思っていたのですが拡大解釈してC4入門の記事を書きます。お許し下さいッ…!!

C4とは

http://www.c4ios.com/

簡単に言えばCoreGraphicsやCoreAnimationを扱いやすくしたものだとか、OpenFrameworkのCoreGraphics版とかそういったものです。
描画系関数や数値計算・生成系関数のセットがSwift3.0で書かれています。

極めればOpenFrameworkのように「Swift出来ればVJ出来ちゃう」みたいな感じのイケイケヒップホップエンジニアになれるはずです。

オシャレな展示を作るのに使ってもよし、UX向上の為に使ってもよしです。
しかも記述も非常に簡単なのでこれからSwiftを始める人にもおすすめです。

インストール方法

Podfile
target 'C4Lab' do
  use_frameworks!
  pod 'C4'
end

cocoapodsに対応しているので、Podfileに書けばインストール出来ます。
公式サイトにはCarthageやマニュアルインストールの方法も書いてあるので、好みによって参照してください。

標準の描画関数

関数の殆どはC4独自のものですが、基本的にはUIKitやFoundationからプリフィクスを取った(Point/Rect/Transformなど)が使われます。
またそれぞれにFoundationの構造体を引数に取って生成するイニシャライザがあるので、例えばCGRectをRectに変換する場合もRect(cgrectValue)のように、書くことができます。
これはSwiftのキャストと非常に記述が似ているので自然に使う事ができます。

以下は公式チュートリアルの基本的な部分を抜粋しています。

背景を青で塗る

ViewController.swift
final class ViewController: CanvasController {
  override func setup() {
    canvas.backgroundColor = C4Blue
  }
}

基本的にC4ではUIViewControllerの代わりにCanvasControllerを継承します。
CanvasControllerにはUIViewControllerにviewが生えているようにcanvasが生えており、それに対してC4のViewをaddして行きます。

スクリーンショット 2016-12-12 23.20.06.png

上記はcanvasの背景色を青にした状態です。
viewDidLoadに初期化の記述を書くように、C4ではsetup関数の中に初期化のプロセスを記述します。

四角を描く

final class ViewController: CanvasController {
  override func setup() {
    let square = Rectangle(frame: Rect(0, 0, 100, 100))
    square.center = canvas.center
    canvas.add(square)
  }
}

スクリーンショット 2016-12-12 23.35.51.png

ビューの追加はUIViewをaddSubViewする時と同じように行います。
addメソッドの内部では

View.swift
    public func add<T>(_ subview: T?) {
        if let v = subview as? UIView {
            view.addSubview(v)
        } else if let v = subview as? View {
            view.addSubview(v.view)
        } else {
            fatalError("Can't add subview of class `\(type(of: subview))`")
        }
    }

このような分岐が行われており、C4のViewの場合はC4.ViewからUIViewを生成してaddSubViewをしています。

C4では一定のパターンの図形はクラスとして定義されており、簡単に生成することが出来ます。

円を描く

final class ViewController: CanvasController {
  override func setup() {
    let circle = Circle(center: canvas.center, radius: 50)
    canvas.add(circle)
  }
}

スクリーンショット 2016-12-12 23.37.00.png

円を描く際、自前で行うとボーダーラインがframeから漏れてしまい微妙に小さくしたりする事がありますがC4では内側に線を引いてくれるようです。

楕円を描く

final class ViewController: CanvasController {
  override func setup() {
    let ellipse = Ellipse(frame: Rect(0, 0, 200, 100))
    ellipse.center = canvas.center
    canvas.add(ellipse)
  }
}

スクリーンショット 2016-12-12 23.37.47.png

CircleはEllipseを継承しており、継承元のEllipseではCircleで補正されているアス比を無視して楕円を描画することが出来ます。

線を描く

final class ViewController: CanvasController {
  override func setup() {
    let points = (Point(), Point(100, 100))
    let line = Line(points)
    line.center = canvas.center
    canvas.add(line)
  }
}

スクリーンショット 2016-12-12 23.38.42.png

基本的な線を描画します。

三角を描く

final class ViewController: CanvasController {
  override func setup() {
    let points = [Point(), Point(100, 100), Point(200, 0)]
    let triangle = Triangle(points)
    triangle.center = canvas.center
    canvas.add(triangle)
  }
}

スクリーンショット 2016-12-12 23.39.24.png

Triangleクラスは与えるpointsが3つ以下だとassertしてくれます。

ポリゴンを描く

final class ViewController: CanvasController {
  override func setup() {
    let points = [Point(), Point(100, 100), Point(200, 0), Point(300, 100)]
    let polygon = Polygon(points)
    polygon.center = canvas.center
    canvas.add(polygon)
  }
}

スクリーンショット 2016-12-12 23.40.11.png

PolygonはTriangleやLineの基底クラスです。
基本的にはPolygonを使えばTriangleやLineと同じように描画出来ます。

多角形

final class ViewController: CanvasController {
  override func setup() {
    let regularPolyon = RegularPolygon(center: canvas.center, radius: 50.0, sides: 6, phase: 0.0)
    canvas.add(regularPolyon)
  }
}

スクリーンショット 2016-12-12 23.41.58.png

多角形を描画します。これも基底クラスはPolygonです。

星を描く

final class ViewController: CanvasController {
  override func setup() {
    let star = Star(center: canvas.center, pointCount: 5, innerRadius: 25.0, outerRadius: 50.0)
    canvas.add(star)
  }
}

スクリーンショット 2016-12-12 23.42.31.png

Starという名前がついていますが、頂点を凸部分は5つ以上にすることで破裂型吹き出しのような形にすることも出来ます。

ここまではSketchにあるシェイプによく似ていますね

スクリーンショット 2016-12-13 1.53.37.png

かまぼこ型を描く

final class ViewController: CanvasController {
  override func setup() {
    let arc = Arc(center: canvas.center, radius: 50, start: M_PI, end: 2 * M_PI)
    canvas.add(arc)
  }
}

スクリーンショット 2016-12-12 23.43.06.png

楔形を描く

final class ViewController: CanvasController {
  override func setup() {
    let wedge = Wedge(center: canvas.center, radius: 50, start: 1.25 * M_PI, end: 1.75 * M_PI)
    canvas.add(wedge)
  }
}

スクリーンショット 2016-12-12 23.43.46.png

ピザ型を表現できます。
タイマーの経過のアニメーションにも使えそうですね。

テキストを描画する

final class ViewController: CanvasController {
  override func setup() {
    let string = "C4"
    let textShape = TextShape(text: string)!
    textShape.center = canvas.center
    canvas.add(textShape)
  }
}

スクリーンショット 2016-12-12 23.44.27.png

テキストを直接描画します。
フォントを選ぶこともできます

final class ViewController: CanvasController {
  override func setup() {
    let string = "日本語のテスト"
    let textShape = TextShape(text: string)!
    textShape.center = canvas.center
    canvas.add(textShape)
  }
}

スクリーンショット 2016-12-12 23.44.54.png

日本語は表示されないようです。
フォントの問題なのかな

プロパティ

基本的なプロパティ

final class ViewController: CanvasController {
  override func setup() {
    let r = Rectangle(frame: Rect(0, 0, 100, 100))
    r.center = canvas.center
    r.lineWidth = 8 //線の太さ
    r.strokeColor = C4Blue //線の色
    r.fillColor = C4Pink //塗りの色
    canvas.add(r)
    canvas.backgroundColor = C4Purple //背景色
  }
}

スクリーンショット 2016-12-12 23.50.29.png

太さや色、塗り色などを指定出来ます。

色について

基本色

基本色が存在し、この色はC4をimportしていれば
backgroundColor = black
のように直接呼び出す事ができます。

black
darkGray
lightGray
white
gray
red
green
blue
cyan
yellow
magenta
orange
purple
brown
clear
C4Pink
C4Blue
C4Purple
C4Grey

カスタムカラー

UIColorやCGColorから生成したりRGBを指定して生成することも出来ます。

Color(UIColor.red.cgColor)
Color(UIColor.red)
Color(12)
Color(red: 0.25, green: 0.5, blue: 0.75, alpha: 1.0)

カスタムカラー2

Color(pattern: String)を使うと画像を敷き詰める事ができます。
これもC4の世界ではColorとして扱います。

final class ViewController: CanvasController {
  override func setup() {
    canvas.backgroundColor = Color("image1")
  }
}

スクリーンショット 2016-12-13 0.00.55.png

画像

Image

final class ViewController: CanvasController {
  override func setup() {
    let image = Image("image1")!
    image.center = canvas.center
    canvas.add(image)
  }
}

スクリーンショット 2016-12-13 0.02.33.png

画像も単体のクラスとして存在します。UIImageViewと違い、Image自体をcanvasにadd出来ます。

Filter

final class ViewController: CanvasController {
  override func setup() {
    let image = Image("image1")
    image?.center = canvas.center
    canvas.add(image)

    var dotScreen = DotScreen()
    dotScreen.width = 10.0
    image?.apply(dotScreen)
  }
}

スクリーンショット 2016-12-13 0.03.27.png

フィルタをかける処理も短く書けます。
C4にはDotScreen以外にもガウジアンブラーやブルームといったフィルターも最初から入っています。
もちろんFilterクラスを継承すれば自分でフィルタを作ることも出来ます。
フィルタの実態はCIFilterなので、CIFilterが書ける人はもちろんOpenGLのシェーダーを使うことも出来ます。

Generate Image

final class ViewController: CanvasController {
  override func setup() {
    let image = Image()
    image.frame = Rect(0, 0, 100, 100)

    let checkerBoard = Checkerboard()
    image.generate(checkerBoard)
    image.center = canvas.center
    canvas.add(image)
  }
}

スクリーンショット 2016-12-13 0.06.39.png

画像を生成するクラスです。
現在C4にはLinearGradientとCheckerboardしかありません。

Video

再生

final class ViewController: CanvasController {
  override func setup() {
    let movie = Movie("ipod.mov")
    movie?.center = canvas.center
    canvas.add(movie)
    movie?.play()
  }
}

C4.gif

AVFoundationを意識せずに再生できます。
このMovie自体にもフィルタをかけたり出来ます。
上記のgifのようにデフォルトでは自動的にリサイズはされず、実サイズで再生されます。

Gesture

C4ではUIGestureRecognizerもラップした関数があり、Viewに生えています。
直感的にビューに対してタップやパンの時の処理を書くことが出来ます。

タップ

final class ViewController: CanvasController {
  override func setup() {
    let square = Rectangle(frame: Rect(0, 0, 100, 100))
    square.addTapGestureRecognizer { (locations, center, state) in
      square.fillColor = red
    }
    canvas.add(square)
  }
}

アニメーション

C4ではアニメーションはViewAnimationクラスを使って記述します。
ViewAnimationクラスは繋げたり同時に実行したりループしたりが簡単に行えて、色や移動の補完も自動的に行ってくれます。

基本的なアニメーション

ViewAnimation(duration: 1.0) {
        square.fillColor = C4Pink
}.animate()

このようにduraitonを指定しクロージャ内で最終状態を記述します。
UIView.animateWithDurationと非常に似ています。

let anim = ViewAnimation(duration: 1.0) {
    self.canvas.backgroundColor = C4Blue
}

anim.curve = .EaseOut
anim.repeats = true

wait(1.0) {
    anim.animate()
}

ViewAnimation自体はインスタンスを返すので、それに対してプロパティを付与することでアニメーションを変化させることが出来ます。

色々遊んでみた

四角と円形をタップで切り替える

final class ViewController: CanvasController {

  private var isCircle = false

  override func setup() {
    let square = Rectangle(frame: Rect(0, 0, 100, 100))
    square.corner = Size(10, 10)
    square.center = canvas.center
    canvas.add(square)

    _ = square.addTapGestureRecognizer { (locations, center, state) in
      self.isCircle = !self.isCircle
      if self.isCircle {
        ViewAnimation(duration: 1.0, animations: {
          square.corner = Size(10, 10)
        }).animate()
      } else {
        ViewAnimation(duration: 1.0, animations: {
          square.corner = Size(50, 50)
        }).animate()
      }
    }
  }
}

circle.gif

C4.ViewをUIViewのように追加する

C4.CanvasControllerじゃなくてもいいっぽい

final class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    let rect = Rect(CGRect(x: 0, y: 0, width: 100, height: 100))
    let c4v = C4.View(frame: rect)
    c4v.center = Point(view.center)
    c4v.backgroundColor = red
    view.add(c4v)
  }
}

Simulator Screen Shot 2016.12.13 0.53.34.png

モーフィング

final class ViewController: CanvasController {
  override func setup() {
    let c1 = Circle(center: canvas.center, radius: 20)
    c1.fillColor = clear
    c1.strokeColor = lightGray
    canvas.add(c1)

    let c2 = Circle(center: canvas.center, radius: 20)
    c2.fillColor = clear
    c2.strokeColor = lightGray
    canvas.add(c2)

    let c3 = Circle(center: canvas.center, radius: 20)
    c3.fillColor = clear
    c3.strokeColor = lightGray
    canvas.add(c3)

    let c1Anim = ViewAnimation(duration: 1.0, animations: {
      c1.transform = Transform.makeScale(0.2, 0.2)
    })

    let c2Anim = ViewAnimation(duration: 1.0, animations: {
      c2.transform.scale(0.2, 0.2)
      c2.transform.translate(Vector(x: -50, y: 0))
    })

    let c3Anim = ViewAnimation(duration: 1.0, animations: {
      c3.transform.scale(0.2, 0.2)
      c3.transform.translate(Vector(x:  50, y: 0))
    })

    canvas.addTapGestureRecognizer { (_, _, _) in
      ViewAnimationGroup(animations: [c1Anim, c2Anim, c3Anim]).animate()
    }
  }
}

mop.gif

まとめ

基本的なところをさらっと載せました。
C4はCoreGraphicsやUIKitを使っていることもあり、低コストにマイクロインタラクションを実装したり出来るなど、一般的なアプリケーション開発との親和性も高そうに思えました。

エンジニアがちょっとした動きを実装したり、デザイナーも実際にコードを触って実装に関われるような簡単さもあって中々楽しいのではないでしょうか。

このC4を通してSwiftを知ったり、逆にエンジニアが芸術・展示作品に対してコミットしていくという事が出来そうに思います。

明日は @_ha1f さんの 「Photos.frameworkのサンプルリーディング」です!
それでは!