carthage SWIFT_VERSION '5.0' is unsupported, supported versions are: 3.0, 4.0, 4.2.エラー
タイトルのエラーの原因と解決策のメモ
今回は「Smile-Lock」というパスコードの入力画面を簡単に実装できるライブラリを使おうとして発生。プロジェクトの都合でxcodeのバージョンは10.1でSwift4.2を使用。
Cartfile
github "recruit-lifestyle/Smile-Lock"
carthage update
$ carthage update Smile-Lock --platform iOS *** Checking out Smile-Lock at "3.0.8" *** xcodebuild output can be found in /var/folders/f8/3j706yps6t97pj4d6b49f5200000gn/T/carthage-xcodebuild.4fxFE3.log *** Building scheme "SmileLock" in SmileLock-Example.xcworkspace Build Failed Task failed with exit code 65: (省略) This usually indicates that project itself failed to compile. Please check the xcodebuild log for more details: /var/folders/f8/3j706yps6t97pj4d6b49f5200000gn/T/carthage-xcodebuild.4fxFE3.log
エラーメッセージのログを見てみる
$ cat /var/folders/f8/3j706yps6t97pj4d6b49f5200000gn/T/carthage-xcodebuild.4fxFE3.log (省略) note: Using new build system note: Planning build note: Constructing build description Build system information error: SWIFT_VERSION '5.0' is unsupported, supported versions are: 3.0, 4.0, 4.2. (in target 'SmileLock') Build system information error: SWIFT_VERSION '5.0' is unsupported, supported versions are: 3.0, 4.0, 4.2. (in target 'SmileLock') ** ARCHIVE FAILED **
Swift5なんて使ってないのにと思ってたら勘違いだった。
Carthage/Checkouts/Smile-Lock/SmileLock-Example/SmileLock-Example.xcworkspace の 「Swift Language Version」がSwift5.0が指定されてた
Cartfile
github "recruit-lifestyle/Smile-Lock" "3.0.7"
Swift5対応される前のバージョンを指定してcarthage updateしたらうまくいった。
けどXcodeとSwiftのバージョンそろそろあげないとな...以上です
android ListViewでContextMenuを使う
今回やりたかったことは、ListViewの各行を長押ししたらコンテキストメニューを表示して選択されたメニューによって処理を行うということ
http://kimagureneet.hatenablog.com/entry/2019/02/19/031844
ListViewはこちらに書いたものを使用する
registerForContextMenuでListViewを設定するとonCreateContextMenuが呼ばれるのでこれをオーバーライドしてメニューアイテムを表示するだけだと思っていたらonCreateContextMenuが呼ばれずにハマった...解決策はListViewの行アイテムにisLongClickable = trueをセットすることでした
MainActivity.kt
class MainActivity : AppCompatActivity() { ・・・ override fun onCreate(savedInstanceState: Bundle?) { ・・・ // ListViewにContextMenuを登録 registerForContextMenu(listView) } override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) { super.onCreateContextMenu(menu, v, menuInfo) val info = menuInfo as AdapterView.AdapterContextMenuInfo menuInflater.inflate(R.menu.comment_context, menu) } override fun onContextItemSelected(item: MenuItem?): Boolean { val menuInfo = item!!.menuInfo as AdapterView.AdapterContextMenuInfo // メニューを表示した行番号を取得 // menuInfo.position when(item.itemId) { R.id.edit -> { // 編集 } R.id.delete -> { // 削除 } } return true } }
res/menu/user_context.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/edit" android:title="編集"/> <item android:id="@+id/delete" android:title="削除"/> </menu>
Adapter/UserAdapter.kt
class UserAdapter(val context: Context, val users: List<User>): BaseAdapter() { ・・・ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { val view = layoutInflater.inflate(R.layout.user_item, parent, false) ・・・ // ★長押しを有効化 view.isLongClickable = true return view } }
以上です
android ステータスバーの文字色を変更する
今回やりたかったことはステータスバーの色を白にして、文字色を黒にしたかったが文字色の変え方が見つからない...
https://stackoverflow.com/questions/30464234/android-lollipop-set-status-bar-text-color
こちらによるとAndroid6以降なら「android:windowLightStatusBar」を指定することでステータスバーの色によって文字色をいい感じに変更してくれるみたい
Android5以下でのやり方がわからなかったので、Androidのバージョンを確認して5以下だったらステータスバーの色を黒っぽくすることにした...
res/values/styles.xml
<resources xmlns:tools="http://schemas.android.com/tools" tools:locale="es"> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> ・・・ <item name="android:statusBarColor">@color/colorPrimaryDark</item> <item name="android:windowLightStatusBar" tools:targetApi="M">true</item> </style> ・・・ </resources>
MainActivity.kt
class MainActivity : AppCompatActivity() { ・・・ // ステータスバーの色を黒っぽく if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { this.window.statusBarColor = ContextCompat.getColor(this, R.color.abc_primary_text_material_light) } }
以上です
ionic cordova-plugin-ionic-webviewプラグインでアプリインストール直後だけFileプラグインでのファイル処理が動作しなかった原因
誰にも必要とされないと思われる情報
cordova-plugin-ionic-webviewプラグインのバージョンを2.4から4.01にバージョンアップした際になぜかアプリインストール直後だけファイルプラグインによるファイルの読み込みがうまくいかなかった。2回目の起動以降はうまくいくのに、1からプロジェクトから作り直したらうまくいくのに。。各プラグインのバージョンとnpmでインストールされるパッケージのバージョンもすべてそろえたのになぜか...ちなみに試したコードは以下だが、readAsTextのSuccessもFailedもログ出力されない...
export class MyApp { constructor( platform: Platform, statusBar: StatusBar, public file: File ) { platform.ready().then(() => { let dir = cordova.file.dataDirectory; let fileName = 'test'; let json = JSON.stringify({ session:'xxxxxxxxx1' }); this.file.writeFile(dir, fileName, json, { replace: true }).then((result) => { console.log('Success writeFile'); this.file.checkFile(dir, fileName).then((result) => { console.log('Success checkFile'); this.file.readAsText(dir, fileName).then((result) => { console.log('Success readAsText'); }).catch((err) => { console.log('Failed readAsText'); }); }).catch((err) => { console.log('Failed checkFile'); }); }).catch((err) => { console.log('Failed checkFile'); }); }); } }
結論
src/index.htmlでサードパーティのライブラリを読み込んでいてそれが、ionic関連のjsより先に読み込んでいたのが原因でした。これを1番最後に読み込むように修正した動いた。インストール直後だけうまくいかないのは不明だが...こんなので1日つぶした
ionicでのハイブリッドアプリ開発のメリット・デメリットについてそろそろまとめとく
3年くらい前からionicを使い始めて良いところと悪いところが少しつつわかってきたのでメモしておく
メリッド
学習コストが低い & 開発リソースが確保しやすい
swiftやjavaなどでの実装となるとそれぞれのプログラム言語の学習する必要がある。言語自体の学習コストよりもios、androidそれぞれのUIKitというか概念を勉強する必要がありやはり慣れるのにそれなりに時間がかかると思う。その点、ionicだとWebの技術(HTML+CSS+Javascript) がある程度わかっていればなんとなく作れてしまう。またアプリ作り始めてもすごい時間がかかったりすると挫折しがちだがなんとなくでも動くものが作れればあとは慣れなのでこれはメリットかと思う。また、ネイティブだとちょっとしたレイアウト調整や文言変更でもそこそこ経験が必要になるがHTML+CSSだと直感的にもわかりやすく作業できる人が多いのでそういう意味でリソースが確保しやすいと思う。
デメリット
開発環境のバージョンアップが速い
これはメリットというか良いことだとは思うがあえて辛いところもあるということでデメリットの方に書いた。ionic1から2に上がったとき、angular1も2への変更がありangular1と2は全く別物となっていてバージョンアップ=アプリの作り直し ということとなってしまいやはり辛かった。ionicに加えてcordova自体のバージョンが上がるとプロジェクトのファイル構成も変わってしまい、これらを新しい環境にあわせて継続的に対応させ続けていくとなるのでやはり大変。長く続くアプリでは開発環境のバージョンアップも視野にいれないとならない。Swiftもけっこうバージョンアップしてるがangularやcordovaの変更に伴う修正作業と比べたらだいぶ楽なんじゃないかと、、
WebViewベースのガワアプリの開発が難しい
cordova自体の仕組み上の問題だが、アプリの一部にWebViewを組み込むようなアプリの実装が難しい。例えば画面の下タブはionicのコンポーネントを使い、タブを選択するとWebViewが切り替わるようななんてことなさそうなアプリだがこれが難しい。ionicだけで何ができて何が難しいかというのは判断はある程度慣れだとは思うがこういうのが出てくると大変
結論
何年もメンテし続ける必要があるようなアプリだと最終的にかかる工数もネイティブの方が少なくなるんじゃないかと思う。また、失敗が許されないような案件もネイティブの方が確実だと思う。逆にプロタイプのアプリや、画面数が少ないので開発環境のバージョンアップがあってもサクッと作り直せるアプリだとionicがいいのかなと思う。あと今あげたようなデメリットを許容してくれることもけっこうあると思うのでそういう場合もionicで作るのはありだと思う。ionicもけっこうたつのでだいぶ成熟してきた印象はある。flutterには期待してるけど正直まだ勉強中なのでわからん。
結論、どっちがいいかはケースバイケース。けど趣味でアプリ作るなら自分はionicかも、、以上です
ios WKWebViewでWebViewアプリの作り方まとめとく
iOSで簡単なWebViewアプリの作るときに必要そうなことまとめておく。以下のiOS版
android WebViewアプリの作り方まとめとく - とりあえずphpとか
WKWebViewを作ってサイトを表示する
ViewController
import UIKit import WebKit class ViewController: UIViewController { var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() webView = WKWebView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)) view.addSubview(webView) let request = URLRequest(url: URL(string: "https://www.google.com")!) webView.load(request) } }
クッキーの値を取得する
スワイプでブラウザバック
webView.allowsBackForwardNavigationGestures = true
メールや電話の起動リンクに対応
class ViewController: UIViewController, WKNavigationDelegate { ・・・ override func viewDidLoad() { ・・・ webView.navigationDelegate = self ・・・ } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { guard let url = navigationAction.request.url else { return } if navigationAction.navigationType == .linkActivated { if url.scheme == "mailto" || url.scheme == "tel" { UIApplication.shared.open(url, options: [:], completionHandler: nil) decisionHandler(.cancel) return } } decisionHandler(.allow) } }
SSLエラーやhttpとhttpsの混在ページを読み込む
info.plist
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
ViewController
class ViewController: UIViewController, WKNavigationDelegate { ・・・ func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { if challenge.protectionSpace.host == "example.com" && challenge.protectionSpace.serverTrust != nil { let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) completionHandler(.useCredential, credential) } else { completionHandler(.performDefaultHandling, nil) } } }
Pull To Refreshを実装する
class ViewController: UIViewController, WKNavigationDelegate { var webView: WKWebView! var refreshControll: UIRefreshControl! override func viewDidLoad() { ・・・ refreshControll = UIRefreshControl() self.webView.scrollView.refreshControl = refreshControll refreshControll.addTarget(self, action: #selector(ViewController.refresh(sender:)), for: .valueChanged) ・・・ } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.refreshControll.endRefreshing() } @objc func refresh(sender: UIRefreshControl) { guard let url = webView.url else { return } webView.load(URLRequest(url: url)) } }
他のサイトは表示したくない
class ViewController: UIViewController, WKNavigationDelegate { ・・・ func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { ・・・ if url.host != "example.com" { decisionHandler(.cancel) return } decisionHandler(.allow) } }
全ソース
import UIKit import WebKit class ViewController: UIViewController, WKNavigationDelegate { var webView: WKWebView! var refreshControll: UIRefreshControl! override func viewDidLoad() { super.viewDidLoad() webView = WKWebView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)) webView.navigationDelegate = self webView.allowsBackForwardNavigationGestures = true refreshControll = UIRefreshControl() self.webView.scrollView.refreshControl = refreshControll refreshControll.addTarget(self, action: #selector(ViewController.refresh(sender:)), for: .valueChanged) view.addSubview(webView) let url = "https://example.com" let request = URLRequest(url: URL(string: url)!) webView.load(request) } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { guard let url = navigationAction.request.url else { return } if navigationAction.navigationType == .linkActivated { if url.scheme == "mailto" || url.scheme == "tel" { UIApplication.shared.open(url, options: [:], completionHandler: nil) decisionHandler(.cancel) return } } if url.host != "example.com" { decisionHandler(.cancel) return } decisionHandler(.allow) } func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { if challenge.protectionSpace.host == "example.com" && challenge.protectionSpace.serverTrust != nil { let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) completionHandler(.useCredential, credential) } else { completionHandler(.performDefaultHandling, nil) } } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.refreshControll.endRefreshing() } @objc func refresh(sender: UIRefreshControl) { guard let url = webView.url else { return } webView.load(URLRequest(url: url)) } }
android WebViewアプリの作り方まとめとく
ただWebサイトを表示するだけのアプリを作ろうとするだけでも意外と実装しないとならないことが多いのがアプリの世界?次回のために作り方をまとめておく
WebViewを作ってサイトを表示する
指定URLを表示するだけのアプリ。JavaScriptは有効にしておく
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
MainActivity.java
public class MainActivity extends AppCompatActivity { WebView mWebView; @SuppressLint("SetJavaScriptEnabled") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWebView = (WebView)findViewById(R.id.webView); mWebView.loadUrl("https://www.google.com"); mWebView.getSettings().setJavaScriptEnabled(true); } }
activity_main.xml
<WebView android:id="@+id/webView" android:layout_width="match_parent" android:layout_height="match_parent" />
リンクに対応する
MainActivity.java
protected void onCreate(Bundle savedInstanceState) { ・・・ mWebView.setWebViewClient(new CustomWebViewClient()); } class CustomWebViewClient extends WebViewClient { }
クッキーの有効化
MainActivity.java
protected void onCreate(Bundle savedInstanceState) { ・・・ mWebView.setDomStorageEnabled(new CustomWebViewClient()); }
クッキーの値を取得する
class CustomWebViewClient extends WebViewClient { ・・・ @Override public void onPageCommitVisible(WebView view, String url) { String cookie = CookieManager.getInstance().getCookie(url); Log.d("",cookie); } }
戻るボタンでブラウザバック
MainActivity.java
public class MainActivity extends AppCompatActivity { ・・・ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) { mWebView.goBack(); } return true; } }
メールや電話の起動リンクに対応
class CustomWebViewClient extends WebViewClient { public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("mailto:") || url.startsWith("tel:")) { Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse(url)); startActivity(intent); return true; } else { return false; } } }
SSLエラーやhttpとhttpsの混在ページを読み込む
もちろん不要なら対応しない方がいいけど
・・・ mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); ・・・ class CustomWebViewClient extends WebViewClient { ・・・ @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); } }
Pull To Refreshを実装する
activity_main.xml
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/refresh" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/webView" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.SwipeRefreshLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity { ・・・ SwipeRefreshLayout mRefresh; protected void onCreate(Bundle savedInstanceState) { ・・・ mRefresh = (SwipeRefreshLayout) findViewById(R.id.refresh); mRefresh.setOnRefreshListener(refreshListener); } class CustomWebViewClient extends WebViewClient { ・・・ @Override public void onPageCommitVisible(WebView view, String url) { MainActivity.this.mRefresh.setRefreshing(false); } } }
他のサイトは表示したくない
class CustomWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (Uri.parse(url).getHost().equals("example.com")) { return false; } else { return true; } } }
input type fileに対応する
videoタグのフルスクリーンに対応する
全ソース
MainActivity.java
package xxx.yyy.zzz; import android.annotation.SuppressLint; import android.content.Intent; import android.net.Uri; import android.net.http.SslError; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.webkit.CookieManager; import android.webkit.SslErrorHandler; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; public class MainActivity extends AppCompatActivity { WebView mWebView; SwipeRefreshLayout mRefresh; @SuppressLint("SetJavaScriptEnabled") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRefresh = (SwipeRefreshLayout) findViewById(R.id.refresh); mRefresh.setOnRefreshListener(refreshListener); mWebView = (WebView)findViewById(R.id.webView); mWebView.loadUrl("https://www.google.com"); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.getSettings().setDomStorageEnabled(true); mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); mWebView.setWebViewClient(new CustomWebViewClient()); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) { mWebView.goBack(); } return true; } private SwipeRefreshLayout.OnRefreshListener refreshListener = new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { mWebView.reload(); } }; class CustomWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("mailto:") || url.startsWith("tel:")) { Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse(url)); startActivity(intent); return true; } else if (Uri.parse(url).getHost().equals("example.com")) { return false; } else { return true; } } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); } @Override public void onPageCommitVisible(WebView view, String url) { String cookie = CookieManager.getInstance().getCookie(url); Log.d("",cookie); MainActivity.this.mRefresh.setRefreshing(false); } } }
activity_main.xml
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/refresh" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/webView" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.SwipeRefreshLayout>
こんなの実装しないと動かないのかというものがいくつかあるけどそういうものと思うしかない。以上です