
コントロールは、アプリケーションの最も重要なコンポーネントの1つです。 実際、これらはユーザーが何らかの方法でアプリケーションやそのデータと対話できるようにするグラフィックコンポーネントです。 このレッスンでは、アプリケーションで後で使用できるカスタムコントロールを作成します。
Appleは、開発者に
UITextField
、
UIButton
UISwitch
を含む約20の異なるUIコンポーネントを提供し
UIButton
。 標準コントロールのすべての機能を使用して、さまざまなインターフェイスオプションを作成できます。 場合によっては、標準コンポーネントでは実現できないものが必要になることがあります。
例を挙げましょう。 販売中の不動産のリストをコンパイルするアプリケーションを開発しているとします。 ユーザーは、特定の価格帯で検索結果を表示できます。 別の方法として、2つの
UISlider
を使用できます。1つはスクリーンショットに示すように、1つは最低価格を設定し、もう1つは最大値を設定します。

このオプションは機能しますが、価格帯の概念をあまりよく示していません。 最小値と最大値を担当する2つのスライダーを備えた1つのスライダーを使用することをお勧めします。

このような要素は使用する方がはるかに便利です。ユーザーは、固定値ではなく値の範囲を設定することをすぐに理解します。 残念ながら、このようなスライダーは標準要素のセットには含まれていません。 その機能を実装するには、カスタムコントロールを開発する必要があります。
このようなスライダーを
UIView
サブクラスにすることができます。 そして、ある特定のアプリケーションのコンテキストで、そのようなソリューションがそれ自体を正当化する場合、他の開発でそれを再利用するには、いくらかの努力が必要です。 このコンポーネントをユニバーサルにして、あらゆるアプリケーションで使用できるようにすることをお勧めします。 これがカスタムコントロールの意味です。
既に述べたように、カスタムコンポーネントは
UIKit Framework
が作成したコントロールであり、
UIKit Framework
一部ではありません。 標準コンポーネントと同様に、それらは完全に普遍的で、あらゆるニーズにカスタマイズ可能でなければなりません。 最近、開発者のコミュニティ全体が形成され、コンポーネントをパブリックドメインに掲載しています。
このレッスンでは、上記の問題を解決する独自の
RangeSlider
コントロールを開発する方法を示します。 既存のコンポーネントの拡張、APIの開発、作成物のパブリックアクセスなどの問題も影響を受けます。
理論は十分だ! カスタマイズを開始しましょう!
開始する
このセクションでは、コンポーネントの基本構造の開発に専念します。これは、画面に簡単なスライダーを表示するのに十分です。 Xcodeを起動し、[ ファイル]-> [新規]-> [プロジェクト]を選択します 。 表示されるウィンドウで、 iOS-> Application-> Single View Applicationを選択し 、 Nextをクリックします。 次の画面で、プロジェクト名としてCERangeSliderと入力し、残りのフィールドに入力します。

このプロジェクトでは、1つの画面で操作するため、ストーリーボードは使用されません。 クラスプレフィックスとして他のものを使用できます。最も重要なことは、対応する変更がアプリケーションコードで発生することを忘れないでください。 「組織名」フィールドと「会社識別子」フィールドに独自の値を入力できます。 完了したら、 「次へ 」をクリックします。 プロジェクトの保存場所を選択し、[ 作成 ]をクリックします 。
カスタムコントロールを作成するときに行う必要がある最初の決定は、継承または拡張する既存のクラスです。 クラス
UIView
ことが重要です。
Apple UIKit
さまざまなコンポーネントをよく見ると、
UILabel
や
UIWebView
などの多くの要素が
UIView
直接継承していることが
UILabel
ます。 ただし、この図に示すように、
UIControl
を継承する要素が存在する可能性があります。

注:インターフェイス要素の詳細な階層は、 UIKit Framework Referenceにあります。
UIControl
クラスは、基本的にコンポーネントの変更を通知する方法であるTarget-Actionテンプレートを実装します。
UIControl
は、オブジェクトの状態の監視に関連するいくつかのプロパティ
UIControl
あります。 このようなテンプレートを使用してコンポーネントを作成するため、
UIControl
が優れた出発
UIControl
となります。
Project Navigatorで CERangeSliderグループを右クリックし、[ 新規ファイル]を選択してから、[ iOS]-> [Cocoa Touch]-> [Objective-C class ]を選択し、[ 次へ ]をクリックします。 クラスにCERangeSliderという名前を付け、 「サブクラス」フィールドにUIControl と入力します 。 「次へ」および「 作成」をクリックして、クラスの保存場所を選択します。
コードの記述自体は楽しいプロセスですが、ほとんどの場合、アイテムが画面にどのように表示されるかを確認する必要があります。 コードの記述を開始する前に、コンポーネントをView Controllerに追加します。
CEViewController.mを開き、次の行を貼り付けます。
#import "CERangeSlider.h"
次に、同じファイルに変数を追加します。
@implementation CEViewController { CERangeSlider* _rangeSlider; }
標準の
viewDidLoad
次のコードブロックに置き換えます。
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSUInteger margin = 20; CGRect sliderFrame = CGRectMake(margin, margin, self.view.frame.size.width - margin * 2, 30); _rangeSlider = [[CERangeSlider alloc] initWithFrame:sliderFrame]; _rangeSlider.backgroundColor = [UIColor redColor]; [self.view addSubview:_rangeSlider]; }
このコードは、必要なディメンションを持つクラスのオブジェクトを作成し、画面に追加します。 コンポーネントの背景色は、アプリケーションのメインの背景で目立つように赤に設定されます。 色を指定しない場合、コンポーネントは透明のままになり、消えた場所を長時間パズルします。 :]
アプリケーションをビルドして実行します。 次の画面が表示されます。

プロジェクトに視覚要素を追加する前に、コンポーネントに保存されているさまざまな情報を追跡するために、いくつかのプロパティを作成する必要があります。 これは、将来のAPIを作成するための基礎として機能します。
注:コンポーネントAPIは、他の開発者に提供する予定のメソッドとプロパティを定義します。 記事の後半で、APIの構造について読むことになります。今のところ、連絡を取り合いましょう!
標準コンポーネントプロパティを追加する
CERangeSlider.hを開き、 @ interfaceと
@end:
間に次のプロパティを追加します
@end:
@property (nonatomic) float maximumValue; @property (nonatomic) float minimumValue; @property (nonatomic) float upperValue; @property (nonatomic) float lowerValue;
これらの4つのプロパティは、コンポーネントの状態(範囲の最大値と最小値、および現在の下限と上限のしきい値)を記述するのに十分です。
適切に設計されたコントロールには標準設定が含まれている必要があります。そうでないと、画面にレンダリングするときに少し奇妙に見えます。 CERangeSlider.mを開き、 Xcodeによって生成された
initWithFrame:
メソッドを見つけて、次のコードに置き換えます。
- (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code _maximumValue = 10.0; _minimumValue = 0.0; _upperValue = 8.0; _lowerValue = 2.0; } return self; }
次に、コンポーネントのインタラクティブ要素であるスライダーとそれらが配置されているプログレスバーを操作します。
画像と Coregraphics
画面にアイテムを表示する方法は2つあります。
- 画像コンポーネントの一部として使用し、
- レイヤーとCore Graphicsの組み合わせを使用します。
各メソッドには長所と短所があります。
- 少なくとも描画できる場合は、画像を使用するのが最も簡単なオプションです。 他の開発者がこれらの画像を独自の画像に置き換えることを許可する場合は、
UIImage
プロパティとして設定できます。 - イメージを使用すると、コンポーネントに十分な柔軟性が提供されます。 サードパーティの開発者はすべてのピクセルに至るまですべてを変更できますが、一方では特定の描画スキルが必要です。また、コードを使用して何かを変更することは非常に困難です。
アプリケーションコードは、 Core Graphicsを使用してコンポーネントを表示する責任があり、その結果、プログラマはさらに多くの労力を必要とします。 しかし、この方法により、より柔軟なAPIを構築できます。 Core Graphicsを使用すると、コンポーネントのほぼすべてのプロパティ(色、厚さ、曲率)、および実際にレンダリングを行うパラメーターを設定できます。 このアプローチにより、開発者はコントロールを使用して、ニーズに簡単に適合させることができます。
このレッスンでは、2番目のアプローチであるCore Graphicsを使用します。
注:不思議なことに、Appleはコンポーネントでグラフィックリソースを使用することを好みます。 ほとんどの場合、これは各要素の標準サイズを設定し、完全なカスタマイズを許可していないためです。
Xcodeで 、プロジェクト設定ウィンドウに移動します。 次に、「 ビルドフェーズ」タブと「 ライブラリとバイナリをリンク」セクションを選択します。 リストにQuartzCore.frameworkを追加します。 このフレームワークのクラスとメソッドは、コンポーネントを手動でレンダリングするために使用されます。
このスクリーンショットは、混乱した場合にQuartzCore.frameworkを見つけて追加する方法を示しています。

CERangeSlider.mを開き、次の行を追加します。
#import <QuartzCore/QuartzCore.h>
@implementation
直後に、同じファイルに次の変数を追加します。
@implementation CERangeSlider { CALayer* _trackLayer; CALayer* _upperKnobLayer; CALayer* _lowerKnobLayer; float _knobWidth; float _useableTrackLength; }
これらの3つのレイヤー
_trackLayer
、
_upperKnobLayer
および
_lowerKnobLayer
は、コンポーネントのさまざまな要素を表示するために使用されます。 2つの変数
_knobWidth
および
_useableTrackLength
は、これらの要素のパラメーターを設定するために使用されます。
initWithFrame:
見つけて、次のコードを
if (self) { }
ブロックに追加します。
_trackLayer = [CALayer layer]; _trackLayer.backgroundColor = [UIColor blueColor].CGColor; [self.layer addSublayer:_trackLayer]; _upperKnobLayer = [CALayer layer]; _upperKnobLayer.backgroundColor = [UIColor greenColor].CGColor; [self.layer addSublayer:_upperKnobLayer]; _lowerKnobLayer = [CALayer layer]; _lowerKnobLayer.backgroundColor = [UIColor greenColor].CGColor; [self.layer addSublayer:_lowerKnobLayer]; [self setLayerFrames];
このコードブロックは3つのレイヤーを作成し、それらをメインのレイヤーに子として追加します。 同じファイルに次のメソッドを追加します。
- (void) setLayerFrames { _trackLayer.frame = CGRectInset(self.bounds, 0, self.bounds.size.height / 3.5); [_trackLayer setNeedsDisplay]; _knobWidth = self.bounds.size.height; _useableTrackLength = self.bounds.size.width - _knobWidth; float upperKnobCentre = [self positionForValue:_upperValue]; _upperKnobLayer.frame = CGRectMake(upperKnobCentre - _knobWidth / 2, 0, _knobWidth, _knobWidth); float lowerKnobCentre = [self positionForValue:_lowerValue]; _lowerKnobLayer.frame = CGRectMake(lowerKnobCentre - _knobWidth / 2, 0, _knobWidth, _knobWidth); [_upperKnobLayer setNeedsDisplay]; [_lowerKnobLayer setNeedsDisplay]; } - (float) positionForValue:(float)value { return _useableTrackLength * (value - _minimumValue) / (_maximumValue - _minimumValue) + (_knobWidth / 2); }
setLayerFrames
は、スライダーの現在の値に基づいて、スライダーと進行状況バーの両方のサイズを設定します。
positionForValue
は、単純な比率を使用して値を画面の座標にバインドし、コンポーネントの最大値と最小値の間の距離をスケーリングします。
アプリケーションをコンパイルして実行します。 スライダーが形になり始めます! 次のようになります。

コンポーネントはフォームを取得しましたが、作業はまだ始まったばかりです。結局、各コントロールはユーザーにフォームを管理する機能を提供する必要があります。 あなたの場合、ユーザーは各スライダーを動かして、希望の範囲を設定できるはずです。 これらの変更を追跡し、値に基づいてコンポーネントのプロパティと外観の両方を更新します。
インタラクティブ機能コンポーネントを追加
ユーザーがコンポーネントとやり取りするコードは、どのスライダーが移動したかを追跡し、それに応じて外観を更新する必要があります。 これを実装するのに最適な場所は、コンポーネントのレイヤーです。
新しいファイルをCERangeSliderグループに追加します。 新規ファイル-> iOS- > Cocoa Touch- > Objective-Cクラス 、
CALayer
サブクラスにしてCERangeSliderKnobLayerという名前を付けます。
新しく作成されたCERangeSliderKnobLayer.hを開き、その内容を次のものに置き換えます。
#import <QuartzCore/QuartzCore.h> @class CERangeSlider; @interface CERangeSliderKnobLayer : CALayer @property BOOL highlighted; @property (weak) CERangeSlider* slider; @end
このコードは2つのプロパティを追加します。1つはスライダーが強調表示されているかどうかを示し、もう1つは親スライダーを示しています。 CERangeSliderKnobLayer.mを開き、 #importを追加し
#import
。
#import "CERangeSliderKnobLayer.h"
次に、
@implementation
ブロックの
_upperKnobLayer
および
_lowerKnobLayer
タイプを変更します。
CERangeSliderKnobLayer* _upperKnobLayer; CERangeSliderKnobLayer* _lowerKnobLayer;
これらのレイヤーは、新しく作成されたCERangeSliderKnobLayerクラスのオブジェクトになりました。
同じCERangeSlider.mで
initWithFrame:
見つけ、初期化コード
upperKnobLayer
と
lowerKnobLayer
を次のコードブロックに置き換えます。
_upperKnobLayer = [CERangeSliderKnobLayer layer]; _upperKnobLayer.slider = self; _upperKnobLayer.backgroundColor = [UIColor greenColor].CGColor; [self.layer addSublayer:_upperKnobLayer]; _lowerKnobLayer = [CERangeSliderKnobLayer layer]; _lowerKnobLayer.slider = self; _lowerKnobLayer.backgroundColor = [UIColor greenColor].CGColor; [self.layer addSublayer:_lowerKnobLayer];
このコードは、作成したクラスを使用してレイヤーを初期化し、
slider
プロパティを
self
設定します。 プロジェクトを実行し、すべてがスクリーンショットと同じに見えることを確認します。

レイヤーが配置されたので、スライダーを移動する機能を実現する必要があります。
クリックハンドラーを追加する
CERangeSlider.mを開き、変数宣言ブロックの下に次のコードを追加します。
CGPoint _previousTouchPoint;
この変数は、クリック座標を追跡するために使用されます。 コンポーネントのさまざまなクリックイベントとリリースイベントをどのように追跡しますか?
UIControl
は、クリックを追跡するためのいくつかのメソッドを提供します。 UIControl
UIControl
はこれらのメソッドをオーバーライドして、独自のロジックを実装できます。 コントロールで
continueTrackingWithTouch
beginTrackingWithTouch
、
continueTrackingWithTouch
および
endTrackingWithTouch
3つのメソッドをオーバーライドします。
CERangeSlider.mに次のメソッドを追加します。
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { _previousTouchPoint = [touch locationInView:self]; // hit test the knob layers if(CGRectContainsPoint(_lowerKnobLayer.frame, _previousTouchPoint)) { _lowerKnobLayer.highlighted = YES; [_lowerKnobLayer setNeedsDisplay]; } else if(CGRectContainsPoint(_upperKnobLayer.frame, _previousTouchPoint)) { _upperKnobLayer.highlighted = YES; [_upperKnobLayer setNeedsDisplay]; } return _upperKnobLayer.highlighted || _lowerKnobLayer.highlighted; }
このメソッドは、ユーザーが最初にコンポーネントに触れたときに呼び出されます。 クリックイベントをコンポーネント座標系に変換します。 次に、各スライダーをチェックして、クリックがいずれかの範囲内に収まっているかどうかを判断します。 その結果、メソッドは現在のクリックを追跡するかどうかについて親クラスに通知します。
スライダーの1つが強調表示されている場合、クリックトラッキングが続行されます。
setNeedsDisplay
メソッドを呼び出すと、レイヤーが更新されたことを確認でき
setNeedsDisplay
。これが重要な理由を理解できます。
ファーストクリックハンドラーが用意できたので、ユーザーの指が画面上を動き回るときにイベントを処理する必要があります。 CERangeSlider.mに次のメソッドを追加します。
#define BOUND(VALUE, UPPER, LOWER) MIN(MAX(VALUE, LOWER), UPPER) - (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { CGPoint touchPoint = [touch locationInView:self]; // 1. determine by how much the user has dragged float delta = touchPoint.x - _previousTouchPoint.x; float valueDelta = (_maximumValue - _minimumValue) * delta / _useableTrackLength; _previousTouchPoint = touchPoint; // 2. update the values if (_lowerKnobLayer.highlighted) { _lowerValue += valueDelta; _lowerValue = BOUND(_lowerValue, _upperValue, _minimumValue); } if (_upperKnobLayer.highlighted) { _upperValue += valueDelta; _upperValue = BOUND(_upperValue, _maximumValue, _lowerValue); } // 3. Update the UI state [CATransaction begin]; [CATransaction setDisableActions:YES] ; [self setLayerFrames]; [CATransaction commit]; return YES; }
このコードをコメントごとに分析してみましょう。
- まず、
delta
-指を動かしたピクセル数を計算します。 次に、コンポーネントの最小値と最大値に応じてそれらを変換します。 - ここでは、ユーザーがスライダーを動かした場所に応じて、上限と下限を変更します。
MIN/MAX
より読みやすいBOUND
マクロを使用していることに注意してください。 - このコードブロックは、
disabledActions
フラグをCATransaction
設定しdisabledActions
。 これにより、各レイヤーの境界の変更がすぐに適用され、アニメーション化されないことを確認できます。 最後に、setLayerFrames
メソッドがsetLayerFrames
、スライダーを目的の場所に移動します。
スライダーの動きを実装しましたが、それでもコンポーネントとの対話の終了を処理する必要があります。 CERangeSlider.mに次のメソッドを追加します。
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { _lowerKnobLayer.highlighted = _upperKnobLayer.highlighted = NO; [_lowerKnobLayer setNeedsDisplay]; [_upperKnobLayer setNeedsDisplay]; }
このコードは、両方のスライダーを非アクティブ状態に戻します。 プロジェクトを起動して、新しいスライダーで少し遊んでください! スクリーンショットと同じように見えるはずです:

コンポーネントの境界を越えて指を動かし、メソッドの実行を中断せずに境界に戻ることができることに気付くかもしれません。 これは、小さな画面と低精度のスタイラスを備えたデバイスの使いやすさの非常に重要な機能です(理解できない場合は、指について話します)。 :]
変更通知
したがって、ユーザーが範囲の上限と下限を設定できるインタラクティブなコントロールがあります。 しかし、アプリケーションで使用できるように、これらの変更を残りのコードに関連付ける方法はありますか? NSNotification 、 Key-Value-Observing(KVO) 、委任テンプレート、Target-Actionテンプレートなど、変更の通知に使用できる多くの異なるオプションがあります。
UIKit
のコンポーネントを見ると、 NSNotificationまたはKVOを使用していないことがわかります
UIKit
との互換性のために、これら2つのオプションを放棄する必要があります。
UIKit
、他の2つのテンプレート-委任とTarget-
UIKit
が非常によく使用され
UIKit
。
両方のパターンの詳細な分析を行います。
- 委任を使用して、さまざまな通知に使用される多くのメソッドを含むプロトコルを作成します。 コンポーネントには、一般にデリゲートと呼ばれる、このプロトコルを実装するクラスを受け入れるプロパティがあります。 古典的な例は、UITableViewDelegateプロトコルを提供するUITableViewです。 コントロールに含めることができるデリゲートオブジェクトは1つだけです。 委任されたメソッドは任意の数のパラメーターを受け入れることができるため、必要なだけ情報を渡すことができます。
- Target-Actionテンプレートは、
UIControl
基本クラスによって実装されます。 コンポーネントの状態が変化すると、 ターゲットにアクションが通知されます。アクションは、UIControlEvents
列挙値の1つによって記述されます。 すべてのアクションを制御するために多くの目標を提供できます。独自のイベント(UIControlEventApplicationReserver
)を追加することは可能ですが、その数は4つに制限されています。 これらのアクションは、イベントとともに情報を送信できません。 したがって、それらを使用して追加のデータを送信することはできません。
2つのパターンの主な違いは次のとおりです。
- Target-Actionテンプレートはその変更をブロードキャストしますが、委任は単一のオブジェクトに制限されます。
- 委任を使用するときは、プロトコルを独立して実装します。つまり、送信する特定の情報を決定できます。Target-Actionでは、追加のデータを転送できません。
コンポーネントには、通知が必要な可能性のある状態やイベントが多くありません。役割を果たすのは、スライダーの最大値と最小値だけです。
この状況では、Target-Actionテンプレートの使用が最も正当化されます。これが
UIControl
、レッスンの最初に継承した理由の1つです。
スライダーの読み取り値はmethod内で更新される
continueTrackingWithTouch:withEvent:
ため、ここで通知メカニズムを実装する必要があります。CERangeSlider.mを開き、メソッド
continueTrackingWithTouch:withEvent
を見つけて、前に次のコードを追加します
return YES
。
[self sendActionsForControlEvents:UIControlEventValueChanged];
変更の必要な目標を通知するために必要なのはこれだけです。まあ、それは予想よりも簡単でした!CEViewController.mを
開き、メソッドの最後に次のコードを追加します。
viewDidLoad
[_rangeSlider addTarget:self action:@selector(slideValueChanged:) forControlEvents:UIControlEventValueChanged];
このコードは、
slideValueChanged
スライダーがイベントを送信するたびにメソッドを実行し
UIControlEventValueChanged
ます。
次のメソッドをCEViewController.mに追加します。
- (void)slideValueChanged:(id)control { NSLog(@"Slider value changed: (%.2f,%.2f)", _rangeSlider.lowerValue, _rangeSlider.upperValue); }
このメソッドは、すべてが正常に機能することの証拠として、スライダーのすべての値をログに送信します。アプリケーションを起動し、スライダーのスライダーを移動します。座標の値がログに表示されます:

恐ろしいフルーツサラダのように見えるカラフルなインターフェイスに既にうんざりしています。コンポーネントに正しい外観を与える時が来ました!
Core Graphicsを使用してコンポーネントの外観を変更する
まず、スライダーが移動するプログレスバーの外観を更新しましょう。CERangeSliderグループで、CALayerのサブクラスである新しいCERangeSliderTrackLayerクラスを追加します。CERangeSliderTrackLayer.hを開き、その内容を次のものに置き換えます。
#import <QuartzCore/QuartzCore.h> @class CERangeSlider; @interface CERangeSliderTrackLayer : CALayer @property (weak) CERangeSlider* slider; @end
このコードは、スライダーレイヤーに対して行ったことに基づいて、スライダーへのリンクを追加します。CERangeSlider.mを開き、次を追加します
#import
。
#import "CERangeSliderTrackLayer.h"
すぐ下の変数を見つけ、
_trackLayer
そのタイプを作成したクラスに変更します。
CERangeSliderTrackLayer* _trackLayer;
メソッド
initWithFrame:
を見つけて、レイヤー作成コードを更新します。
_trackLayer = [CERangeSliderTrackLayer layer]; _trackLayer.slider = self; [self.layer addSublayer:_trackLayer]; _upperKnobLayer = [CERangeSliderKnobLayer layer]; _upperKnobLayer.slider = self; [self.layer addSublayer:_upperKnobLayer]; _lowerKnobLayer = [CERangeSliderKnobLayer layer]; _lowerKnobLayer.slider = self; [self.layer addSublayer:_lowerKnobLayer];
このコードにより、新しいストリップが使用され、vyrviglazny色が背景に適用されなくなることを確認できます。 :]
もう少しやることは残っています-コンポーネントから赤い背景を削除します。CEViewController.mを開き、メソッドで次のコード行を見つけて
viewDidLoad
消去します。
_rangeSlider.backgroundColor = [UIColor redColor];
アプリケーションをビルドして実行します...何が見えますか?

何もない?いいね!
あなたが尋ねる-それについて何がそんなに素晴らしいですか?ハードワークの成果はすべてなくなりました!心配しないでください-使用したレイヤーに適用された明るい色を削除しただけです。コンポーネントはまだ配置されていますが、透明になりました。
ほとんどの開発者は、コンポーネントを特定の各アプリケーションのスタイルにカスタマイズできることを好むため、スライダーの外観を担当するいくつかのプロパティを追加します。CERangeSlider.hを開き、次のコードを追加します。
@property (nonatomic) UIColor* trackColour; @property (nonatomic) UIColor* trackHighlightColour; @property (nonatomic) UIColor* knobColour; @property (nonatomic) float curvaceousness; - (float) positionForValue:(float)value;
さまざまな色のプロパティの目的は非常に明白です。に関しては
curvaceousness
-少し後で調べてください。そして最後に
positionForValue:
。すでにこのメソッドを実装することができましたが、さまざまなレイヤーで使用できるようになりました。
色のプロパティの初期値を追加する必要があります。CERangeSlider.mを開き
initWithFrame:,
、残りの変数の初期化を担当するコードブロックの下のメソッドに次のコードを追加します。
_trackHighlightColour = [UIColor colorWithRed:0.0 green:0.45 blue:0.94 alpha:1.0]; _trackColour = [UIColor colorWithWhite:0.9 alpha:1.0]; _knobColour = [UIColor whiteColor]; _curvaceousness = 1.0; _maximumValue = 10.0; _minimumValue = 0.0;
CERangeSliderTrackLayer.mを開き、次を追加します
#import
。
#import "CERangeSlider.h"
このレイヤーには、両方のスライダーが配置されているストリップが表示されます。現時点では、
CALayer
単色のみを使用できるようにするクラスを継承しています。
ストリップを正しく描画するには、メソッドを実装し
drawInContext:
、CoreGraphics APIを使用してレンダリングする必要があります。
注:Core Graphicsの詳細についてはこのレッスンの範囲外であるため、Core Graphicsの詳細については、Core Graphics 101チュートリアルシリーズのコースをお勧めします。
次のメソッドをCERangeSliderTrackLayer.mの下に追加します
@implementation
。
- (void)drawInContext:(CGContextRef)ctx { // clip float cornerRadius = self.bounds.size.height * self.slider.curvaceousness / 2.0; UIBezierPath *switchOutline = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:cornerRadius]; CGContextAddPath(ctx, switchOutline.CGPath); CGContextClip(ctx); // 1) fill the track CGContextSetFillColorWithColor(ctx, self.slider.trackColour.CGColor); CGContextAddPath(ctx, switchOutline.CGPath); CGContextFillPath(ctx); // 2) fill the highlighed range CGContextSetFillColorWithColor(ctx, self.slider.trackHighlightColour.CGColor); float lower = [self.slider positionForValue:self.slider.lowerValue]; float upper = [self.slider positionForValue:self.slider.upperValue]; CGContextFillRect(ctx, CGRectMake(lower, 0, upper - lower, self.bounds.size.height)); // 3) add a highlight over the track CGRect highlight = CGRectMake(cornerRadius/2, self.bounds.size.height/2, self.bounds.size.width - cornerRadius, self.bounds.size.height/2); UIBezierPath *highlightPath = [UIBezierPath bezierPathWithRoundedRect:highlight cornerRadius:highlight.size.height * self.slider.curvaceousness / 2.0]; CGContextAddPath(ctx, highlightPath.CGPath); CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:1.0 alpha:0.4].CGColor); CGContextFillPath(ctx); // 4) inner shadow CGContextSetShadowWithColor(ctx, CGSizeMake(0, 2.0), 3.0, [UIColor grayColor].CGColor); CGContextAddPath(ctx, switchOutline.CGPath); CGContextSetStrokeColorWithColor(ctx, [UIColor grayColor].CGColor); CGContextStrokePath(ctx); // 5) outline the track CGContextAddPath(ctx, switchOutline.CGPath); CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor); CGContextSetLineWidth(ctx, 0.5); CGContextStrokePath(ctx); }
この画像では、すべてのコードブロックがコメントで区切られて結合されていることがわかります。

番号付きのセクションは番号付きのコメントに対応しています。
- ストリップの形状が描画された後、その背景が塗りつぶされます。
- 選択した範囲が強調表示されます。
- 追加の照明が追加され、ストリップにボリュームが追加されます。
- 影が描かれます。
- ストリップのボリュームエッジが描画されます。
すべてが段階的に分解されたので、CERangeSliderのさまざまなプロパティがその外観にどのように影響するかを確認できます。
アプリを起動します。次のよう

になります。入力したプロパティの値をいろいろ試して、スライダーの外観にどのように影響するかを確認します。まだプロパティが何をしているのか興味があるなら
curvaceousness
、今がそれを試す時です!
スライダーを描画するには、同様のアプローチを使用します。CERangeSliderKnobLayer.mを開き、次を追加します
#import
。
#import "CERangeSlider.h"
次のメソッドを追加します。
- (void)drawInContext:(CGContextRef)ctx { CGRect knobFrame = CGRectInset(self.bounds, 2.0, 2.0); UIBezierPath *knobPath = [UIBezierPath bezierPathWithRoundedRect:knobFrame cornerRadius:knobFrame.size.height * self.slider.curvaceousness / 2.0]; // 1) fill - with a subtle shadow CGContextSetShadowWithColor(ctx, CGSizeMake(0, 1), 1.0, [UIColor grayColor].CGColor); CGContextSetFillColorWithColor(ctx, self.slider.knobColour.CGColor); CGContextAddPath(ctx, knobPath.CGPath); CGContextFillPath(ctx); // 2) outline CGContextSetStrokeColorWithColor(ctx, [UIColor grayColor].CGColor); CGContextSetLineWidth(ctx, 0.5); CGContextAddPath(ctx, knobPath.CGPath); CGContextStrokePath(ctx); // 3) inner gradient CGRect rect = CGRectInset(knobFrame, 2.0, 2.0); UIBezierPath *clipPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:rect.size.height * self.slider.curvaceousness / 2.0]; CGGradientRef myGradient; CGColorSpaceRef myColorspace; size_t num_locations = 2; CGFloat locations[2] = { 0.0, 1.0 }; CGFloat components[8] = { 0.0, 0.0, 0.0 , 0.15, // Start color 0.0, 0.0, 0.0, 0.05 }; // End color myColorspace = CGColorSpaceCreateDeviceRGB(); myGradient = CGGradientCreateWithColorComponents (myColorspace, components, locations, num_locations); CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect)); CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect)); CGContextSaveGState(ctx); CGContextAddPath(ctx, clipPath .CGPath); CGContextClip(ctx); CGContextDrawLinearGradient(ctx, myGradient, startPoint, endPoint, 0); CGGradientRelease(myGradient); CGColorSpaceRelease(myColorspace); CGContextRestoreGState(ctx); // 4) highlight if (self.highlighted) { // fill CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:0.0 alpha:0.1].CGColor); CGContextAddPath(ctx, knobPath.CGPath); CGContextFillPath(ctx); } }
繰り返しますが、このメソッドの結果を分析します。
- スライダーの形状の準備ができるとすぐに、スライダーの背景が塗りつぶされます。
- さらに、スライダーの境界線が描画されます。
- 軽いグラデーションが追加されます。
- そして最後-スライダーが強調表示されている場合-スライダーが移動されている場合はそのまま-灰色になります。
アプリケーションを再度実行します

。CoreGraphicsを使用してコンポーネントをレンダリングすることは努力する価値があることがわかります。このフレームワークを使用すると、グラフィックリソースを使用するよりもはるかにカスタマイズ可能で柔軟なコントロールを作成できます。
コンポーネントプロパティの変更の処理
それで、私たちは何をしなければなりませんか?コンポーネントはかなりキャッチーに見え、外観は完全にカスタマイズ可能で、Target-Actionテンプレートをサポートしています。
スライダーのプロパティの1つが、画面に描画された後にアプリケーションコードで変更されるとどうなるかを考えてください。たとえば、スライダーの値の範囲を変更したり、スライダーのハイライトを変更したりできます。現時点では、これらの機能のサポートは実装されていません。これをコードに追加する必要があります。
外部で設定されているコンポーネントプロパティを判断するには、セッターメソッドを記述する必要があります。最初の推測は次のコードである可能性が高いです。
- (void)setTrackColour:(UIColor *)trackColour { if (_trackColour != trackColour) { _trackColour = trackColour; [_trackLayer setNeedsDisplay]; } }
プロパティが変更されると
trackColor
、このコードブロックは、スライダーバーレイヤーに更新が必要であることを通知します。しかし、スライダーAPIが8つの変数を使用するという事実を考えると、同じコードを何度も書き換えることは最良の解決策ではありません。
マクロを使用する機会のようです!CERangeSlider.mを開き、initWithFrameメソッドの上に次のコードを追加します。
#define GENERATE_SETTER(PROPERTY, TYPE, SETTER, UPDATER) \ - (void)SETTER:(TYPE)PROPERTY { \ if (_##PROPERTY != PROPERTY) { \ _##PROPERTY = PROPERTY; \ [self UPDATER]; \ } \ }
このコードブロックは、4つのパラメーターを受け取り、それらを使用して合成プロパティとそのセッターメソッドを生成するマクロを定義します。このコードの下に、別のブロックを追加します。
GENERATE_SETTER(trackHighlightColour, UIColor*, setTrackHighlightColour, redrawLayers) GENERATE_SETTER(trackColour, UIColor*, setTrackColour, redrawLayers) GENERATE_SETTER(curvaceousness, float, setCurvaceousness, redrawLayers) GENERATE_SETTER(knobColour, UIColor*, setKnobColour, redrawLayers) GENERATE_SETTER(maximumValue, float, setMaximumValue, setLayerFrames) GENERATE_SETTER(minimumValue, float, setMinimumValue, setLayerFrames) GENERATE_SETTER(lowerValue, float, setLowerValue, setLayerFrames) GENERATE_SETTER(upperValue, float, setUpperValue, setLayerFrames) - (void) redrawLayers { [_upperKnobLayer setNeedsDisplay]; [_lowerKnobLayer setNeedsDisplay]; [_trackLayer setNeedsDisplay]; }
一度にすべての変数のセッターメソッドを生成します。このメソッド
redrawLayers
は、コンポーネントの外観に関連付けられた変数、および
setLayerFrames
マークアップを担当する変数に対して呼び出されます。
スライダーがそのプロパティの変更に適切に応答するために必要なのはこれだけです。
とにかく、新しいマクロをテストし、すべてが正常に機能することを確認するために、さらにコードを追加する必要があります。CEViewController.mを開き、メソッドの最後に次のコードを追加します
viewDidLoad
。
[self performSelector:@selector(updateState) withObject:nil afterDelay:1.0f];
この行は
updateState
、2番目の遅延後にメソッドを呼び出します。このメソッドをCEViewController.mに追加します。
- (void)updateState { _rangeSlider.trackHighlightColour = [UIColor redColor]; _rangeSlider.curvaceousness = 0.0; }
このメソッドは、ストリップの色を青から赤に、スライダーの形状を正方形に変更します。プロジェクトを実行し、スライダーの形状がこれから

:

注:追加したコードは、カスタムコンポーネントの開発における最も興味深い(そして、ちなみに、開発者によって忘れられがちな)側面の1つであるテストを明確に示しています。
コントロールを開発するときは、そのプロパティのすべての可能な値と、コンポーネントの外観に対するそれらの影響-あなたの責任を確認してください。良い方法は、いくつかのボタンとスライダーを追加することです。各ボタンとスライダーは、コンポーネントのいくつかのプロパティを担当します。この方法により、コードを変更することで気を散らすことなくコントロールをテストできます。
次は?
これでスライダーは完全に機能し、あらゆるアプリケーションで使用できるようになりました!カスタムコンポーネントの主な利点の1つは、さまざまな開発者がさまざまなアプリケーションで使用できることです。
さて、スライダーの初演の準備はできていますか?
そうでもない。さらにいくつかのタスクを完了する必要があります:
ドキュメント。すべてのプログラマーのお気に入りの娯楽。 :]コードは完璧で、追加のドキュメントは必要ないと思いますが、他の開発者の意見は劇的に異なる場合があります。すべてのドキュメントを他のコード開発者に公開することをお勧めします。少なくとも、これは公開されているすべてのクラスとそのプロパティの説明です。たとえば、あなたのCERangeSliderは、以下の書類が必要です-目的変数の説明を
max
、
min
、
upper
、
lower
。
信頼性
upperValue
より大きい値を設定するとどうなりますか
maximumValue
?もちろん、あなた自身がこれを行うことは決してありません-これは少なくとも愚かです。しかし、他の誰かが試みることを保証することはできません!開発者の愚かさのレベルに関係なく、コンポーネントが常に適切に機能することを確認する必要があります。
API構造。信頼性に関する以前のポイントは、APIの構造というより広範なトピックに密接に関連しています。柔軟で直感的で信頼性の高い構造を作成することで、コンポーネントが広く使用され、普及するのに役立ちます。私の会社であるShinobiControlsでは、APIの細部について議論するのに何時間も費やすことができます! APIの構造は非常に深いトピックであり、このレッスンの範囲を超えています。このトピックに興味がある場合は、Matt Gemmellの25のAPI設計ルールを読むことをお勧めします。
コンポーネントの配布を開始できる場所はたくさんあります。以下にいくつかのオプションを示します。
- GitHubの。GitHubは、最も人気のあるオープンソースコンポーネントホスティングサイトの1つになりました。GitHubには、無数の異なるiOSコントロールがあります。GitHubの特に優れている点は、コンポーネントコードをすばやく便利に表示し、他のプロジェクトで使用して、さまざまなコメントを残すことができることです。
- CocoaPods。ユーザーが自分のコンポーネントをプロジェクトに簡単に追加できるようにするために、iOSおよびOSXの拡張マネージャーであるCocoaPodsにコンポーネントを追加できます。
- Cocoa Controls . , . GitHub, .
- Binpress . , . , — ? , , - API.
カスタムコントロールの開発に興味があり、おそらく独自のコンポーネントを作成するというアイデアに触発されたことを願っています。
プロジェクトのソースコードは、開発の段階に対応するコミットの履歴とともにGitHubで公開されます。迷子になった場合でも、最後に完了した段階から簡単に作業を続けることができます! :]
このリンクからプロジェクト全体をダウンロードできます。