Xcode6+swift アプリのディレクトリ構成とディレクトリパスの取得
アプリに同梱した画像のzipファイルを、初回起動時に解凍する処理を作っています。
iOSのときは、同梱するファイルや解凍したファイル、どこに置くんだっけ?と思ったので調べてみました。
ついでに、各ディレクトリパスの取得方法も。
1.ディレクトリ構成について
いきなり、わかりやすいまとめがありました。
参考:今こそ復習したい、iOSアプリのディレクトリ構成 - Qiita
・zipファイル・・・MyApp.app/(普通の画像と同じ扱い)
・解凍したファイル・・・Library/
に置けば良さそうです。
2.各ディレクトリパス取得方法
主なディレクトリパスの取得方法は、MyApp.app/、tmp/、その他のディレクトリ
で分かれています。
①MyApp.app/
NSHomeDirectory()
②tmp/
NSTemporaryDirectory()
③その他は、NSSearchPathForDirectoriesInDomains() メソッドか、NSFileManagerクラスの
URLsForDirectory() または URLForDirectory()メソッドで取得します。
NSFileManagerのほうを検討するように、とあったので、今回はそちらを使用します。
参考:Foundation Functions Reference
URLsForDirectory(directory:NSSearchPathDirectory,
inDomains domainMask:NSSearchPathDomainMask) -> [AnyObject]
URLForDirectory(directory:NSSearchPathDirectory,
inDomain domain:NSSeatchPathDomainMask,
appropriateForURL url:NSURL?,
create shouldCreate:Bool,
error error:NSErrorPointer) -> NSURL?
パラメータ名 | 役割 |
---|---|
directory | 検索したいディレクトリ。NSSearchPathDirectoryを使って指定。 |
domain | 検索したい場所。NSSearchPathDomainMaskを使って指定。 |
url | directoryにNSItemReplacementDirectory、domainにNSUserDomainMaskが指定されているとき以外は、無視される何か。(わたしの理解力の限界) |
shouldCreate | ディレクトリが存在しなかった場合、作成するかどうか。trueで作成する。 |
error | エラー情報用のポインタ |
どちらのメソッドもだいたい一緒ですが
・URLsForDirectoryは戻りが配列になっていること
・URLForDirectoryのほうは、戻りがひとつなので、複数の場所を指定するdomain(NSAllDomainMask)は使えないこと
がポイントかなと思います。
使い方は、こんな感じ。
// MyApp.app/ let homeDir = NSHomeDirectory() println("homeDir: \(homeDir)") // tmp/ let tmpDir = NSTemporaryDirectory() println("tmpDir: \(tmpDir)") // NSFileManager取得 var mgr = NSFileManager.defaultManager() // Documents/ let documentDir:AnyObject = mgr.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] println("documentDir: \(documentDir)") // Library/ ディレクトリがなかったら作成する、エラー情報は使用しない let libraryDir = mgr.URLForDirectory(.LibraryDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true, error: nil) println("libraryDir: \(libraryDir)") // Library/Caches ディレクトリがなかったら作成しない、エラー情報を使用する var error:NSError? let cachesDir = mgr.URLForDirectory(.CachesDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false, error: &error) println("cachesDir: \(cachesDir)") if (error != nil) { println("error: \(error)") }
そして、実行結果はこんな感じでした。
homeDir: /Users/[user name]/Library/Developer/CoreSimulator/Devices/[device ID]/data/Containers/Data/Application/[App ID] tmpDir: /Users/[user name]/Library/Developer/CoreSimulator/Devices/[device ID]/data/Containers/Data/Application/[App ID]/tmp/ documentDir: [file:///Users/[user name]/Library/Developer/CoreSimulator/Devices/[device ID]/data/Containers/Data/Application/[App ID]/Documents/] libraryDir: Optional(file:///Users/[user name]/Library/Developer/CoreSimulator/Devices/[device ID]/data/Containers/Data/Application/[App ID]/Library/) cachesDir: Optional(file:///Users/[user name]/Library/Developer/CoreSimulator/Devices/[device ID]/data/Containers/Data/Application/[App ID]/Library/Caches/)
NSSearchPathDirectoryの値は、上記以外にもPicturesDirectory(~/Pictures)とか
使いそうなものがたくさんありました。
やっぱり、Reference読むのが一番ですね。
Xcodeのグループと、実際のディレクトリ構成を合わせるツール Synx
以前、一度だけiOSのプロジェクトに入ったことがあるのですが
Xcode上でせっせとグループ分けをしたのに、実際のファイルはプロジェクト名のディレクトリ直下に
ばーっと入っていて、とてもびっくりしました。
それ以来、個人でアプリを作るときは、グループ作ったらディレクトリも作って・・・と
気をつけていたものの、気を抜くと忘れちゃう。
Synxはコマンドひとつで、Xcodeプロジェクト上のグループに実際のディレクトリ構成を
合わせてくれる便利ツールです。
1.インストール
ターミナルから
$ gem install synx
おわりー!!
2.使い方
使い方もとっても簡単です。
$ synx [プロジェクトファイルのパス(XX/XXX/XXX.xcodeproj)]
※cocoapodsを使用している場合は、上記コマンドの後に
$ pod install
したほうが良いようです。
(私の環境では、しなくても大丈夫でした)
もし、pod install してもうまく動かなくなってしまった場合は、一度pod関係を削除してからsynxするのが良いようです。
参考:cocoapods使用環境でsynxすると動かなくなる - Qiita
3.オプション
オプション | 役割 |
---|---|
--prune, -p | Xcodeプロジェクトから参照されていないソースファイルと画像ファイルを削除 |
--no-color | ログ出力をカラーにしない(指定しないと色分けして出力されます) |
--no-default-exclusions | デフォルトの除外(/Libraries、/Frameworks、/Products)を使用しない |
--quiet, -q | ログを出力しない |
--exclusion, -e | 対象外のディレクトリを指定する |
-p、-eあたりはよく使いそうです。
jQuery.ajaxSubmit()実行後、元画面が表示された時にPIE_IE678.js内でエラーが出る
以前、jQuery Form Pluginで画像ファイルをアップロードするプロジェクトを作りました。
struts+jQuery Form Pluginを使ったファイルアップロード① - わたしの日記だよ
このプロジェクトは、CSSでボタンの角を丸くする処置が入っていて
かつ、IE8標準対応だったので、CSS3 PIEという
CSS非対応のブラウザに対して、がんばってCSSの表示を再現してくれるライブラリ(JavaScript版)を使っていました。
CSS3 PIEについてはこちら:CSS3 PIE: CSS3 decorations for IE
初回読み込み時は、問題ないのですが
jQuery.ajaxSubmit()実行後、元の画面が再読み込みされると
1242行目のel.getBoundingClientRect()のところでUnspecifiedエラーが出てしまいます。
◯解決方法
jQuery.ajaxSubmit()が実行される前に、PIE.attachを
jQuery.ajaxSubmit()実行後に、PIE.detachを呼んであげるだけでOKでした。
例えばこんな感じ。
$('form').ajaxSubmit({ 'target': "body", 'beforeSubmit': function(formData, jqForm, options) { // 送信前に、PIEの適用をはずす $('.btn').each(function(){ PIE.detach(this); }); }, 'success': function(filename) { // 戻ってきたら、PIEの適用する $('.btn').each(function(){ PIE.attach(this); }); } });
最初、target(サーバからのレスポンス出力先)を指定しているため、
普通にページ読み込みしたときと違って、PIEの処理のタイミングがずれてしまっているのかなと思い
PIEライブラリの読み込みを<header>ではなく<body>でしてみたり、
ライブラリ内でログを出しまくってページ読み込み時との差分を探したり、
見当外れなことばかりしていました。
使い方調べろ!!って話だった。。。
フォームデータ送信後、DB更新に成功したら印刷プレビューを開く
画面から、任意の項目を選択→確認画面へ移行→OKボタン押下でサーバへ送信した後
DB更新に成功したら、元のページで印刷プレビューを開いてほしいな、と言われました。
ちなみに、struts2のプロジェクトです。
当初、ajax使って〜とのことだったので、jQuery.ajax()でasync:falseを指定して
実装してみたのだけど、jQuery1.8以降非推奨らしいし、エラーも出てしまう。
(Choromeで確認)
Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check http://xhr.spec.whatwg.org/.
これはちょっと嫌な感じなので、今回はformを使うことを考えてみました。
1.フォーム送信〜DB更新するところまで
まず、jsp側(抜粋)。
<div class="print-off"> <s:form> <input id="printStatus" type="hidden" value='<s:property value="printStatus" />'/> <s:iterator status="st" value="idList"> <input type="hidden" name='idList[<s:property value="#st.index" />]' value='<s:property value="idList[#st.index]" />' /> </s:iterator> <s:submit method="updateStatus" value="一括印刷"/> </s:form> </div> <div class="print-on"> ・・・ </div>
「print-on」クラスを持つ要素は、通常はCSSで非表示にしておいて
印刷プレビューを表示するときだけ表示。
「print-off」クラスを持つ要素は、その逆です。
(印刷のときに、ボタンとか余計なものを入れないための処置)
また、hiddenの<input>タグは、以下の用途に使います。
・printStatus・・・DB更新結果を保持する
・idList[<s:property value="#st.index" />]・・・印刷したいIDの配列要素
Action側(抜粋)。jsp側の<s:submit>タグで指定した「updateStatus」メソッドを作り
その中でDB更新するだけです。抜粋っていうか、日本語しか書いていない。。。
public class TestAction extends ActionSupport { // 印刷したいID public List<string> idList = new ArrayList<string>(); // DB更新結果フラグ public String printStatus = ""; ・・・ public String updateStatus() throws Exception { // idListを使って、DBテーブルの「印刷したよ」フラグを更新する処理・・・☆ // ☆に成功したらprintStatusに"SUCCESS"、失敗したら"FAIL"をセットする ・・・ } ・・・ }
2.printStatusの更新をチェックする
フォーム送信後、レスポンスを受け取ったときに、window.loadのイベントが呼ばれるので
そこでprintStatusの値をチェックすることにします。
jsファイル側。
$(window).load(function(){ // 印刷フラグのチェック var status = $("#printStatus"); if (!status && !status.val()) { var val = status.val(); if (val === "SUCCESS") { // 印刷フラグ更新成功ー>印刷プレビュー表示 showPrintPreview(); } else if (val === "FAIL") { // 印刷フラグ更新失敗 alert("DBの更新に失敗しました。"); } } }) function showPrintPreview() { // 印刷しない要素を非表示にする var printOff = document.getElementsByClassName("print-off"); for(var i = 0; i < printOff.length; i++) { printOff[i].style.display = "none"; } // 印刷する要素を表示する var printOn = document.getElementsByClassName("print-on"); for(var i = 0; i < printOn.length; i++) { printOn[i].style.display = "block"; } // 印刷プレビュー表示 if (isIE()) { // IEの場合 var sWebBrowserCode = '<object width="0" height="0" classid="CLSID:8856F961-340A-11D0-A96B-00C04FD705A2"></object>'; document.body.insertAdjacentHTML( 'beforeEnd', sWebBrowserCode ); var objWebBrowser = document.body.lastChild; if(objWebBrowser == null) { return; } objWebBrowser.ExecWB( 7, 1 ); document.body.removeChild( objWebBrowser ); } else { // IE以外 window.print(); } // 印刷しない要素を再表示する for(var i = 0; i < printOff.length; i++) { printOff[i].style.display = "block"; } // 印刷する要素を非表示に戻す for(var i = 0; i < printOn.length; i++) { printOn[i].style.display = "none"; } } function isIE(){ if(!window.ActiveXObject){ if(!document.documentMode){ return false; } } return true; }
ちょっと長いですが、
印刷したい部分のみを表示する→印刷プレビューを開く→表示を元に戻す
としているだけです。
IEのみ、印刷プレビューを表示するのにActiveXObjectを使わないとうまくいかないので
そこだけポイントです。
こちらを参考にしました。
3.印刷プレビューが表示される前に、表示を戻す処理が走ってしまう
上記のjsは、試しに<s:submit>タグのonclickイベントから呼んでいたときには
期待通りに動作していましたが、いざwindow.loadから呼んでみると
印刷プレビューを表示する前に、表示を戻す処理が走ってしまうようになってしまいました。
onclickから呼ぶのと何が違うのか、まだわかってないのですが
JavaScriptは非同期処理なのですね。
[JavaScript] JavaScriptはシングルスレッド:非同期処理の仕組み | Ouka Studio
だったら、表示を戻す処理は、印刷プレビューが閉じるタイミングで実行すればいいかな。
印刷プレビューが閉じて、元画面の描画が完了すると
window.onafterprintというイベントが呼ばれるので、こちらを使います。
jsファイル側(表示を戻す処理のみ抜粋)。
var afterPrint = function() { // 印刷しない要素を表示する var printOff = document.getElementsByClassName("print-off"); for(var i = 0; i < printOff.length; i++) { printOff[i].style.display = "block"; } // 印刷する要素を非表示に戻す var printOn = document.getElementsByClassName("print-on"); for(var i = 0; i < printOn.length; i++) { printOn[i].style.display = "none"; } } // Chorome・safari if (window.matchMedia) { var mediaList = window.matchMedia('print'); mediaList.addListener(function(mql) { if (!mql.matches) { afterPrint(); } }) } // IE・Firefox window.onafterprint = afterAllPrint;
onafterprintイベントをサポートしていないブラウザもあるので
window.matchMediaを判定してごにょごにょする処理も加えておきます。
Operaはどっちもサポートしていないようです。。。困った!
(今回は、対象ブラウザじゃないのでパス)
JavaScript、いつもなんとなくで書いていたので
早急に入門しなおさなくては・・・
struts+jQuery Form Pluginを使ったファイルアップロード②〜IEの動作〜
前回、タイトルの通り、画像ファイルをアップロードして
元の画面に表示するという処理を書きました。
struts+jQuery Form Pluginを使ったファイルアップロード① - わたしの日記だよ
ただ、IEのときだけActionが呼ばれないことがあったので
以下のように確認してみました。
1.レスポンスの確認
まず、ieHTTPHeaders使って、そもそも応答が来てるかどうか調べます。
IE8だと、元々は入ってないので、別途インストールが必要です。
使い方は、IEブラウザの
Tools → Explorer Bars → ieHTTPHeaders を選択後、確認したい通信を実行するだけです。
今回は、HTTPステータス200:OKが返ってきていました。
一応、正常に返ってきたことにはなっているのか。。。
2.Actionの前に呼ばれる処理
Struts2にはインターセプターという仕組みがあり、リクエストが投げられると
このインターセプターが呼ばれた後、Actionへ処理が渡ります。
たくさん種類がありますが、
Validationインターセプター(名前のとおり、バリデーション処理を行う)
Servlet Configインターセプター(サーブレット関連の値をセットする)
なんかが、イメージが湧きやすいでしょうか。
自前で用意することも可能です。
まずは、Servlet Configインターセプターが呼ばれているか、確認します。
Actionクラスで「なんとかAware」インターフェースを実装して使用します。
今回は
ServletRequestAware、ServletResponseAware、SessionAware
の3つを使っていたので、ServletRequestAwareのメソッドにブレークポイントを
置いて、アップロードを実行してみました。
他のAwareのメソッドも含め、ちゃんと呼ばれていました。
次に、その他のインターセプターを確認します。
Actionクラスにアノテーションで指定されています。
@InterceptorRefs({ @InterceptorRef(value = "myDefaultStack") }) public class TestAction extends ActionSupport { ・・・
こんな感じ。@InterceptorRefの値「myDefaultStack」はstruts.xmlの中で
設定されています。
<package name="attend-reserve" extends="struts-default"> <interceptors> <interceptor name="myInterceptor" class="xx.xxx.MyInterceptor" /> <interceptor-stack name="myDefaultStack"> <interceptor-ref name="defaultStack" /> <interceptor-ref name="myInterceptor" /> </interceptor-stack> </interceptors> ・・・ </package>
Actionクラスで指定している「myDefaultStack」では、
defaultStack(struts2のデフォルトインターセプター)と
myInterceptor(自前のインターセプター)を呼ぶよう指定しています。
myInterceptorの最初と最後でログを吐くようにしてみましたが、全然出力されませんでした。
そこで、myInterceptorとdefaultStackの順番を入れ替えたところ
ログ出力されたので、defaultStackで引っかかってるみたい!!
しかし、defaultStackを削除して、アップロードを実行しても
Action側に処理は渡ってきません。
defaultStackの中に、必須のインターセプターが居るのかな??
↓↓↓のページによると、あらかじめ用意されているインターセプターは
struts-default.xmlに定義されていて、defaultStackで使用しているのはそのうち17ほどだそう。
Struts2入門(4)〜インターセプターという仕組み〜:http://codezine.jp/article/detail/3264
多い。。。今回は時間の都合で、これ以上追うのは諦めてしまいました。
3.対策
リクエストが届いていることはわかったので、正常に呼ばれるところで
なんとかすることにしました。
struts2:http://blog.pionet.co.jp/experience/archives/43
formから送信された値がActionの同名フィールドに入ってくるんだな、とは思ってたのですが
<s:property value="フィールド名">などがgetterの呼び出し
<input>タグや<s:textfield>がsetterの呼び出し(name属性=フィールド名)
だったのですね。
getter/setterなしの、publicフィールドを使っていたので気付きませんでした。
前回作ったActionクラスを、以下のように修正しました。
public class TestAction extends ActionSupport implements ServletResponseAware, ServletRequestAware { private static final long serialVersionUID = 1L; // 画面表示用のファイルパス public String imgFileName; // アップロードファイル格納用 private File imgFile; public void setImgFile(File imgFile) { this.imgFile = imgFile; // リクエスト取得 HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(org.apache.struts2.StrutsStatics.HTTP_REQUEST); // 保存するファイル名(一時保存されたファイルは、形式にかかわらず「.tmp」なので「.jpg」に変更) String fileName = imgFile.getName().replace(".tmp", ".jpg"); // 保存用のディレクトリ(/test)をチェックして、存在しなければ作成 File tmpDir = new File(request.getServletContext().getRealPath("/test")); if (!tmpDir.exists()) { tmpDir.mkdir(); } // ファイルを保存 BufferedImage img = null; boolean result = false; File writeFile = new File(request.getServletContext().getRealPath("/test/" + fileName)); try { img = ImageIO.read(imgFile); result = ImageIO.write(img, "jpg", writeFile); } catch (Exception e) { e.printStackTrace(); result = false; } // 保存に成功したら、画面表示用のパスをセットする if (result) { imgFileName = "test/" + fileName; } } public String execute() throws Exception { // 念のため、こっちでもセットしておく if (imgFile != null) { // リクエスト取得 HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(org.apache.struts2.StrutsStatics.HTTP_REQUEST); // 保存された(はずの)ファイル名(一時保存されたファイルは、形式にかかわらず「.tmp」なので「.jpg」に変更) String fileName = imgFile.getName().replace(".tmp", ".jpg"); // 保存ファイルの存在チェック File writeFile = new File(request.getServletContext().getRealPath("/test/" + fileName)); if (writeFile.exists()) { // ファイルが存在していたら、画面表示用のパスをセットする imgFileName = "test/" + fileName; } } return "success"; } ・・・ }
画像ファイルが送信されてきたときの処理を、まるまるsetterに移して
念のため、execute()でも画像表示用のパスのセットだけ行っています。
これで、IEでも毎回画像のアップロード〜表示が行えるようになりました。
あんまりすっきりしないけど、めでたし②!!
それにしても、今さらstruts入門することになるとは。。。
struts+jQuery Form Pluginを使ったファイルアップロード①
会社で急遽、おとなりのプロジェクトのお手伝いをすることになりました。
久しぶりのJava。一番経験の長い言語なので大丈夫でしょ
とタカをくくっていたところ、強烈に洗礼を受けました。。。
1.環境
・Java
・Tomcat v7.0
基本のところはこんな感じ。あと、PIE_IE678.jsという
CSSが効かない古いIEに、がんばってCSS適用してくれるjsを使っていました。
2.要件
・ファイル選択ダイアログからファイルを選択する
・ファイルをアップロードし、同じ画面に表示する
・ファイル形式はjpegのみで、200KB以内
・IE8標準対応(!)
3.準備
今回は、form内の一項目として扱うため、jQuery Form Pluginというものを使うことにしました。
jQuery Form Plugin : jQuery Form Plugin
↑からダウンロードして、jspファイルで読み込み。
<script src="js/jquery-1.11.2.min.js" type="text/javascript" charset="utf-8"></script> <script src="js/jquery.form.js" type="text/javascript" charset="utf-8"></script>
4.実装
test.jsp(他の項目や装飾は省略しています)。
<s:form> <input type="file" name="imgFile" onchange="fileupload(event);" accept="image/jpeg" /> <div> <s:if test="imgFileName != null"> <img src="<s:property value="imgFileName" />" /> </s:if> <s:else> <img src="img/img-no-image.png" /> </s:else> </div> </s:form>
ファイル選択ダイアログ表示ボタン<input type="file">と
選択した画像ファイル表示用の<img>があるだけです。
<img>は、Action側が持っている値を判定して、値がなければデフォルト用の画像を表示するようにしました。
file-upload.js
fileupload = function(event) { var $form, target; target = event.target ? event.target : event.srcElement; $form = $(target).closest('form'); return $form.ajaxSubmit({ 'target': "body", 'cache': false, 'beforeSubmit': function(formData, jqForm, options) { // ファイルサイズチェックなどの処理 }, 'success': function(filename) { // アップロード成功時の処理 alert("success"); } }); };
$form.ajaxSubmit で、formの値をAction側へ送ります。このとき、引数でオプションを指定することができます。
主なオプションは以下。
オプション名 | 役割 |
---|---|
beforeSubmit | フォーム送信前の処理を指定。 今回は、ファイルサイズ等のチェックがあるので使用。 |
dataType | レスポンスのデータ型。xml、script、jsonから指定。省略時はnull レスポンスをそのまま表示するだけであれば、指定しなくて大丈夫だと思います。 |
error | エラー時の処理を指定。 |
success | 処理を指定。 |
target | サーバからのレスポンスを出力する箇所。jQueryオブジェクトやDOM要素で指定。 今回は、Action側でページを返していたので「body」を指定。 |
type | リクエストタイプをGETまたはPOSTで指定。省略時はフォームの指定に従う。 |
url | フォームデータを送信先のURL。省略時はフォームでの指定に従う。 |
testAction.java
public class TestAction extends ActionSupport implements ServletResponseAware, ServletRequestAware { private static final long serialVersionUID = 1L; // 画面表示用のファイルパス public String imgFileName; // アップロードファイル格納用 public File imgFile; public String execute() throws Exception { if (imgFile != null) { // リクエスト取得 HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(org.apache.struts2.StrutsStatics.HTTP_REQUEST); // 保存するファイル名(一時保存されたファイルは、形式にかかわらず「.tmp」なので「.jpg」に変更) String fileName = imgFile.getName().replace(".tmp", ".jpg"); // 保存用のディレクトリ(/test)をチェックして、存在しなければ作成 File tmpDir = new File(request.getServletContext().getRealPath("/test")); if (!tmpDir.exists()) { tmpDir.mkdir(); } // ファイルを保存 BufferedImage img = null; boolean result = false; File writeFile = new File(request.getServletContext().getRealPath("/test/" + fileName)); try { img = ImageIO.read(imgFile); result = ImageIO.write(img, "jpg", writeFile); } catch (Exception e) { e.printStackTrace(); result = false; } // 保存に成功したら、画面表示用のパスをセットする if (result) { imgFileName = "test/" + fileName; } } return "success"; } ・・・ }
フォームデータが送信されると、Action側の同じ名前のプロパティに格納されます。
execute()は、初回表示時などにも呼ばれるため、もしimgFileがセットされていたら
ファイルアップロードが実行されたとみなしています。
これでOKとかと思いきや、IEのときだけ1回目のアップロードでexecute()が呼ばれない
問題が起きてしまいました。
1回目:呼ばれない、2回目:呼ばれる、3回目:呼ばれない、4回目:呼ばれる・・・
の繰り返し。
IE8とIE11で確認したので、バージョンは関係ないようです。
これを調べるのが結構大変だったので、次回にまとめようと思います。
Xcode6+swift で storyboadを使わずに実装する
以前、一度だけObject-CでiOSアプリを作ったことがあるのですが、
久しぶりにやってみたらXcodeの使い方すら
すっかり忘れ去っていたので、最初からお勉強しなおすことにしました。
せっかくなので、swiftにしてみます。
今回は、会社でやっていたときと同じく、storyboadは使わず
xibファイルだけ使います。
1.プロジェクト作成
Single View Applicationを作成します。
↓こちらなどを参考に。
Xcode6でストーリーボードを使わないで開発する - kzy52's blog
2.xibファイルとUIViewControllerを作成する
xibとcontrollerを別々に作成したら、xibファイルを開いて
あとは、controllerのviewDidLoad()で、xibを指定してUIViewのインスタンスを作成し
controller自身のviewに追加します。
"xib-name"のところは、作成したxib名に置き換えます。
let view:UIView = UINib(nibName: "xib-name", bundle: nil).instantiateWithOwner(self, options: nil)[0] as UIView self.view.addSubview(view)
controllerから「Also create XIB file」にチェックを入れて、xibも一緒に作成した場合は
xibの[Connection inspector]-[Outlets]-[view]を削除すれば大丈夫です。
たったこれだけなのだけど、けっこう時間がかかってしまった。
色々調べていたときに、
・UIViewControllerを作るときに「Also create XIB file」にチェックを入れれば、自動的に紐付く
・xibとcontrollerを別々に作ってから、File's Ownerの指定と[Connections Inspector]-[Outlets]-[view]
を設定すれば紐付く
とか見かけたのですが、わたしの環境だとうまく行きませんでした。
エラーにはならないものの、何も表示されない。
→でも、controllerに上記と同じコードを追加すると
「Can't add self as subview」エラーが出るので、全然紐付いてないわけじゃないみたいなのに!!
ほんとはもっとスマートな方法があるのかも。