今回のエンジニアブログを担当する加賀です。
UnityでiOSビルドを行うとXcodeプロジェクトが生成されるのですが、
その際にiOSのコードで使用する設定を、自動で登録するようにしてみました。
今回のクラスはUnityのPostProcessBuildで使用するクラスです。
また、今回のコードはUnity 4.5.0f6のProライセンスで確認しています。
この2種類の設定をinfo.plistに登録してみようと思います。
- 設定値
- URLスキーマ
.plistはXML形式で記述されているので、System.Xml内のクラスを使用して処理します。
using UnityEngine; using System.IO; using System.Xml; // info.plistの構成 // //<?xml version="1.0" encoding="UTF-8"?> //<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> //<plist version="1.0"> // <dict> // <key>...</key> // <string>...</string> // ... // </dict> //</plist> public class PlistMod { // すべての設定の直接の親であるdictエレメントを取得 private static XmlNode FindPlistDictNode(XmlDocument doc) { var cur = doc.FirstChild; while (cur != null) { if (cur.Name.Equals ("plist") && cur.ChildNodes.Count == 1) { var dict = cur.FirstChild; if (dict.Name.Equals ("dict")) { return dict; } } cur = cur.NextSibling; } return null; } // すでにそのkeyが存在しているか? // dict:親ノード private static bool HasKey(XmlNode dict, string keyName) { var cur = dict.FirstChild; while (cur != null) { if (cur.Name.Equals ("key") && cur.InnerText.Equals (keyName)) { return true; } cur = cur.NextSibling; } return false; } // 子エレメントを追加 // elementName:<...>の<>の中の文字列 // innerText:<key>...</key>のタグで囲まれた文字列 private static XmlElement AddChildElement(XmlDocument doc, XmlNode parent, string elementName, string innerText = null) { var newElement = doc.CreateElement (elementName); if (!string.IsNullOrEmpty (innerText)) { newElement.InnerText = innerText; } parent.AppendChild (newElement); return newElement; } // 指定したkeyに対応する値を更新する // <key>KEY_TEXT</key> // <ELEMENT_NAME>VALUE</ELEMENT_NAME> // 以上の構造の場合のみ正常に動作 // key:KEY_TEXT // elementName:ELEMENT_NAME // value:VALUE private static XmlNode UpdateKeyValue(XmlNode node, string key, string elementName, string value){ // まず<key>...</key>のノードを取得 var keyNode = GetChildElement (node, "key", key); if (keyNode.NextSibling != null && keyNode.NextSibling.Name.Equals (elementName)) { // 取得したkeyノードの次のノードのelementNameが指定された文字列だった場合、値を更新する keyNode.NextSibling.InnerText = value; return keyNode; } return null; } // 子エレメントを取得 // elementName:<...>の<>の中の文字列 // innerText:<key>...</key>のタグで囲まれた文字列 private static XmlNode GetChildElement(XmlNode node, string elementName, string innerText=null) { var cur = node.FirstChild; while (cur != null) { if (cur.Name.Equals (elementName)) { if ((innerText == null && cur.InnerText == null) || (innerText != null && cur.InnerText.Equals (innerText))) { return cur; } } cur = cur.NextSibling; } return null; } // info.plistのあるディレクトリパスと設定値を受け取り、info.plistに設定を登録する public static void UpdatePlist(string path, string val) { // info.plistを読み込む string fullPath = Path.Combine (path, "info.plist"); var doc = new XmlDocument(); doc.Load (fullPath); // すべての設定の直接の親であるdictエレメントを取得する var dict = FindPlistDictNode (doc); if (dict == null) { Debug.LogError ("Error plistの解析に失敗 パス:" + fullPath); return; } // 1. 設定値 // key:sample_key として登録します // // 登録後の例 // <key>sample_key</key> // <string>val</string> if(!HasKey (dict, "sample_key")) { AddChildElement (doc, dict, "key", "sample_key"); AddChildElement (doc, dict, "string", val); } else { UpdateKeyValue (dict, "sample_key", "string", val); } // 2. URLスキーマ // <key>CFBundleURLTypes</key> // <array> // <dict> // <key>CFBundleURLName</key> // <string>BUNDLE_IDENTIFIER</string> // <key>CFBundleURLSchemes</key> // <array> // <string>BUNDLE_IDENTIFIER</string> // </array> // </dict> // ... // </array> { XmlNode urlSchemeTop = null; if (!HasKey (dict, "CFBundleURLTypes")) { AddChildElement (doc, dict, "key", "CFBundleURLTypes"); urlSchemeTop = AddChildElement (doc, dict, "array"); } else { //すでにkey:CFBundleURLTypesが存在している //key:CFBundleURLTypesを取得 var urlScheme = GetChildElement (dict, "key", "CFBundleURLTypes"); urlSchemeTop = urlScheme.NextSibling; } //存在確認・更新 bool isExist = false; foreach (XmlNode urlDict in urlSchemeTop.ChildNodes) { if (urlDict.Name.Equals ("dict") && urlDict.HasChildNodes) { //子がdict構造であり、更に子を持っている var urlUrlName = GetChildElement (urlDict, "key", "CFBundleURLName"); if (urlUrlName != null && urlUrlName.NextSibling != null) { //key:CFBundleURLNameの要素があり、その次の要素も存在する var urlUrlString = urlUrlName.NextSibling; if (urlUrlString.Name.Equals ("string") && urlUrlString.InnerText.Equals (PlayerSettings.bundleIdentifier)) { //同じBundleIDの設定が見つかった isExist = true; //設定の上書き urlUrlString.InnerText = PlayerSettings.bundleIdentifier; break; } } } } if (!isExist) { //存在していない場合のみ追加 var urlSchemeDict = AddChildElement (doc, urlSchemeTop, "dict"); AddChildElement (doc, urlSchemeDict, "key", "CFBundleURLName"); AddChildElement (doc, urlSchemeDict, "string", PlayerSettings.bundleIdentifier); AddChildElement (doc, urlSchemeDict, "key", "CFBundleURLSchemes"); var innerArray = AddChildElement (doc, urlSchemeDict, "array"); { AddChildElement (doc, innerArray, "string", PlayerSettings.bundleIdentifier); } } } // 保存 doc.Save(fullPath); // <!DOCTYPE の行を書き換えて保存してしまうため、修正する string textPlist = string.Empty; using (var reader = new StreamReader (fullPath)) { textPlist = reader.ReadToEnd (); } // 本来の行が存在していれば処理終了 int fixupStart = textPlist.IndexOf ("<!DOCTYPE plist PUBLIC", System.StringComparison.Ordinal); if (fixupStart <= 0) { return; } int fixupEnd = textPlist.IndexOf ('>', fixupStart); if (fixupEnd <= 0) { return; } // 修正処理 string fixedPlist = textPlist.Substring (0, fixupStart); fixedPlist += "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"; fixedPlist += textPlist.Substring (fixupEnd+1); using (var writer = new StreamWriter (fullPath, false)) { writer.Write (fixedPlist); } } }
95行目からの[UpdatePlist]関数が実装の本体です。
値を追加するときに[HasKey]関数を呼んで、keyが存在していない場合のみ
値を追加していますが、これは値の2重登録を防ぐためです。
info.plistはiOSビルド時に毎回作り直されるため、値の変更が反映されないことはありません。
keyが存在している場合は、再設定するようにすれば、値の変更が反映されます。
まとめ
ビルドするたびに毎回手動で設定し直すことは、非常に手間がかかり、時間が無駄になります。
設定する数が増えれば設定漏れも起きやすくなります。
自動で設定するようにコードを記述しておけば、そのような心配もないでしょう。
修正・変更点
2014/06/30
・すでにXcodeプロジェクトが存在している状態で再度ビルドすると、URLスキーマの設定項目が増えてしまうのを修正。
・すでにXcodeプロジェクトが存在している状態で再度ビルドすると、値が更新されないことがあるのを修正。
・StreamReader、StreamWriterの部分にusingを使用するように変更。