2015年9月29日火曜日

AppSotreからのXocdeアップデートが失敗する

追記:AppSotreからのXocde 8へのアップデートが失敗する

アップデートの度に容量不足が発生するが、8へのアップデートでは20Gほど空いていないとアップデートできない。

ディスク使用量をチェックしたら~/Library/Developer/Xcode/iOS DeviceSupportに古いバージョンのものが残っていおり、大量にスペースを消費していた。
これらはシンボルが入っているだけで、必要であればXcodeがデバイスからダウンロードするという書き込みがあったので、古いものを削除した。


AppSotreからのXocde 7へのアップデートが失敗する

アップデート失敗後に「”購入済みページ”からダウンロードし直してください。」とメッセージが表示される。
そのとおりにしてみても、ダウンロード完了直前に同じことが再発する。

結果的にディスクの残容量不足でした。9G程度必要なようです。
このエラーが発生したときに、どうも途中までダウンロードしたものが残っているようで、残容量を更に圧迫していたようです。

途中でAppStoreにDebugメニューを表示し、Reset Applicationを行っています。もしかしたら、これも効果があったのかもしれません。

Debugメニューを表示するにはタミーナルから次のコマンドを実行します。
defaults write com.apple.appstore ShowDebugMenu -bool true

追加:iMovieアップデートでも同様のことが発生。なんどかアップデートを試しても失敗。AppStoreのReset Application後に成功した。

参考:
[Sy] Xcodeアップデート時に「”購入済みページ”からダウンロードし直してください。」とエラーになる場合の対処方法
Xcode failed to download. Use the Purchases page to try again


おまけ:"アプリケーション"フォルダのサイズがえらく大きくなっていたので調べたところ、Chromeが古いバージョンが残っていた。古いバージョンを消しても今のところ問題なし。

- /Applications/Google Chromeを右クリック
- "パッケージの内容を表示"を選択
- Contents/versionsへ移動
- 古いバージョンを削除


ビルド時のsdkのディレクトリが見つからないエラー
  • Project選択
  • Build Settings
  • Framework Search Paths検索
  • 該当する設定を削除
私の場合はTest Projectにこのパスが含まれていました。

Directory not found for option '-F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/Developer/Library/Frameworks'

参考:
Getting Framework related warning in Xcode 7.0

Blockの引数エラー

エラーメッセージ
/Users/my/Desktop/Proj/WebTest/WebTest/NLWebView.m:103:35: Conflicting parameter types in implementation of 'webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:': 'void (^ _Nonnull __strong)(void)' vs 'void (^__strong _Nonnull)()'

後ろのほうの型宣言部分の
void (^__strong _Nonnull)()'
は、実際には
void (^)()'
となっています。

必要な修正は
void (^)(void)'
とすることですが、メッセージ通りに
void (^ _Nonnull __strong)(void)
としておいた方が無難かもしれません。

iOS Simulatorエラー

WKWebViewのテストプログラムをiOS Simulatorで何度か起動していたら、次のエラーが発生して、再起動できなくなった。

The operation couldn’t be completed. FBSOpenApplicationErrorDomain Code=3

iOSシミュレータの次のメニューからコンテンツをリセットする。
[iOS Simulator]-[Rest Contents and Settings...]-[Reset]

参考:
Unable to run app in Simulator エラーの対応方法


WKWebViewのhtmlロードでエラー発生

こんなエラーが発生。
The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.

Xcodeをアップデートしたらメッセージが変わった?
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

これまでのコーディングでレスポンスが戻らず無反応状態になっている場合は
- (void)webView:didFailProvisionalNavigation:withError:
を実装し、errorをチェックしてみる。

iOS9からApp Transport Security(ATS)が導入され、デフォルトではHTTPでの通信ができなくなっている。
App Transport Security policyはplistで変更することが可能で、従来の動作に戻すには次の設定を追加する。

<key>NSAppTransportSecurity</key>  
     <dict>  
          <key>NSAllowsArbitraryLoads</key><true/>  
     </dict>  

不特定なサイトにアクセスする必要がある場合は、この設定にするしかなさそう。
特定のサイトに限定できる場合はドメイン指定でアクセス可能にする。

参考:
これに関連したGoogleのページもありました。

The launch image set "LaunchImage" has 5 unassigned children.

どうやら、以前のProjectでlaunch image setを2つ作ってしまったようです。

Project選択
 => Target
 => App Icons and Launch Images
 => Launch Image Sources

のプルダウンリストを見ると"Launchimage"が選択状態になっているが、もうひとつLaunchimage−1がある。
こちらに変更すると以前の状態にもどった。

もともとのLaunchimageが適切に設定されておらず、別のLaunchimageを追加し、そちらを使っていた。Xcode7は元の名前の方を選択状態にしたため、ということのようです。


画面がフルスクリーンにならない

前項の続き。
Imageが適切に設定できていないと、Default@2x(640x960)のサイズが適用され、そのイメージサイズがWindowサイズに適用されているようだ。そのため、画面がフルスクリーンにならない。

iOS8以降のみのサポートであればLaunch ImageにStoryboadまたはxibを使うことができるが、iOS7以前のサポートが必要な場合は従来のstatic imageを使う必要がある。
新しいImage Setを追加すると、iPhone6(Plus)のイメージも割り当てらるようになる。

Project選択
 => Target
 => App Icons and Launch Images
 => Launch Image Sources
で設定を表示する。

画面上部がiPhone Portrait iOS8,9 / iPhone Landscape iOS 8,9になっていない場合は 右クリック => New Image Set で追加。
Launch Imagesを再設定する。古いImage Setは削除可能。
App Icons and Launch Images => Launch Image Sourcesのプルダウンで、追加したImage Setを選択する。

各々のイメージフォルダには適切なサイズのImageを割り当てないと有効にならない。
その場合、こんなWarningが出る。
launchimage/Default-568h@2x-1.png is 640x1136 but should be 750x1334.

また、Xcodeから起動するデバイスに適用可能なImageがひとつもないと、次のエラーが発生する。
The launch image set named "LaunchImage-1" did not have any applicable content.

そのデバイスに対応するImageが未設定あるいは不適切でも、適用可能なものがあればそれが割り当てられる。

たとえばiPhone 6用だけにiPhone5のImageをセットすると上のエラーになる。その状態でiPhone 5用のImageもセットするとエラーは消え、iPhone5のサイズが適用される。

参考:
Launch Image not showing up in iOS application (using Images.xcassets)

iOS 6もサポートする場合はこんなエラーもでる。

iPhone Retina (4-inch) launch image for iOS 6.x and prior is required when targeting releases prior to 7.0.

この場合は、こちらをご覧ください。

marble seijin の開発日記 まだまだある、xcode5での変更点


2015年9月26日土曜日

JQueryMobile listview サンプル 言語切り替え付き

jQuery Mobileのlistviewを使ったサンプルです。


JQueryMobile listview サンプル に言語切り替えを加えたものです。
デモサイト

言語切り替えなしバージョンと基本的に同じですが、表示文字列を"jp"、"en"のペアで設定し、言語選択に応じて表示を切り替えます。
言語選択はメインメニュー画面のラジオボタンでJapanese、Englishを選択することができます。

HTML一式ダウンロードできます。

index.html のタグ構造

<body onload="init();">
          <div data-role="page" id="topPage">
            <div data-role="header">
                <h1 class="headerText" id="topPageHeaderText">ホームページ</h1>
            </div>
            <!-- メインメニューを埋め込むdiv -->
            <div id="topMenu"></div>
            <!-- 言語切り替えラジオボタン -->
            <div id="langDiv">
                <input type="radio" name="lang" id="radio_jp" onclick="setLang('jp');">
                    <label for="radio_jp" id="label_jp">Japanese</label>
                <input type="radio" name="lang" id="radio_en" onclick="setLang('en');">
                    <label for="radio_en" id="label_en">English</label>
            </div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>
        
        <!-- メインメニューのページ1をタップしときに遷移するpage。サブメニューを表示する。 -->
        <div data-role="page" id="subMenuPage">
            <div data-role="header" data-add-back-btn="true" data-back-btn-text="戻る" onclick="return backClicked(this);">
                <h1 class="headerText">subMenuPage</h1>
            </div>
            <!-- サブメニューを埋め込むdiv -->
            <div id="subMenu"></div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>

        <!-- コンテンツ表示。メインメニューのページ3、およびサブメニューをタップしときに遷移しコンテンツを表示するpage。 -->
        <div data-role="page" id="contentPage" >
            <div data-role="header" data-add-back-btn="true" data-back-btn-text="戻る" onclick="return backClicked(this);">
                <h1 class="headerText">contentPage</h1>
            </div>
            <div data-role="main" class="ui-content" id="iframeDiv">
                <!-- コンテンツをロードするiframe -->
               <iframe id="iframe"></iframe>
            </div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>
    </body>

言語設定javascipt

//言語設定。ブラウザの言語設定が"ja"なら日本語、さもなければ英語。
            //index.htmlにハッシュ値がある場合は"ja"なら日本語、さもなければ英語。
            //index.htmlアクセス時にハッシュ値で言語設定可能。ハッシュ値をLangにセットする。
            //index.html#jpまたはindex.html#enでアクセス。
            var Lang = (navigator.language.indexOf("ja") < 0 ? "en" : "jp");
            if (location.hash.length > 0) {
                Lang = (location.hash.substr(1) == "jp" ? "jp" : "en");
            }
var Lang = location.hash.length == 0 ? "jp" : location.hash.substr(1);

//文字列のjp、enペアデータ。メインメニューのヘッダ、Backボタンの表示名。
           var label = {
                    "topPageHeader": {"jp":"ホームページへようこそ", "en":"Welcome to My Homepage"},
                    "backBtnText": {"jp":"戻る", "en":"Back"}
                };
//言語切り替えラジオボタンタップ/クリックで呼ばれ、メインメニューを再設定する。
           function setLang(lang) {
                Lang = lang;
                setMenu('topMenu', 'topMenuList');
           }

index.htmlのonloadイベントハンドラ

//body.onloadで呼ばれる。iframeのheight設定。
            //ブラウザのreloadボタンクリック時はindex.htmlをリロードする。
            function init() {
                if (document.getElementById("topMenuList") == null || $('#topMenuList').length == 0) {
                    location = "index.html";
                } else {
                    var h = $(window).height() - 20;
                    $('#iframe').css("height", h + "px");
                }
                $('#radio_' + Lang).prop('checked', true);
                $('input[name=lang]').checkboxradio('refresh');
            }

メインメニューを生成するjavascript

            //メインメニューのデータ。
            //text: リスト表示名
            //page: 遷移先ページ ID
            //src: iframeにロードするhtml、またはサブメニューのjson変数名。
            var topMenuList = [
                    {"jp":"ページ1 - サブメニュー", "en":"Page1 - SubMenu", "page":"#subMenuPage", "src":"subMenuList1"},
                    {"jp":"ページ2 - サブメニュー", "en":"Page2 - SubMenu", "page":"#subMenuPage", "src":"subMenuList2"},
                    {"jp":"ページ3 - コンテンツ", "en":"Page1 - Contents", "page":"#contentPage", "src":"Contents/page3"}
                    ];

            //index.htmlロード時に呼ばれ、メインメニューを生成。
            $(document).on('pagebeforeshow', '#topPage', function(){
                if ($('#topMenuList').length == 0) {
                    setMenu('topMenu', 'topMenuList');
                }
            });

メインメニュータップ/クリックで呼ばれサブメニューを生成するjavascript

//サブメニューのデータ。項目はメインメニューと同じ。 var subMenuList1 = [ {"jp":"1. 項目11", "en":"1. Item11", "page":"#contentPage", "src":"Contents/item11"}, {"jp":"2. 項目12", "en":"2. Item12", "page":"#contentPage", "src":"Contents/item12"}, {"jp":"3. 項目13", "en":"3. Item13", "page":"#contentPage", "src":"Contents/item13"} ]; //サブメニューのデータ。項目はメインメニューと同じ。 var subMenuList2 = [ {"jp":"1. 項目21", "en":"1. Item21", "page":"#contentPage", "src":"Contents/item21"}, {"jp":"2. 項目22", "en":"2. Item22", "page":"#contentPage", "src":"Contents/item22"}, {"jp":"3. 項目23", "en":"3. Item23", "page":"#contentPage", "src":"Contents/item23"} ]; //サブメニュー生成。異なるサブメニューで同じDIV構成を強要するため、表示のたびにlistviewを作り直す。 function setMenu(menuDivId, menuDataName) { if (menuDivId == "topMenu") { $('#topPageHeaderText').text(label.topPageHeader[Lang]); } var menuListId = '#' + menuDivId + 'List'; var ul = $(menuListId); if (ul.length > 0) { ul.empty(); } else { ul = $('<ul>').attr({'id':menuDivId + 'List','data-role':'listview'}).appendTo('#' + menuDivId); } var menuItemList = eval(menuDataName); for(var i=0; i < menuItemList.length; i++) { var li = $('<li>').appendTo(menuListId); var item = menuItemList[i]; $('<a data-transition="slide" href="' + item.page + '">') .attr('onclick', 'linkClicked("' + item.page + '","' + item[Lang] + '","' + item.src + '")') .text(item[Lang]).appendTo(li); } $(menuListId).listview().listview('refresh'); }

メニュー項目タップ/クリックで呼ばれ画面遷移を行うjavascript

//画面遷移の履歴。 //iframeを使用するとBackボタンを二度クリック/タップしないと画面遷移しなくなるため、独自に履歴を管理する。 var pageArray = new Array(""); //listviewの項目がクリック/タップされたときに呼ばれる。 //遷移先のヘッダのタイトル設定。現在のページをpageArrayにpushする。 function linkClicked(page, title, src) { if (page == "#subMenuPage") { setMenu("subMenu", src); } else if (src.length > 0) { $('#iframe').attr('src', src + '_' + Lang + '.html'); } $(page).find('.headerText').text(title); //'data-back-btn-text'を変更しても要素が再作成されるわけではないので表示が変わらない。 //refreshする方法が見つからなかったのでボタンのaタグのtextを直接変更する。 //$(page).find('div[data-role="header"]').attr('data-back-btn-text', label.backBtnText[Lang]); $(page).find('a[role="button"]').text(label.backBtnText[Lang]);
                pageArray.push(findParentPage($(event.srcElement)));
            }
        
            //paretnElementを辿り、data-role="page"の要素を返す。見つからない場合はnullを返す。
            function findParentPage(elm) {
                if (elm == null || elm.attr('data-role') == 'page') return elm;
                return findParentPage(elm.parent());
            }

戻るボタンで画面遷移を行うjavascript

//Backボタンタップ/クリック。 //戻り先pageIdがあればchangePageをよびfalseを返す。さもなけばtrueを返し、frameworkに任せる。 function backClicked(elm) { var page = pageArray.pop(); if (page == null) return true; var pageId = page.attr('id'); if (pageId.length == 0) return true; $.mobile.changePage('#'+pageId, { transition: "slide", reverse: true } ); return false; }
参照
JQueryMobile listview サンプル
HTML5+JavaScriptで画面をスライドさせて切り替える方法

JQueryMobile listview サンプル

jQuery Mobileのlistviewを使ったサンプルです。

自前で作ったメニュー表示/画面スライドをJQueryMobileで作り直したものです。
デモサイト
日本語/英語の言語切り替え付きもあります。

メインメニュー/サブメニュー/コンテンツといった多階層の画面遷移を組み込んでいます。
メニューのlistviewをjsonデータからjavascriptで動的に生成し、コンテンツ表示部分は各項目で共有し、コンテンツはiframeに外部のhtmlを読み込む構成になっています。
これにより画面遷移に関わる部分のhtmlがシンプルになり、コンテンツのメンテナンスも容易になります。

iframeを使っているため、backボタンの動作に手を加えています。これが最善かどうかはわかりませんが、ともかくも期待通りに動作しています。

HTML一式ダウンロードできます。

index.html のタグ構造

<body onload="init();">
        <div data-role="page" id="topPage">
            <div data-role="header">
                <h1 class="headerText">ホームページ</h1>
            </div>
            <!-- メインメニューを埋め込むdiv -->
            <div id="topMenu"></div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>
        
        <!-- メインメニューのページ1をタップしときに遷移するpage。サブメニューを表示する。 -->
        <div data-role="page" id="subMenuPage">
            <div data-role="header" data-add-back-btn="true" data-back-btn-text="戻る" onclick="return backClicked(this);">
                <h1 class="headerText">subMenuPage</h1>
            </div>
            <!-- サブメニューを埋め込むdiv -->
            <div id="subMenu"></div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>

        <!-- コンテンツ表示。メインメニューのページ3、およびサブメニューをタップしときに遷移しコンテンツを表示するpage。 -->
        <div data-role="page" id="contentPage" >
            <div data-role="header" data-add-back-btn="true" data-back-btn-text="戻る" onclick="return backClicked(this);">
                <h1 class="headerText">contentPage</h1>
            </div>
            <div data-role="main" class="ui-content" id="iframeDiv">
                <!-- コンテンツをロードするiframe -->
               <iframe id="iframe"></iframe>
            </div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>
    </body>

index.htmlのonloadイベントハンドラ
//body.onloadで呼ばれる。iframeのheight設定。
            //ブラウザのreloadボタンクリック時はindex.htmlをリロードする。
            function init() {
                if (document.getElementById("topMenuList") == null || $('#topMenuList').length == 0) {
                    location = "index.html";
                } else {
                    var h = $(window).height() - 20;
                    $('#iframe').css("height", h + "px");
                }
            }

メインメニューを生成するjavascript
//メインメニューのデータ。
            //text: リスト表示名
            //page: 遷移先ページ ID
            //src: iframeにロードするhtml、またはサブメニューのjson変数名。
            var topMenuList = [
                    {"text":"ページ1 - サブメニュー", "page":"#subMenuPage", "src":"subMenuList1"},
                    {"text":"ページ2 - サブメニュー", "page":"#subMenuPage", "src":"subMenuList2"},
                    {"text":"ページ3 - コンテンツ", "page":"#contentPage", "src":"Contents/page3.html"}
                    ];

            //index.htmlロード時に呼ばれ、メインメニューを生成。
            $(document).on('pagebeforeshow', '#topPage', function(){
                if ($('#topMenuList').length == 0) {
                    var ul = $('<ul>').attr({'id':'topMenuList','data-role':'listview'}).appendTo('#topMenu');
                    for(var i=0; i < topMenuList.length; i++) {
                    var li = $('<li>').appendTo('#topMenuList');
                    var item = topMenuList[i];
                    $('<a data-transition="slide" href="' + item.page + '">')
                        .attr('onclick', 'linkClicked("' + item.page + '","' + item.text + '","' + item.src + '")')
                        .text(item.text).appendTo(li);
                    }
                    $('#topMenuList').listview().listview('refresh');
                }
            });


メインメニュータップ/クリックで呼ばれサブメニューを生成するjavascript
//サブメニューのデータ。項目はメインメニューと同じ。
            var subMenuList1 = [
                   {"text":"1. 項目11", "page":"#contentPage", "src":"Contents/item11.html"},
                   {"text":"2. 項目12", "page":"#contentPage", "src":"Contents/item12.html"},
                   {"text":"3. 項目13", "page":"#contentPage", "src":"Contents/item13.html"}
                   ];

            //サブメニューのデータ。項目はメインメニューと同じ。
            var subMenuList2 = [
                   {"text":"1. 項目21", "page":"#contentPage", "src":"Contents/item21.html"},
                   {"text":"2. 項目22", "page":"#contentPage", "src":"Contents/item22.html"},
                   {"text":"3. 項目23", "page":"#contentPage", "src":"Contents/item23.html"}
                   ];

            //サブメニュー生成。異なるサブメニューで同じDIV構成を共用するため、表示のたびにlistviewを作り直す。
            function setMenu(name) {
                var ul = $('#subMenuList');
                if (ul.length > 0) {
                    ul.empty();
                } else {
                    ul = $('<ul>').attr({'id':'subMenuList','data-role':'listview'}).appendTo('#subMenu');
                }
                var menuList = eval(name);
                for(var i=0; i < menuList.length; i++) {
                    var li = $('<li>').appendTo('#subMenuList');
                    var item = menuList[i];
                    $('<a data-transition="slide" href="' + item.page + '">')
                    .attr('onclick', 'linkClicked("' + item.page + '","' + item.text + '","' + item.src  + '")')
                    .text(item.text).appendTo(li);
                }
                $('#subMenuList').listview().listview('refresh');
            }

メニュー項目タップ/クリックで呼ばれ画面遷移を行うjavascript
            //サブメニューのデータ。項目はメインメニューと同じ。
            //画面遷移の履歴。
            //iframeを使用するとBackボタンを二度クリック/タップしないと画面遷移しなくなるため、独自に履歴を管理する。
            var pageArray = new Array("");
            
            //listviewの項目がクリック/タップされたときに呼ばれる。
            //遷移先のヘッダのタイトル設定。現在のページをpageArrayにpushする。
            function linkClicked(page, title, src, backTo) {
                if (page == "#subMenuPage") {
                    setMenu(src);
                } else if (src.length > 0) {
                    $('#iframe').attr('src', src);
                }
                $(page).find('.headerText').text(title);
                pageArray.push(findParentPage($(event.srcElement)));
            }
        
            //paretnElementを辿り、data-role="page"の要素を返す。見つからない場合はnullを返す。
            function findParentPage(elm) {
                if (elm == null || elm.attr('data-role') == 'page') return elm;
                return findParentPage(elm.parent());
            }

戻るボタンで画面遷移を行うjavascript
            //Backボタンタップ/クリック。
            //戻り先pageIdがあればchangePageをよびfalseを返す。さもなけばtrueを返し、frameworkに任せる。
            function backClicked(elm) {
                var page = pageArray.pop();
                if (page == null) return true;
                var pageId = page.attr('id');
                if (pageId.length == 0) return true;

                $.mobile.changePage('#'+pageId, { transition: "slide", changeHash: true, reverse: true } );
                return false;
            }

参照
JQueryMobile listview サンプル 言語切り替え付き
HTML5+JavaScriptで画面をスライドさせて切り替える方法