開発の最近のブログ記事

本文はQiitaにあります。

本文はQiitaにあります。
Qiitaページへ

weak var delegate: DelegateProtocol?


これはよく見られる1つだけ登録可能なdelegateプロパティです。

対象のライフサイクルに干渉しない様、weak属性となっています。


複数登録可能なdelegateプロパティを実装する際、Array型を使うと上記と同じ仕様になりません。

Array型に要素を追加することにより、retain countが増加してしまい強参照となってしまう為です。


弱参照でありつつ複数登録可能なArray(Set)を作るには、NSHashTableを用います。


var delegateTable = NSHashTable.weakObjectsHashTable()


delegateTableに追加した要素は弱参照で登録され、参照先が解放された際自動的に要素は削除されます。


要素の追加は次の通りです。


delegateTable.addObject(ObjA())


NSHashTable内の要素はAnyObject型で管理される為、Array型と違いあらかじめ要素の型を指定することはできません。

登録されている各要素のdelegateメソッドを正しく実行する為には、次の手順を実行します。


for case let target as DelegateProtocol in self.delegateTable.allObjects {

target.sampleFunc()

}

動画や音声のエクスポートは非常にコストの高い処理となるため、通常UIに負担をかけないためサブスレッドで処理します。
1つのエクスポートであれば単純にCompletionHandlerがコールされるまで待てば良いのですが、複数のエクスポートを行う場合少々難しいことになります。

exportAsynchronouslyWithCompletionHandlerは複数同時に動作させることができません。
 ※2つのexportAsynchronouslyWithCompletionHandlerを同時に動かした場合、1つめの処理が瞬間的に失敗となります。
この仕様を満たすため、複数のエクスポートを行う際にexportAsynchronouslyWithCompletionHandlerの逐次処理が必要となります。

この処理はディスパッチセマフォを用いるとシンプルに実装可能です。
最大の注意点としてセマフォにより該当スレッドを待機状態にしますので、間違ってメインスレッドを止めてしまわないようにする必要があります。

// スレッド待機状態になっても問題ないよう、サブスレッド上で処理する

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

for (NSString* filePath in self.filePaths) {

// AVURLAsset生成

AVURLAsset* asset = [AVURLAsset assetWithURL:[NSURL filePath]];

// エクスポート

AVAssetExportSession *exportSession = [[[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetPassthrough] autorelease];

// ディスパッチセマフォを宣言する。初期値は0

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

[exportSession exportAsynchronouslyWithCompletionHandler:^(void) {

switch (exportSession.status) {

case AVAssetExportSessionStatusCompleted:

// エクスポート成功

// セマフォによるスレッド停止を終了する

dispatch_semaphore_signal(semaphore);

break;

case AVAssetExportSessionStatusFailed:

// エクスポート失敗

// セマフォによるスレッド停止を終了する

dispatch_semaphore_signal(semaphore);

break;

case AVAssetExportSessionStatusCancelled:

// エクスポートキャンセル

// セマフォによるスレッド停止を終了する

dispatch_semaphore_signal(semaphore);

break;

default:

break;

}

}];

// セマフォによるスレッド停止

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_release(semaphore);

}

}

});

複数行対応のUITableViewCellを使用するときなど、自動的にセル高さを決定してくれるUITableViewAutomaticDimensionは非常に便利です。
便利なのですが画面回転などによりUITableViewの(内部Cellの)frameが変わってしまう場合、正しい高さを求めてくれなくなってしまいます。
UITableView.frameの変更に合わせて次のおまじないを唱えることで、簡単に対応することが可能です。

[UITableView beginUpdates];

[UITableView endUpdates];


NSMutableArrayに標準で用意されていない、項目の移動メソッドを拡張で実装します。

[NSMutableArray+MoveObject.h]

@interface NSMutableArray (MoveObject)


- (void)moveObjectFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex;


@end


[NSMutableArray+MoveObject.m]

@implementation NSMutableArray (MoveObject)


- (void)moveObjectFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex

{

if (fromIndex > toIndex) {

id dummy = [[[NSObject alloc] init] autorelease];

id obj = [self objectAtIndex:fromIndex];

[self insertObject:dummy atIndex:fromIndex];

[self removeObject:obj];

[self insertObject:obj atIndex:toIndex];

[self removeObject:dummy];

}

else if (toIndex > fromIndex) {

id obj = [self objectAtIndex:fromIndex];

[self removeObject:obj];

[self insertObject:obj atIndex:toIndex];

}

}


@end


使用例は次の通りです。
UITableViewでCellの移動を行い、データソースであるNSMutableArrayを適正な順序に並び替える処理を想定しています。

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath

{

[self.dataList moveObjectFromIndex:sourceIndexPath.row toIndex:destinationIndexPath.row];

}



通常UIViewに枠線を付ける場合、次のプロパティを設定するだけで実現可能です。
UIView.layer.borderColor = [UIColor whiteColor].CGColor;
UIView.layer.borderWidth = 1.0;
とても簡単ですが、このプロパティを使うと必ずUIViewの縁4辺に枠線が引かれます。
例えば左側1辺のみに枠線を付けるなどどいう事はできません。

今回は縁4辺それぞれに対して枠線の表示/非表示を切り替え可能なUIViewを設計します。

BorderExView.h

// 枠強調オプション

typedef NS_OPTIONS(NSUInteger, eBorderEmphasisOption)

{

BorderEmphasisOption_None = 0,

BorderEmphasisOption_Top = 1 << 0,

BorderEmphasisOption_Bottom = 1 << 1,

BorderEmphasisOption_Left = 1 << 2,

BorderEmphasisOption_Right = 1 << 3,

BorderEmphasisOption_All = NSUIntegerMax

};


@interface BorderExView : UIView


@property (nonatomic) eBorderEmphasisOption borderEmphasis;

@property (nonatomic, strong) UIColor* borderColor;

@property (nonatomic) float borderWidth;


@end


BorderExView.m

@interface BorderExView ()

{

eBorderEmphasisOption _borderEmphasis;

}


@end


@implementation BorderExView


@synthesize borderColor = _borderColor;

@synthesize borderWidth = _borderWidth;


@dynamic borderEmphasis;

- (eBorderEmphasisOption)borderEmphasis

{

return _borderEmphasis;

}

- (void)setBorderEmphasis:(eBorderEmphasisOption)borderEmphasis

{

_borderEmphasis = borderEmphasis;

[self setNeedsDisplay];

}


- (id)initWithFrame:(CGRect)frame

{

if (self = [super initWithFrame:frame]) {

_borderEmphasis = BorderEmphasisOption_None;

self.borderColor = [UIColor blackColor];

self.borderWidth = 1.0;

}

return self;

}


- (void)drawRect:(CGRect)rect

{

CGContextRef ctx = UIGraphicsGetCurrentContext();

CGContextSetLineWidth(ctx, self.borderWidth);

CGContextSetStrokeColorWithColor(ctx, self.borderColor.CGColor);

if (self.borderEmphasis & BorderEmphasisOption_Top) {

CGContextMoveToPoint(ctx, 0, 0);

CGContextAddLineToPoint(ctx, self.frame.size.width, 0);

CGContextStrokePath(ctx);

}

if (self.borderEmphasis & BorderEmphasisOption_Bottom) {

CGContextMoveToPoint(ctx, 0, self.frame.size.height);

CGContextAddLineToPoint(ctx, self.frame.size.width, self.frame.size.height);

CGContextStrokePath(ctx);

}

if (self.borderEmphasis & BorderEmphasisOption_Left) {

CGContextMoveToPoint(ctx, 0, 0);

CGContextAddLineToPoint(ctx, 0, self.frame.size.height);

CGContextStrokePath(ctx);

}

if (self.borderEmphasis & BorderEmphasisOption_Right) {

CGContextMoveToPoint(ctx, self.frame.size.width, 0);

CGContextAddLineToPoint(ctx, self.frame.size.width, self.frame.size.height);

CGContextStrokePath(ctx);

}

}


@end


使い方は次の通りです。

BorderExView* view = [[BorderExView alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];

[self.view addSubview:view];

view.backgroundColor = [UIColor yellowColor];

view.borderColor = [UIColor grayColor];

view.borderWidth = 5.0;

view.borderEmphasis = BorderEmphasisOption_Left | BorderEmphasisOption_Top;



UITapGestureRecognizerを登録したviewに対してUITableViewをaddSubviewした場合、セルをタップしてもUITapGestureRecognizerが優先されてしまいdidSelectRowAtIndexPathがコールされなくなってしまいます。
このような競合を解消するにはUIGestureRecognizerの次のプロパティを設定します。

UITapGestureRecognizer* viewTap = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(view_Tap:)] autorelease];

viewTap.cancelsTouchesInView = NO;

[self.view addGestureRecognizer:viewTap];

fontWithNameメソッドを使う際に目的のフォント名が分からなかった事はないでしょうか。

今回は使用できるフォント名一覧をログに出力する方法を紹介します。


+ (void)fontNameLog

{

for (NSString* familyName in [UIFont familyNames]) {

for (NSString* fontName in [UIFont fontNamesForFamilyName:familyName]) {

NSLog(@"%@", fontName);

}

}

}

bringSubviewToFrontなどのメソッドでsubviewの表示順を操作することは可能ですが、subviewの数や配置の複雑度が増してくると処理が煩雑になりがちです。


例えば

 ・背景(2views)

 ・情報表示(5views)

 UIボタン群(5views~10views)

 ・広告を上下に1つずつ配置(2views?)

という構造を考えてみましょう。


背景は常に一番下に配置されますが、状況によりviewの表示順を入れ替える必要があります。

情報表示画面は常に最重要な画面を前面に表示する必要があり、頻繁に表示順の入れ替えが行われます。

UIボタン群は最前面に表示している情報表示画面を操作するためのもの、また情報表示画面を切り替えるためのものがあり、viewの数や表示順は状況により変わります。

UIボタンは常に情報表示画面より上に配置し、隠れないことが求められます。

広告はUIボタンを隠さない位置に表示され、常に最前面に配置します。サードパーティ製の広告SDKを用いるため、どのような実装がされているかは不明です。


このような複雑度を持つアプリの場合、もはやbringSubviewToFrontでなんとかしようとするのは現実的ではありません。

insertSubviewexchangeSubviewAtIndexを使うことで多少コード量は減らすことができますが、それでも煩雑であるという印象は解消できないでしょう。

ある情報表示画面を前面に配置したい場合、次の条件でsubviewsから挿入箇所を検索する必要があります。

 ・情報表示画面群の最前面

 ・UIボタン群の1つ背面である

UIボタン群の中で常に最背面にあるボタンが固定なら楽かもしれませんんが、UIボタンは状況により変わります。

もしかすると検索対象としたUIボタンインスタンスそのものがsubviewsに含まれていないかもしれません。


背景の入れ替えについても安易にsendSubviewToBackを用いるのは危険です。

なぜなら今後の仕様追加により背景viewが追加された場合に不具合の原因となる可能性が高くなるからです。


UIボタンの入れ替えも大変です。

広告のview構成が不明であるため、広告viewの1つ背面にinsertするのは危険です。(広告が読み込まれたタイミングでview構成が変わり、もしかしたらviewのインスタンスすら変わっているかもしれません)

そのため現在前面に表示されている情報表示画面を検索する必要が出てきます。


仕様変更も視野に入れて設計することを考えただけでウンザリしてきたのではないでしょうか。


今回はこのような複雑なsubview構成に対応するため、階層構造でsubviewを管理できるUIViewControllerを設計します。

階層は好きな数を自由に追加でき、階層に追加したsubviewはその階層内でのみ表示順の入れ替えが可能です。

たとえbringSubviewToFrontを使ったとしても、その階層より上の階層に存在するsubviewを上書きすることはありません。


先ほどの例を階層構造を用いて実装した場合のイメージを下図に示します。


Hierarchy.png


それぞれのsubviewは対応した階層にaddされます。

先ほどと同じくある情報表示画面を前面に配置したい場合の処理を考えてみましょう。

情報表示階層にaddされている情報表示画面はその上階層であるUIボタン階層を上書きすることはできません。

つまり情報表示画面を前面に表示したいのであれば、単純にbringSubviewToFrontを使うだけで実装することができます。


背景も前面に出したいsubviewbringSubviewToFrontするだけです。

bringSubviewToFrontによる実装であれば、今後背景viewが増えたとしても不具合は発生しません。


UIボタンの入れ変えはどうでしょうか。

UIボタン階層に存在するsubviewを一掃し、新しいUIボタンをUIボタン階層にaddするだけで完了です。


広告がどのようなview構造で実装されていたとしても、それは広告階層の中の問題です。

他の階層が気にする必要はなくなり、実装におけるリスクも随分軽減されます。


階層はUIViewで実装しますが、そのままUIViewを使ってしまうとタッチイベントがブロックされてしまいます。

UIボタン階層の上に広告階層が存在するため、すべてのタッチイベントは広告階層にブロックされUIボタンを押すことはできません。

この問題を解決するため、一切のタッチイベントをスルーするUIView(TouchTransmissionView)を実装します。


TouchTransmissionView.h

@interface TouchTransmissionView : UIView


@property (nonatomic, readonly) NSUInteger hierarchy;


- (id)initWithFrame:(CGRect)frame hierarchy:(NSUInteger)hierarchy;

- (NSComparisonResult)compareHierarchyAscending:(TouchTransmissionView*)view;


@end


TouchTransmissionView.m

@interface TouchTransmissionView ()

{

NSUInteger _hierarchy;

}


@end


@implementation TouchTransmissionView


@synthesize hierarchy = _hierarchy;


- (id)initWithFrame:(CGRect)frame hierarchy:(NSUInteger)hierarchy

{

    self = [super initWithFrame:frame];

    if (self) {

self->_hierarchy = hierarchy;

    }

    return self;

}


- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event

{

UIView* eventView = nil;

UIView *hitView = [super hitTest:point withEvent:event];

if (hitView != self) eventView = hitView;

return eventView;

}


- (NSComparisonResult)compareHierarchyAscending:(TouchTransmissionView*)view

{

if (self.hierarchy > view.hierarchy) return NSOrderedDescending;

else if (self.hierarchy < view.hierarchy) return NSOrderedAscending;

else return NSOrderedSame;

}


@end


subviewを階層構造で管理するためのUIViewController(HierarchyViewController)を実装します。


HierarchyViewController.h

@interface HierarchyViewController : UIViewController


- (void)addSubview:(UIView*)view hierarchy:(NSUInteger)hierarchy;


- (void)bringSubviewToFront:(UIView*)view;


- (NSArray*)subviews;

- (NSArray*)subviewsOfHierarchy:(NSUInteger)hierarchy;


@end


HierarchyViewController.m

#import "TouchTransmissionView.h"


@interface HierarchyViewController ()


- (TouchTransmissionView*)addLayerViewWithHierarchy:(NSUInteger)hierarchy;


@property (nonatomic, retain) NSMutableArray* layerViewList;


@end


@implementation HierarchyViewController


@synthesize layerViewList;


- (TouchTransmissionView*)addLayerViewWithHierarchy:(NSUInteger)hierarchy

{

TouchTransmissionView* newLayerView = [[[TouchTransmissionView alloc] initWithFrame:self.view.bounds hierarchy:hierarchy] autorelease];

newLayerView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin |

UIViewAutoresizingFlexibleLeftMargin |

UIViewAutoresizingFlexibleRightMargin |

UIViewAutoresizingFlexibleTopMargin |

UIViewAutoresizingFlexibleHeight |

UIViewAutoresizingFlexibleWidth ;

[self.view addSubview:newLayerView];

[self.layerViewList addObject:newLayerView];

NSArray* sortedLayerViewList = [self.layerViewList sortedArrayUsingSelector:@selector(compareHierarchyAscending:)];

for (UIView* view in sortedLayerViewList) {

[self.view bringSubviewToFront:view];

}

self.layerViewList = [[sortedLayerViewList mutableCopy] autorelease];

return newLayerView;

}


#pragma mark - public

- (void)addSubview:(UIView*)view hierarchy:(NSUInteger)hierarchy

{

if (self.layerViewList == nil) {

self.layerViewList = [NSMutableArray array];

}

UIView* targetLayerView = nil;

for (TouchTransmissionView* layerView in self.layerViewList) {

if (layerView.hierarchy == hierarchy) {

targetLayerView = layerView;

break;

}

}

if (targetLayerView == nil) {

targetLayerView = [self addLayerViewWithHierarchy:hierarchy];

}

[targetLayerView addSubview:view];

}


- (NSArray*)subviewsOfHierarchy:(NSUInteger)hierarchy

{

TouchTransmissionView* targetLayerView = nil;

for (TouchTransmissionView* layerView in self.layerViewList) {

if (layerView.hierarchy == hierarchy) {

targetLayerView = layerView;

break;

}

}

return targetLayerView.subviews;

}


- (NSArray*)subviews

{

NSMutableArray* subViewList = [NSMutableArray array];

for (UIView* layerView in self.layerViewList) {

[subViewList addObjectsFromArray:layerView.subviews];

}

return subViewList;

}


- (void)bringSubviewToFront:(UIView*)view

{

for (UIView* layerView in self.layerViewList) {

for (UIView* subView in layerView.subviews) {

if (subView == view) {

[layerView bringSubviewToFront:subView];

break;

}

}

}

}


@end



最後にサンプルとしてHierarchyViewControllerを継承したSampleViewControllerを実装し、動作を確認してみましょう。


SampleViewController.h

@interface SampleViewController : HierarchyViewController

@end


SampleViewController.m

typedef enum {

ViewHierarchy_BackGround = 0,

ViewHierarchy_Back,

ViewHierarchy_Middle,

ViewHierarchy_Front,

}eViewHierarchy;


@interface SampleViewController ()


- (void)handleBackgroundView_Tap:(UITapGestureRecognizer*)sender;


@end


@implementation SampleViewController


- (void)viewDidLoad {

    [super viewDidLoad];

UIView* view2 = [[[UIView alloc] initWithFrame:CGRectMake(30, 30, 200, 200)] autorelease];

view2.backgroundColor = [UIColor blueColor];

[self addSubview:view2 hierarchy:ViewHierarchy_Front];

UIView* view1 = [[[UIView alloc] initWithFrame:CGRectMake(90, 90, 200, 200)] autorelease];

view1.backgroundColor = [UIColor redColor];

[self addSubview:view1 hierarchy:ViewHierarchy_Back];

UIView* backgroundView = [[[UIView alloc] initWithFrame:self.view.bounds] autorelease];

backgroundView.backgroundColor = [UIColor greenColor];

[self addSubview:backgroundView hierarchy:ViewHierarchy_BackGround];

UIView* middleView1 = [[[UIView alloc] initWithFrame:CGRectMake(60, 130, 250, 40)] autorelease];

middleView1.backgroundColor = [UIColor yellowColor];

[self addSubview:middleView1 hierarchy:ViewHierarchy_Middle];

UIView* middleView2 = [[[UIView alloc] initWithFrame:CGRectMake(70, 150, 250, 40)] autorelease];

middleView2.backgroundColor = [UIColor grayColor];

[self addSubview:middleView2 hierarchy:ViewHierarchy_Middle];

[backgroundView addGestureRecognizer:[[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBackgroundView_Tap:)] autorelease]];

}


- (void)handleBackgroundView_Tap:(UITapGestureRecognizer*)sender

{

NSArray* subViews = [self subviewsOfHierarchy:ViewHierarchy_Middle];

UIView* backView = [subViews firstObject];

[self bringSubviewToFront:backView];

}


@end


2014-12-02 04.57.22.png

各subviewを配置するための階層はeViewHierarchyで管理しています。

subviewは最背面から順にaddSubviewをしているわけではなく、まったくのチグハグです。

しかし階層により表示順が管理されているため、正しい表示順が保持されます。

背景view(緑色のview)をタップすると、Middle階層に存在するviewの表示順が入れ替わります。

表示順の入れ替えにbringSubviewToFrontを用いていますが、階層内の最前面に移動するだけで決してFront階層を上書きすることはありません。


あまり多くの階層を作ってしまうとパフォーマンスの悪影響が考えられますので、その点には注意してください。

Twitterボタン
Twitterブログパーツ