忍者ブログ
  • 2024.10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 2024.12
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

【2024/11/24 13:23 】 |
adcrops導入
adcropsを導入したときの話。
かなり変な癖があって行儀が悪いSDKだったので、今後のために書いておく。

公式

今回実装した広告は、全部ウォール型。正しい名前は確認していないが
・リストウォール
・バナーウォール
・スロットウォール


◯広告設定
ID的なものは2つ。
サイトIDとサイトキー。
1つのアプリに複数種類の広告を置いても、このIDは増えない。
どの種類の広告もこの2つのIDを用いて表示する。

ここが他の広告媒体と違うので注意。
1つで何でも表示できるので便利…といえないこともない。
でも表示箇所ごとに広告を管理(フィルタリングなど)することができないのでどうなんだ。


◯SDKインストール
まずはSDKをインストールする。
header,resourceをまるっとプロジェクトに追加


リンカオプションに
-ObjC
を追加する。


なぜこのオプションが必要か。

このSDKは、categoryファイルを含んでいる。

そもそもUNIXでC/C++をビルドしたとき、
オブジェクトファイル(ソースファイルから作られるバイナリファイル)内に見つからないシンボルがあるとき、リンカがそれを解決する。つまり、そのシンボル(メソッド)を含む別のオブジェクトファイルをインクルードする。

だが、ObjCのstatic library の categoryはそうならない。
未定義シンボルは未定義のままで、実行時に"selector not recognized"例外が発生してしまう。

そこで-ObjCオプションを付けると、すべてのcategoryのメソッドが実行可能ファイルに含まれるようになる。
ただし、不要なメソッドも含んでしまうので、実行可能ファイルが膨大になることがある。(だからデフォルトオプションになっていない)
また、コード中にC/C++で書かれている部分がコンパイルエラーになることがある。

特にcocos2dのライブラリ内でよく起きるのだが、
その場合は、そのファイル全体をコメントアウトしてしまうのが早い。
(大抵ムービーとかゲームコントローラとかあんまり使わないところでしか起きないからだ)


公式のQ&A


◯サンプル
さて、この状態ではまだ広告は出ない。
adcropsのSDKはかなり基本的なところしか対応していなくて、
ウォールを表示しようとしたら自分でテーブルビューとか作らないといけない。

これを自分でやるとなると不親切極まりないと憤慨するところだが、
サンプルコードがいろいろ付いている。


サンプルコードをみると、SDKの他に
Chk***.h/m/mm
というファイルがたくさん入っている。
これらがテーブルビューとかを用意している。いわばヘルパークラスで、アプリ側とSDKを繋ぐ位置にいる。


これらのヘルパーファイルをコピーして、自分のプロジェクトに追加する。

で、アプリ側からはこれらのヘルパーを呼び出す形にする。
複数の広告種類を出すなら、それぞれのサンプルからヘルパーをコピーしてくる。


これで動くようになる。
初めからこのヘルパーもSDKに入れておけ、というところだ。


PR
【2015/01/07 16:48 】 | iPhone | 有り難いご意見(0)
cocos2d-xのクラスを継承する
cocos2d-xで、LayerとかSpriteを継承して新しいクラスを作る場合がある。

たとえば画面を作りたいなら、Layerを継承すればいい。cocosプロジェクトを作ったときもコード中にHelloWorldSceneがそうして作られているし、それを真似すればいい。

基本的にゲーム中のオブジェクトなら、Layerを継承して作るのが良さそうだ。
前の記事も参照。

その他にも、たとえばキャラクターなら、Spriteを継承して作るなど、内容に応じていろいろなクラスを継承して作るのが良い。

以下はLayerを継承して作った例

class MyClass : public cocos2d::Layer{
//create() メソッド作成
CREATE_FUNC(MyClass);

//init()作成
bool init() override{
cocos2d::Layer::init(); //親クラスのinitを呼び出す。忘れずに!
//自前の初期化処理
//
}

//その他の定義
//
};

//生成
auto obj = MyClass::create(); //参照カウンタが1になる

//破棄
obj->release(); //参照カウンタを1減らす。0になるので後で自動的に破棄される。
obj = nullptr; //これはC++の作法通りしておいたほうがよい






こうすると、コード中にnewもdeleteも現れなくなる。
cocos2d-xの作法にあった形になる。

static関数のcreate() は、CREATE_FUNCマクロによって生成されている。
CREATE_FUNCマクロを使うためには、
・引数無しコンストラクタがあること
・引数無しで戻り値がboolのinit()という関数を持っていること
・そのクラスがRefを継承していること(より正確には、autorelease()という関数を持っていること)
が必要。


#define CREATE_FUNC(__TYPE__) \
static __TYPE__* create() \
{ \
__TYPE__ *pRet = new __TYPE__(); \
if (pRet && pRet->init()) \
{ \
pRet->autorelease(); \
return pRet; \
} \
else \
{ \
delete pRet; \
pRet = NULL; \
return NULL; \
} \
}







純粋なデータ構造を扱うクラスだから、SceneもNodeも継承する必要が無い、と思っても
「メモリ管理の方法は統一しておくべき」という観点から、最低でもRefは継承するのがよい。
Refは自動でメモリ管理をしてくれるクラス。使用されなくなったらreleaseすれば、裏でPoolManagerが自動的に破棄する。


<<注意!>>

init()関数はNodeクラスに仮想関数として定義がある。
オーバーライドして、さらに親クラスのinit()を呼ぶのを忘れないように
Refクラスを直接継承する場合は、オーバーライドではなく新規の定義になる(Refクラスにinit()がないので)


Spriteクラスを継承して、親クラスのinit()の呼び出しを忘れた場合は、大抵バクる。
(glProgramStateがセットされないというアサートが出る。)





もうひとつ。
create()メソッドは継承はしないが、
元々create()メソッドを持っているクラスから継承した場合、自クラスで定義しなくても呼び出せるわけだ。
例えばSpriteクラスを継承してMySpriteクラスを作った場合

auto sprite = MySprite::create();
とすると、
Sprite::create()が呼び出されることになる。

ただしその場合、戻り値型はSprite*なので、

MySprite* sprite = MySprite::create();
はコンパイルエラーになる。
ちゃんとMySpriteでcreate()を定義する必要がある(CREATE_FUNC(MySprite); と書くだけ)


Spriteの場合、create()だけでなく
create(const std::string& filename)
create(const std::string& filename, const Rect& rect)
createWithTexture(Texture2D *texture)
createWithTexture(Texture2D *texture, const Rect& rect, bool rotated=false)
createWithSpriteFrame(SpriteFrame *spriteFrame)
createWithSpriteFrameName(const std::string& spriteFrameName)
と言う具合に多くのcreate()系のstaticメソッドがある。

本来であれば、MySpriteクラスにすべての同名関数を用意すべきである。
ただし、戻り値がMySprite*でなくSprite*なので、コンパイルエラーで気付けるといえば気付ける。
【2014/12/24 11:37 】 | cocos2d-x | 有り難いご意見(0)
cocos2d-x 3.0 の主なクラス

Ref 参照カウンタを持つ
+-- Node   親子関係を持つ
+-- Scene  Nodeとほぼ同じ。違いはアンカーポイントのデフォルト位置が画面中心になっていることだけ(Nodeは左下がデフォルト位置)
+-- Layer   タッチイベントを扱う
+-- LayerColor 背景に色を塗れる
+-- LayerGradient 背景にグラデーションできる
+-- ScrollView  スクロールビュー
+-- TableView   テーブルビュー
+-- Sprite   スプライトを表示する
+-- SpriteBatchNode  スプライトをまとめて表示するためのクラス(別記事に書く)
+-- Label  文字列表示用クラス。zyngaが作った?らしい
+-- LabelTTF  コンフィグ設定付きのラベル
【2014/12/09 19:14 】 | cocos2d-x | 有り難いご意見(0)
cocos2d-xの参照カウンタについて
cocos2d-xにおいては、すべてのクラスは
cocos2d::Ref
の子孫クラスである。
Refクラスは、参照カウンタを扱っている。

Refクラスのautorelease()メソッドを呼んでおくと、
Refとその子孫クラスのインスタンスを生成したとき、破棄処理を自分でしなくても
ゲームのメインループが来たときに自動で破棄してくれる。

CCRef.cpp

Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}



CCDirector.cpp

void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (! _invalid)
{
drawScene();

// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
}



CCAutoReleasePool.cpp

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = true;
#endif
for (const auto &obj : _managedObjectArray)
{
obj->release();
}
_managedObjectArray.clear();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = false;
#endif
}



CCRef.cpp

void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0");
--_referenceCount;

if (_referenceCount == 0)
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
auto poolManager = PoolManager::getInstance();
if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
{
// Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.
// This happens when 'autorelease/release' were not used in pairs with 'new/retain'.
//
// Wrong usage (1):
//
// auto obj = Node::create(); // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool.
// obj->autorelease(); // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first.
//
// Wrong usage (2):
//
// auto obj = Node::create();
// obj->release(); // Wrong: obj is an autorelease Ref, it will be released when clearing current pool.
//
// Correct usage (1):
//
// auto obj = Node::create();
// |- new Node(); // `new` is the pair of the `autorelease` of next line
// |- autorelease(); // The pair of `new Node`.
//
// obj->retain();
// obj->autorelease(); // This `autorelease` is the pair of `retain` of previous line.
//
// Correct usage (2):
//
// auto obj = Node::create();
// obj->retain();
// obj->release(); // This `release` is the pair of `retain` of previous line.
CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
}
#endif

#if CC_USE_MEM_LEAK_DETECTION
untrackRef(this);
#endif
delete this;
}
}



CCAutoReleasePool.cppを見るとわかるが、_managedObjectArrayに格納されているすべてのobjectに対してrelease()を実行している。
ずいぶん潔いやり方だ。生成しても放っておいてよい、というか放っておかなくてはいけない。自分でrelease()とか呼ぶと却ってバグる。
その使い方についてはCCRef.cppのrelase()メソッド内に詳しく書いてある。


一言で言うと
new/retain() と autorelease()/release() を対で呼び出さなければならない。
ということだ。

生成時にnewが呼ばれる。
cocos的には、Node::create()やSprite::create()などのように、static関数のcreate()メソッドが相当。create()の中でnewをしている。
例としてNode::create()を見てみると


Node * Node::create()
{
Node * ret = new Node();
if (ret && ret->init())
{
ret->autorelease();
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}


となっている。
createの中で new と autorelease() を両方やっている。

それ以外にも、retain()を呼ぶならrelease()かautorelease()をどこかで呼んでおく。
対で呼び出されるようになっていれば、メモリリークは起こらない。

autorelease()は複数回呼び出しても問題ないようだ。autoreleasePoolに2回登録されるので、2回releaseが走る。


なお、Nodeインスタンスの場合、別のNodeの子になる(addChild)と参照カウンタが1増える。

parentNode->addChild(childNode); //childNodeの参照カウンタが1増える



これは、parentNodeのメンバ変数
Vector<Node*> _children;
に追加されるから。
cocos2d-xのVectorクラスは、pushBack(またはinsert,replace)したときにretain()を呼ぶようになっている。
このため、addChildされた子ノードは破棄されずに残る。


【2014/12/09 13:13 】 | cocos2d-x | 有り難いご意見(0)
cocos2d-xのアニメーション
cocosでアニメーション作成する

以下は、立っている絵から歩きアニメーションを再生するところ。

standSprite = Sprite::create("stand.png");

Animation* walkAnimation;
walkAnimation = Animation::create();
walkAnimation->addSpriteFrame(cache->getSpriteFrameByName("walk_1.png"));
walkAnimation->addSpriteFrame(cache->getSpriteFrameByName("walk_2.png"));
walkAnimation->setDelayPerUnit(0.5f);
walkAnimation->setRestoreOriginalFrame(true); //(A)

Animate* animate = Animate::create(walkAnimation);
standSprite->runAction(RepeatForever::create(animate));



standSpriteは、アニメーションを再生しなければ単なるスプライト。
アニメーション(この場合スプライトアニメーション)を再生することで、立ち絵のスプライトが歩きスプライトのアニメーションに変わる。
setRestoreOriginalFrame(true)
を指定していると、アニメーションが終了したときに元のスプライト(立ち絵の表示)に戻る。ただしここではRepeatForeverアニメーションなので、終了することがないので設定が無意味だが。






【2014/12/09 12:33 】 | cocos2d-x | 有り難いご意見(0)
Xcodeでメモリ周りのデバッグ
Xcodeでデバッグする便利機能いくつか

【メモリのアクセス違反を検知】
Guard Malloc という機能を使う。
これはXcodeにデフォルトで入っている。
使い方は以下のとおり。スキームの設定をする。



参考

その他の機能の説明
Enable Scribble. Fill allocated memory with 0xAA and deallocated memory with 0x55.
Enable Guard Edges. Add guard pages before and after large allocations.
Enable Guard Malloc. Use libgmalloc to catch common memory problems such as buffer overruns and use-after-free.
引用元

zombie objectとは、解放されたメモリをNSZombieObjectにリプレースする機能。
解放済みメモリにアクセス違反したとき、どこにアクセスしたのかなどの情報が分かりやすくなる。
NSZombieObjectの説明


さらにいろんな便利機能
参考
うう〜ん、べんり!


なお、注意点として、Guard Mallocを有効にした状態で実機実行はできない。
以下のエラーが出る。

dyld: could not load inserted library '/usr/lib/libgmalloc.dylib' because image not found


面倒だが、シミュレータのときと実機のときで、スキームから設定を変更しよう。
参考
【2014/12/08 10:45 】 | iPhone | 有り難いご意見(0)
iOS通知のカスタムアクション実装
公式ドキュメント

を参照。
26ページから(特に28ページ冒頭)を要チェック。

引用----------

リスト 2-7 通知アクションを定義するコード例
UIMutableUserNotificationAction *acceptAction =
[[UIMutableUserNotificationAction alloc] init];
// Define an ID string to be passed back to your app when you handle the action
acceptAction.identifier = @"ACCEPT_IDENTIFIER";
// Localized string displayed in the action button
acceptAction.title = @"Accept";
// If you need to show UI, choose foreground
acceptAction.activationMode = UIUserNotificationActivationModeBackground;
// Destructive actions display in red
acceptAction.destructive = NO;
// Set whether the action requires the user to authenticate
acceptAction.authenticationRequired = NO;



リスト 2-8 アクションをカテゴリにまとめるコード例

// First create the category
UIMutableUserNotificationCategory *inviteCategory = [[UIMutableUserNotificationCategory alloc] init];
// Identifier to include in your push payload and local notification
inviteCategory.identifier = @"INVITE_CATEGORY";
// Add the actions to the category and set the action context
[inviteCategory setActions:@[acceptAction, maybeAction, declineAction] forContext:UIUserNotificationActionContextDefault];
// Set the actions to present in a minimal context
[inviteCategory setActions:@[acceptAction, declineAction]
forContext:UIUserNotificationActionContextMinimal];




リスト 2-9 通知カテゴリを登録するコード例

NSSet *categories = [NSSet setWithObjects:inviteCategory, alarmCategory, ...
UIUserNotificationSettings *settings =
[UIUserNotificationSettings settingsForTypes:types categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];



リスト 2-11 ローカル通知に用いるアクションのカテゴリを定義するコード例

UILocalNotification *notification = [[UILocalNotification alloc] init];
...
notification.category = @"INVITE_CATEGORY";
[[UIApplication sharedApplication] scheduleLocalNotification:notification];




引用ここまで-------------


つまり

まずアクションを作成する。
UIUserNotificationActionクラス

identifier
title
activationMode
authenticationRequired
destructive

などの設定をする。
通知において、ひとつのボタンの操作に対応。


次に、アクションをまとめてカテゴリーを作る。
UIMutableUserNotificationCategory

カテゴリーはアクションの配列を格納し、識別子を持つ。
ひとつの通知におけるビューがこれで作られる。
カテゴリーは別に1アプリ1つではない。通知の種類によっていくらでもカテゴリーは作れる。
ただし、コンテキストによって2種類の配列を登録する必要がある。
UIUserNotificationActionContextDefault …これはモーダルビューで見た時
UIUserNotificationActionContextMinimal …これはロック画面で見た時。こちらは最大2つしかアクションを持てない



さらに、カテゴリーをセッティングにまとめる。
UIUserNotificationSettings
セッティングは、通知種別(アラート、バッジ、サウンドなど)を持つ


そして、このセッティングをアプリケーションに登録する。
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];

これにて通知が仕えるようになる。



実際に使うためには、通知をスケジューリング登録する。

UILocalNotification *notification = [[UILocalNotification alloc] init];
...
notification.category = @"INVITE_CATEGORY";
[[UIApplication sharedApplication] scheduleLocalNotification:notification];


【2014/12/05 18:25 】 | iPhone | 有り難いご意見(0)
コードから制約を生成する
今やiOSでは当たり前となったAutoLayout。
制約(NSLayoutConstraint)を適切に指定することで、レイアウトを指定することができ、画面サイズの違う端末でも意図通りに表示できるようになる。


AutoLayoutによる位置・サイズの決定は、viewWillAppearとviewDidAppearの間で行われる。



【画面初期化の流れ】

viewDidLoad実行
   ↓
viewWillAppear実行
   ↓
AutoLayout実行(内部)
   ↓
ビューのlayoutSubviews実行
   ↓
viewDidLayoutSubviews実行(もう一度呼ばれる)
   ↓
画面が構成される(内部)
   ↓
画面遷移開始(ここからユーザの眼に触れる)
   ↓
画面遷移終了
   ↓
viewDidAppear実行



間違って(あるいは面倒くさがって)ビューコントローラ上のコードで画面レイアウトを作ろうとして、画面表示前(viewDidLoadなど)でビューのframeを直接いじっても、
その後にAutoLayout制約が適用されて上書きされてしまう。
(じゃあviewDidAppearの後にframeをいじろう、と考えたくなるところだが、それだとちょっと間に合わない。画面遷移をしながら表示するビューの場合、frameをいじる前のビューが画面に映ってしまう(=手遅れ)なのだ。


だから、レイアウトを処理・変更する必要があるビューは、必ずカスタム定義をして
layoutSubviews
メソッドを実装しておく必要がある。
このメソッドは、viewWillAppearの後に呼ばれる。
また、画面を再構成する必要があると自動的に判断されたときにも呼ばれるし、明示的に呼びたい場合は、ビューコントローラで
[view setNeedsLayout];
を実行すればよい。




じゃあどうするか。
制約適用直後に画面が映ってしまうのはさけられない。
ということはつまり、制約適用時に求める画面が構成されるようにしておけばいい。


だから、、、

【制約を動的に作成する】

ということになる。


つまり、AutoLayoutを使うようになった時点で、画面構成はすべて制約で作るようにするべきなのだ。
frameとかboundsとかを直接いじることはなくなってくるはずだ(少なくとも、定数値を代入するようなことはないはずだ。せっかくAutoLayoutを指定していても台無しになる)。


さて、そうすると、どうやってコード上で制約を記述するのか、ということを覚えなければならない。ストーリーボードではいつも作ってるけど、コーディングはどうやって…?


公式のAutoLayoutマニュアル


制約クラスは
NSLayoutConstraint

制約のインスタンスを作るメソッドは2種類ある

1.constraintsWithVisualFormat: options: metrics: views:



VisualFormat
VisualFormatとは、制約を人間に見やすく記述できるDSL。
これを使用して、ビューのサイズや位置を指定する。storyboardとほぼ同じことができる

公式のVisual Format Language仕様

いくつか例示する。
なお、view1,view2などは、制約を作りたいビューのメンバ変数名(詳細は後述)

view1の横幅を10にする
H:[view1(==10)]

view1の高さを20以上にする
V:[view1(>=20)]

view1の高さを20以上100以下にする
V:[view1(>=20,<=100)]

view1とview2の幅を同じにする
H:[view1(==view2)]

※比較演算子は、==,>=,<= の3つしか使えない
※カッコで囲わないと、パーサーがビュー名の区切りを判断できない。
※HもVも書かないときはH(水平方向)になる

view1とview2を12px空けて横に並べる
H:[view1]-12-[view2]

view1とview2をデフォルトの間隔を空けて縦に並べる
V:[view1]-[view2]

view1とview2を間隔無しで並べる
H:[view1][view2]

superviewとの間隔を指定する。パイプ(|)がsuperviewを表す
H:|-50-[view1]-50-|


options
指定できる内容は以下
NSLayoutFormatAlignAllLeft
NSLayoutFormatAlignAllRight
NSLayoutFormatAlignAllTop
NSLayoutFormatAlignAllBottom
NSLayoutFormatAlignAllLeading
NSLayoutFormatAlignAllTrailing
NSLayoutFormatAlignAllCenterX
NSLayoutFormatAlignAllCenterY
NSLayoutFormatAlignAllBaseline
NSLayoutFormatAlignAllLastBaseline
NSLayoutFormatAlignAllFirstBaseline

NSLayoutFormatDirectionLeadingToTrailing
NSLayoutFormatDirectionLeftToRight
NSLayoutFormatDirectionRightToLeft

何もいらなければ0を指定。


metrics
調べてない

views
VisualFormat内に登場したビュー名と、ビューへの参照を格納したNSDictionary。
上記の例だと、
NSDictionary viewsDictionary = [[NSDictionary alloc]initWithObjects:@[view1,view2] forKeys:@[@"view1",@"view2"]];
のような感じ。
ちなみにこれと同じことが
NSDictionaryOfVariableBindings(view1, view2);
と書くことができる。


戻り値
記述した制約をすべて満たせるNSLayoutConstraintの配列が返ってくる。


最終的なコード例
view1の横幅を40にし、view2を水平に12px空けて並べ、それぞれsuperviewと左右端を合わせる。(制約が4つ作られる)

NSDictionary viewsDictionary = NSDictionaryOfVariableBindings(view1, view2);
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view1(==40)]-12-[view2]|" options:0 metrics:nil views:viewsDictionary]];






例外を捕まえる
VisualFormatは実行時にパースされるので、書き間違えていると例外が飛んでくる。
ちゃんとキャッチしてチェックしよう。


@try{
NSDictionary viewsDictionary = NSDictionaryOfVariableBindings(view1);
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:[view1==%d)]" options:0 metrics:nil views:viewsDictionary]];
}@catch(id exception) {
NSLog(@"%@", [exception description]);
}

Unable to parse constraint format:
Expected a ']' here. That is how you give the end of a view.
V:|[view1==123)]|
^
こんな感じで出力される







2.constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:

item
制約を付けるビュー。

attribute
以下のものがある。
実態は前のメソッドのoptionに指定するNSLayoutFormat***と同じ。
NSLayoutAttributeLeft = 1,
NSLayoutAttributeRight,
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,
NSLayoutAttributeTrailing,
NSLayoutAttributeWidth,
NSLayoutAttributeHeight,
NSLayoutAttributeCenterX,
NSLayoutAttributeCenterY,
NSLayoutAttributeBaseline,
NSLayoutAttributeLastBaseline = NSLayoutAttributeBaseline,
NSLayoutAttributeFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0),

NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),

relatedBy
比較演算子。以下の3つ
NSLayoutRelationLessThanOrEqual = -1,
NSLayoutRelationEqual = 0,
NSLayoutRelationGreaterThanOrEqual = 1,


toItem
対象となるビューを指定する。
align系なら、どのビューからの距離か、を指定するときに使う。
size系なら、あのビューのサイズの○倍、というような指定の時に使う。
不要ならnilを指定。

attribute
toItem側のattribute。
itemのtopとtoItemのtopが○○px離れてる、というような制約のときに使用。
なければ0。

multiplier
係数。計算式に使う。後述

constant
定数。計算式に使う。後述


コード例
view1の高さを123にする場合。

[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil attribute:0
multiplier:1.f constant:123]
];



view1の上端をview2の上端から25px以上空ける制約

[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:self.view2 attribute:NSLayoutAttributeTop
multiplier:1.f constant:25]
];



view1の幅をview2の高さの2倍より10大きいサイズにする制約

[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view2 attribute:NSLayoutAttributeHeight
multiplier:2.f constant:10]
];




【2014/12/05 14:01 】 | iPhone | 有り難いご意見(0)
UITabBarをタップしたときのdelegateメソッド
2種類ある。

UITabBarDelegateの
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item;

UITabBarItem が引数で渡されてくる。
UITabBarController はすでにこのdelegateを実装している。
(ちなみに、ユーザがタップしてタブを選択したときにだけ呼び出される。プログラム上からタブを選択状態にしたときは呼ばれない)


もう一つ
UITabBarControllerDelegateの
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController;
こちらはUIViewControllerが引数になる。
UITabBarControllerはこのdelegateを実装していない。

UITabBarControllerを継承したクラスで使用したい場合は、UITabBarControllerDelegateを継承し、さらに

self.delegate = self;


と記述すること。


そうすると、タブを選択したときに上記の2メソッドが両方呼ばれる。

【2014/11/19 23:02 】 | iPhone | 有り難いご意見(0)
UITabBarItemにぴったり背景画像をセットする
UITabBarControllerを使うとき、タブアイテムに画像をセットすることがよくある。
普通、この画像はアイコンだけで、背景はグラフィックに持たせないことが多い。

・端末によって画面サイズが違っていて、それに伴ってタブの縦横比も違っている。→分かりやすい解説ページ
・タブ数が変わると、1つのアイコンの横幅も変わる

…というのが理由である。(当たり前)


それでもアイコンに背景を付けたい、という人は

背景用画像をアイコン1つぶんの大きさにリサイズする。
で、[UITabBar appearance] の selectionIndicatorImage にセットする。


UIImage *originImage = [UIImage imageNamed:@"tab_bg.png"];
CGSize tabItemSize = CGSizeMake([self rotatingFooterView].frame.size.width / self.tabBar.items.count, [self rotatingFooterView].frame.size.height);

UIGraphicsBeginImageContext(tabItemSize);
[originImage drawInRect:CGRectMake(0, 0, tabItemSize.width, tabItemSize.height)];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

[[UITabBar appearance] setSelectionIndicatorImage:resizedImage];



となる。

【2014/11/19 22:58 】 | iPhone | 有り難いご意見(0)
<<前ページ | ホーム | 次ページ>>