Swiftでスライダーライブラリを作って学んだあれこれ

こんにちは増島です!

オニドリルにモンスターボールを60個使用しましたが捕獲出来ず、悔しさで眠れませんでした。家の近くにポケストップがあまり無いのでモンスターボールの確保にはいつも必死です。

話は変わりますが、以前iOSのスライダー系のライブラリを公開しました。 その際CoreGraphicsやUIControlなどあまり触れていなかった部分を触ることが出来てとても勉強になりました。 今回は作成を通して学んだ事をiOSのTipsとしてお話します!

公開したライブラリはこちらになります。

https://github.com/shushutochako/CircleSlider
CircleSlider

UISliderを円形にしたイメージでしょうか!
使い方によってプログレス表示なども出来るようになってます。

UIViewをくり抜く

アプリのチュートリアルなどでボタンのヒント表示などで使われているのをよく見ますね! 今回のライブラリだと円形のバーの内側のところで使われています。

internal class TrackLayer: CAShapeLayer {  
  internal init(bounds: CGRect, setting: Setting) {
    super.init()
    ・
    ・
    ・
    self.mask()
  }

・
・
・

  private func mask() {
    let maskLayer = CAShapeLayer()
    maskLayer.bounds = self.bounds
    let ovalRect = self.hollowRect
    let path =  UIBezierPath(ovalInRect: ovalRect)
    path.appendPath(UIBezierPath(rect: maskLayer.bounds))
    maskLayer.path = path.CGPath
    maskLayer.position = self.currentCenter
    maskLayer.fillRule = kCAFillRuleEvenOdd
    self.mask = maskLayer
  }
}

初期化時にmaskメソッドにて真ん中部分をくり抜き、ドーナツのような形状にしています。
マスクするLayerのインスタンスを別途生成して、くり抜きたいlayerのmaskプロパティに指定します。またfillRuleにkCAFillRuleEvenOddを指定する事によって真ん中がくり抜かれた状態に塗りつぶされます。

くり抜かれた部分については背面にあるViewなどへタッチイベントも通知されます。

UIControlの継承

カスタムViewを作るときに色々なイベントを持たせたい場合などは、UIViewではなくUIControlを継承するととても便利です。
UIButtonでよく使用するUIControlEventsや状態関連のenabledなどはこのクラスの持ち物なんですね!

このライブラリでは主にcontinueTrackingWithTouchをオーバライドしてつまみ部分の移動や塗りつぶす部分の描画を制御しています。
このメソッドはタッチ開始後からタッチを終了するまで間のタッチポイントの移動時によばれます。

今回は使用していませんがトラッキング開始時に呼ばれる「beginTrackingWithTouch」や終了時に呼ばれる「endTrackingWithTouch」などをオーバライドしてより柔軟に制御することも可能です。

  override public func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
    // このメソッドでつまみ部分の移動とトラッキング部分の塗りつぶしの描画をする
    return true
  }

また、このスライダーではUISliderを踏襲してValueChangedにて値をハンドリング出来るように設計しています。

ライブラリ側ではsendActionsForControlEventsを呼ぶことによってアプリ側で指定したオブザーバにイベントを通知します。

  • アプリ側のイベント登録
self.circleSlider= CircleSlider(frame:self.sliderArea.bounds, options: self.sliderOptions)

self.circleSlider?.addTarget(self, action: #selector(ViewController.valueChange(_:)), forControlEvents: .ValueChanged)
  • CircleSliderのイベント通知
self.sendActionsForControlEvents(.ValueChanged)  

Layerの塗りつぶし

iOSで柔軟な描画処理をしたい場合はCAShapeLayerを使うのが便利です。
Layerを再描画したい場合にsetNeedsDisplayを実行すると
drawInContextが呼ばれるのでそこで描画の処理を実装します。

internal class TrackLayer: CAShapeLayer {

・
・
・

  override internal func drawInContext(ctx: CGContext) {
    self.drawTrack(ctx)
  }

・
・
・

  private func drawTrack(ctx: CGContext) {
    let adjustDegree = Math.adjustDegree(self.setting.startAngle, degree: self.degree)
    let centerX = self.currentCenter.x
    let centerY = self.currentCenter.y
    let radius = min(centerX, centerY)
    CGContextSetFillColorWithColor(ctx, self.setting.trackingColor.CGColor)
    CGContextBeginPath(ctx)
    CGContextMoveToPoint(ctx, centerX, centerY)
    CGContextAddArc(ctx, centerX, centerY, radius,
      CGFloat(Math.degreesToRadians(self.setting.startAngle)),
      CGFloat(Math.degreesToRadians(adjustDegree)), 0)
    CGContextClosePath(ctx);
    CGContextFillPath(ctx);
  }
}

このライブラリではCGContextAddArcを使って塗りつぶし部分を円弧として描画しています。
「UIViewをくり抜く」で書いた通り、中央部分はマスクがかかっているのでバーに当たる部分だけ塗りつぶされるような表示になります。

他にも四角を描くCGContextAddRectなど色々なメソッドがあるので、柔軟な描画処理がお手軽に実装できます。


このライブラリ作成を通して色々勉強になりましたが、UIBezierPathやCGContextのなどの理解があまい状態なので、これからより深い部分をキャッチアップしていきたいと思います!