iOS用のコンポーネントの作成方法

画像



コントロールは、アプリケーションの最も重要なコンポーネントの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つあります。

  1. 画像コンポーネントの一部として使用し、
  2. レイヤーとCore Graphicsの組み合わせを使用します。


各メソッドには長所と短所があります。







アプリケーションコードは、 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.minitWithFrame:



見つけ、初期化コード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; }
      
      





このコードをコメントごとに分析してみましょう。



  1. まず、 delta



    -指を動かしたピクセル数を計算します。 次に、コンポーネントの最小値と最大値に応じてそれらを変換します。
  2. ここでは、ユーザーがスライダーを動かした場所に応じて、上限と下限を変更します。 MIN/MAX



    より読みやすいBOUND



    マクロを使用していることに注意してください。
  3. このコードブロックは、 disabledActions



    フラグをCATransaction



    設定しdisabledActions



    。 これにより、各レイヤーの境界の変更がすぐに適用され、アニメーション化されないことを確認できます。 最後に、 setLayerFrames



    メソッドがsetLayerFrames



    、スライダーを目的の場所に移動します。




スライダーの動きを実装しましたが、それでもコンポーネントとの対話の終了を処理する必要があります。 CERangeSlider.mに次のメソッドを追加します。



 - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { _lowerKnobLayer.highlighted = _upperKnobLayer.highlighted = NO; [_lowerKnobLayer setNeedsDisplay]; [_upperKnobLayer setNeedsDisplay]; }
      
      





このコードは、両方のスライダーを非アクティブ状態に戻します。 プロジェクトを起動して、新しいスライダーで少し遊んでください! スクリーンショットと同じように見えるはずです:



画像



コンポーネントの境界を越えて指を動かし、メソッドの実行を中断せずに境界に戻ることができることに気付くかもしれません。 これは、小さな画面と低精度のスタイラスを備えたデバイスの使いやすさの非常に重要な機能です(理解できない場合は、指について話します)。 :]



変更通知



したがって、ユーザーが範囲の上限と下限を設定できるインタラクティブなコントロールがあります。 しかし、アプリケーションで使用できるように、これらの変更を残りのコードに関連付ける方法はありますか? NSNotificationKey-Value-Observing(KVO) 、委任テンプレート、Target-Actionテンプレートなど、変更の通知に使用できる多くの異なるオプションがあります。



UIKit



のコンポーネントを見ると、 NSNotificationまたはKVOを使用していないことがわかりますUIKit



との互換性のために、これら2つのオプションを放棄する必要があります。 UIKit



、他の2つのテンプレート-委任とTarget- UIKit



が非常によく使用されUIKit







両方のパターンの詳細な分析を行います。







2つのパターンの主な違いは次のとおりです。





コンポーネントには、通知が必要な可能性のある状態やイベントが多くありません。役割を果たすのは、スライダーの最大値と最小値だけです。



この状況では、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); }
      
      





この画像では、すべてのコードブロックがコメントで区切られて結合されていることがわかります。



画像



番号付きのセクションは番号付きのコメントに対応しています。



  1. ストリップの形状が描画された後、その背景が塗りつぶされます。
  2. 選択した範囲が強調表示されます。
  3. 追加の照明が追加され、ストリップにボリュームが追加されます。
  4. 影が描かれます。
  5. ストリップのボリュームエッジが描画されます。




すべてが段階的に分解されたので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); } }
      
      





繰り返しますが、このメソッドの結果を分析します。



  1. スライダーの形状の準備ができるとすぐに、スライダーの背景が塗りつぶされます。
  2. さらに、スライダーの境界線が描画されます。
  3. 軽いグラデーションが追加されます。
  4. そして最後-スライダーが強調表示されている場合-スライダーが移動されている場合はそのまま-灰色になります。




アプリケーションを再度実行します



画像



。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公開されます迷子になった場合でも、最後に完了した段階から簡単に作業を続けることができます! :]



このリンクからプロジェクト全体をダウンロードできます



All Articles