iOS
No iOS Development certificate was found に対処する
iOSでの開発を久しぶりに、しかも新しい機種で行おうとしたらxcodeで「No iOS Development certificate was found」というエラーになり、デバイスが登録できないという現象に陥ったので、対処方法を忘れないようにメモしておく。
単にProvisioning Profileの有効期限切れなのだが、どうやったかすっかり忘れてしまっている。これまた久しぶりに iTunes Connect へアクセスし、「Certificates, Identifiers & Profiles」というメニューに入る。左メニューの「Provisioning Profiles」にて[+]マークをクリックすると新しいプロファイルを作成する手順に移行する。
新しいプロファイルの作成過程で、証明書も期限が切れていると証明書(ios_development.cer)も作り直すことができる。この時別の証明書(CertificateSigningRequest.certSigningRequest)が必要になる。もしこれも有効なものが無ければキーチェーンアクセスから認証局に証明書を要求する。これで無事にプロファイル(*.mobileprovision)が作成され、ダウンロードが可能になる。
最後にxcodeのOrganizerでダウンロードしたプロファイルを追加すればデバイスを登録できるようになる。
UIButtonに任意の画像を使ってタップ時にハイライトさせる
今更ではあるがメモしておく。
画面上にボタンを用意するにはUIButtonを使うが、標準の外観では物足りない場合はボタンに見える画像を用意することが多い。ところがHTMLのつもりでImageプロパティに画像を指定してTitleプロパティにラベルを指定…とやってしまうと画像だけ表示されてラベルが表示されない。
その場合はラベルも含んだボタン画像を用意するというのも一つの方法だが、ボタンの種類が多い場合は用意すべき画像が多くなってしまう。背景画像(BackgroundImageプロパティ)に画像を設定する。
ただ、単純に背景画像を設定してもいいが、より細かく表現するにはボタンの状態別に画像を用意する。非活性状態、有効状態、選択状態、押下状態の4種類あればよいだろう。それぞれ次のようなコードになる。
1 2 3 4 | [button setBackgroundImage:[UIImage ...] forState:UIControlStateDisabled]; [button setBackgroundImage:[UIImage ...] forState:UIControlStateNormal]; [button setBackgroundImage:[UIImage ...] forState:UIControlStateSelected]; [button setBackgroundImage:[UIImage ...] forState:UIControlStateHighlighted]; |
これを利用してトグルボタンのような機能を持たせることもできる。タッチイベントを捕捉してOFF状態であればONにし、ON状態であればOFFにするという具合だ。しかし、これがうまく動作してくれない。いや、トグルは動作するのだが、OFF状態のボタン押下中のハイライト画像が表示されないのだ。
まず、adjustsImageWhenHighlightedプロパティはfalseにしておこう。これがtrueだとOFF状態のボタンを押下した時にハイライト画像ではなく、ボタン全体が暗転してしまう。だがそれでもまだ不充分。
どうやらUIControlState~はビット和を指定することが可能らしい。つまりON状態とOFF状態だけでなく、OFF状態のボタンを押下中やON状態のボタンを押下中という表現ができる。それを改良したのが次のコード。
1 2 3 4 5 | [button setBackgroundImage:[UIImage ...] forState:UIControlStateDisabled]; [button setBackgroundImage:[UIImage ...] forState:UIControlStateNormal]; [button setBackgroundImage:[UIImage ...] forState:UIControlStateSelected]; [button setBackgroundImage:[UIImage ...] forState:(UIControlStateHighlighted|UIControlStateNormal)]; [button setBackgroundImage:[UIImage ...] forState:(UIControlStateHighlighted|UIControlStateSelected)]; |
これでボタンの状態に関係なく、ボタン押下中はハイライト画像を表示させることができた。
“Unknown class ‘class name’ in Interface Builder file” に対処する
久しぶりにiOSアプリの開発環境をいじったら久しぶりにハマったのでメモしておく。
現象としてはXcodeからプロジェクトをビルドしてシミュレータで起動しようとすると、起動できずにアプリは終了し、以下のエラーメッセージを吐くというもの。実際には「class name」には作成したクラス名が入る。
Unknown class ‘class name’ in Interface Builder file
対処方法は次の通り。
- XcodeのProject Navigatorでプロジェクトのルートを選択して設定画面を開く。
- Standard editorの左ペインで TARGETS 以下の項目を選択する。
- Standard editorの右ペインの Build Phases タブを選択する。
- Compile Sources のグループを開き、不足しているクラスのソース本体(.m)を追加する。
- ビルド及び実行する。
これはInterface Builderから特定のクラスが見えないという状況なのだが、例えば新規にプロジェクトを作成した時に、他のプロジェクトで作成したクラスをファイルのコピーによって追加したような場合に発生する。(クラスを新規作成で追加した場合は自動でBuild Phaseに追加されるようだ。)
絵文字を含むNSStringの正確な文字数をカウントする(2)
ちょっと前に絵文字の混在した文字列のカウント方法について書いたら、ちゃんとカウントできない文字があると指摘された。
なるほど、確かにカウントできない文字がある。いったいどんな規則性があるのかと思っていたら、絵文字の文字コードをまとめていたサイトを教えてもらった。
それがこれ↓
iOS Emoji
う~ん、まるで規則性が見当たらないと思っていたらピンときた。前は UTF8String
とかやってたけど、内部の文字コードはもしかしてUTF-16ではなかろうか?と思って調べたらやっぱりUTF-16だった。それならわざわざUTF-8に変換しないでUTF-16のまま処理した方が良い。
UTF-16といえば主要なトピックはサロゲートペアだ。0xD800-0xDBFFが上位サロゲート、0xDC00-0xDFFFが下位サロゲート。( Wikipedia#Unicode 参考)なので上位サロゲートを検出したら1文字スキップする。(1)
さらに、国旗の絵文字には Regional Indicator という専用のシンボルが2つの組み合わせで使われている。( Unicode.orgのこのページ の最下段に記載されている。)なのでRegional Indicatorを検出して次のサロゲートペアもRegional Indicatorだったら3文字スキップする。(2)
あと、ガラケーではお馴染みの四角で囲まれた数字には通常の数字の後に COMBINING ENCLOSING KEYCAP という専用の文字が配置されている。だがこれは単純に無視すればいいだろう。(3)
そうすると、こんな感じで書けばいいかな?ちゃんと実装するには文字のインデックスが out of bounds にならんようにした方がいいかも。
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 31 32 33 34 35 36 37 38 39 40 | - (NSUInteger) actualNSStringSize:(NSString *) txt { int len = [txt length]; NSUInteger count = 0; for (NSUInteger i = 0; i < len; i++) { unichar c = [txt characterAtIndex:i]; if (0xD83C == c) { unichar c1 = [txt characterAtIndex:i+1]; if ((0xDDE6 <= c1) && (c1 <= 0xDDFF)) { unichar c2 = [txt characterAtIndex:i+2]; if (0xD83C == c2) { unichar c3 = [txt characterAtIndex:i+3]; if ((0xDDE6 <= c3) && (c3 <= 0xDDFF)) { // 国旗なのでスキップ・・・(2) i += 3; ++count; continue; } } } i++; ++count; } else if (0xD800 <= c && c <= 0xDBFF) { // 上位サロゲート・・・(1) i++; ++count; } else if (0xDC00 <= c && c <= 0xDFFF) { // 下位サロゲートなので念のため無視 } else if (0x20E3 == c) { // 囲みなので無視・・・(3) } else { ++count; } } return count; } |
ちなみに、これらの絵文字はiOS独自の実装かと思っていたらそうではなく、Unicode6.0 というれっきとした国際規格なのだそうだ。ガラケーに端を発した日本発の絵文字が知らない間に国際規格に…っていうか Unicode のバージョンって知らない間にこんなに上がっていたんだ。
絵文字を含むNSStringの正確な文字数をカウントする
iPhone/iPad のキーボード設定で「絵文字」というキーボードを追加すると、ガラケーよろしく絵文字を入力できるようになる。なので、何も考えないとUITextFieldなどに絵文字を入力されてしまう可能性がある。ここでアプリの仕様上、絵文字を許容するかしないかという議論は当然起こるが、許容したくないというケースはひとまず置いといて、許容する場合のトピックを取り上げる。
絵文字も他の文字と同様UTF-8であるようだが、絵文字の中には4バイトで表現されるものもあるようで、その4バイトの絵文字を含んだ文字列は [str length]
で得られる文字数が狂ってしまうのだ。そうなるとバリデーションとして文字数制限を設ける場合にうまくない。
この現象はどうやらNSStringクラスのUTF-8の取り扱いに原因があるようだが、不具合だと認識されていれば将来的にSDKの方で対応される可能性がある。それまでは次のような関数で凌ぎたいと思う。
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 | - (NSUInteger) actualUtf8StringSize:NSString str { const char* cstr = [str UTF8String]; int len = strlen(cstr); NSUInteger count = 0; unsigned char c; for (int i = 0; i < len; i++) { c = (unsigned char)cstr[i]; if (0x00 <= c && c <= 0x7F) { ; } else if (0xC2 <= c && c <= 0xDF) { i += 1; } else if (0xE0 <= c && c <= 0xEF) { i += 2; } else if (0xF0 <= c && c <= 0xF7) { i += 3; } else { continue; } ++count; } return count; } |
これはUTF-8の性質を利用して文字数をカウントしている。UTF-8は文字の1バイト目でその文字が何バイトかを示している。なので、バイト列を先頭から調べて行き、文字のバイト数分スキップしながらカウンタを増加している。ひとつ留意したいのは、バイト列から1バイト取得する時に char
型ではなく unsigned char
型の変数に代入することだ。C言語では常識の範疇だが、慣れていないと見落としやすい。
複数行のUILabelを内容に合わせて自動縮小
UILabelは文字列を表示する部品だが、固定した内容を表示する時はもちろん、状況に応じて内容を変更することもできる。この部品にはInterface Builderで見るとFont指定欄下に Autoshrink
という属性(プロパティとしては adjustsFontSizeToFitWidth
)があって、ここにチェックを入れていると文字列が長くてはみ出てしまうときは、自動でフォントサイズを小さくして全体が収まるようにしてくれる。(際限なく小さくすると読めなくなってしまうため、 MinimumSize
という属性で最小サイズを指定できる。)もちろんチェックを外せば、はみ出る場合に例えば末尾が「…」という風に省略される。
ところが、公式リファレンスにも記載されている通り、この Autoshrink
という属性は Lines
属性が 1 の時しか有効にならないという制限がある。そうは言っても長い文字列の時は折り返して見やすく表示したいし、行数も分からないけど表示枠だけは決まっているというケースもあるはずだ。そんな時、自動でフォントサイズを小さくして決められた枠内に収まるような機能があれば嬉しい。
例えば、Interface Builder上でUILabel部品を貼り付けて、labelという名前でOutletを作成したとする。まずは。Size Inspector を開いてサイズを決める。次に Attributes Inspector を開き、フォントとフォントサイズを決めてしまおう。そして今回は Autoshrink
のチェックを外しておく。そして行数は動的に変わって欲しいので Lines
=0 としておく。(こうすると自動で行数を増減してくれる。)そして次のようなコードを書いて、本来のサイズに収まるまでフォントサイズを小さくしていけば良い。
1 2 3 4 5 6 7 8 9 | float orgHeight = self.label.frame.size.height; // ラベルの高さの初期値を保持しておく self.label.text = @"..."; // 充分に長い文字列 [self.label sizeToFit]; // 内容に合わせてラベルの大きさ(高さ)を変更 // 本来の高さになるまでフォントを小さくしていく while (orgHeight < self.label.frame.size.height) { self.label.font = [UIFont fontWithName:self.label.font.fontName size:self.label.font.pointSize - 0.1f]; [self.label sizeToFit]; } |
UIWebViewをスクロールさせない
UIWebViewクラスはUIScrollViewの派生クラスなので基本的にはWEBブラウザと同様に内容がはみ出る場合には自動でスクロールする。
ところが、場合によってはUIWebViewを使いたいがスクロールはさせたくないということもある。
そういう場合は userInteractionEnabled プロパティをNOにしてしまえばよい。これでタップ等のイベントを拾わなくなるのでスクロールすることもない。
(ちなみに、この方法だとハイパーリンクもタップ出来なくなるので注意!)
尚、Interface Builderを使用する場合は、Attribute Inspectorを開き、 View > Interaction > User Interaction Enabled のチェックを外せば良い。