- 投稿日:2019-08-30T23:03:37+09:00
Flutter 1.7.x 環境をmacOS上に構築する
なぜこの文章を書いているか?
この文章(Flutter 1.2.x 環境をmacOS上に構築する)を過去に書き間が空いたのと再度Flutterを触り始めたので環境をまっさらな状態から作り直した作業ログとしてまとめたのが以下です。最近はLaravelとかNuxt.jsとかTypeScriptとかいじってますが、そろそろ他のものもということで。
今回はできる限り公式サイトの順番で環境を作っていこうと思います。
https://flutter.dev/docs/get-started/install/macos環境(2019/08/30(金)現在)
- macOS Mojave(10.14.6)
- Xcode 10.3
- IntelliJ IDEA Ultimate 2019.2.1
- Flutter 1.7.8 + Hotfix 4 Stable
インストール
Tools
公式サイトに書かれている必須ツールは以下の通り。
- bash
- curl
- git 2.x
- mkdir
- rm
- unzip
- which
このうち、macOS Mojaveに標準で入っていないのは
git
なのでインストールする。command$ xcode-select --install「"xcode-select"コマンドを実行するには、コマンドライン・デベロッパ・ツールが必要です。ツールを今すぐインストールしますか?」とウィンドウが表示されたら
インストール
をクリック。
「Command Line Tools使用許諾契約」画面が表示されたら内容を読んだ上で同意する
をクリック。インストールが完了したら以下でバージョンが表示されればOK。
command$ git --version git version 2.20.1 (Apple Git-117)Flutter SDKのインストール
公式サイトの
flutter_macos_v1.7.8+hotfix.4-stable.zip
と書かれている青いボタンをクリックしてZipファイルをダウンロードします。
ダウンロードしたら、~/development
以下に展開します。command$ mkdir ~/development $ cd ~/development $ unzip ~/Downloads/flutter_macos_v1.7.8+hotfix.4-stable.zip
~/development/flutter/bin/
が作られるのでPATHを通します。command$ echo 'export PATH=$PATH:~/development/flutter/bin' >> ~/.profile $ exec $SHELL -l以下でバージョン情報が表示されればOK。
command$ flutter --version Flutter 1.7.8+hotfix.4 • channel stable • https://github.com/flutter/flutter.git Framework • revision 20e59316b8 (6 weeks ago) • 2019-07-18 20:04:33 -0700 Engine • revision fee001c93f Tools • Dart 2.4.0以下でバイナリをダウンロード(キャッシュ)しておきます。瞬間で終わるので実際に何か行われている感じはしないのですが、念の為。
command$ flutter precache
Flutterの実行に必要な環境が揃っているか以下でチェックします。
途中「"gen_snapshot"はこのMac用に最適化されていないため、アップデートが必要です。」と出ますのでOK
をクリックします。command$ flutter doctor -v [✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Mac OS X 10.14.6 18G95, locale ja-JP) • Flutter version 1.7.8+hotfix.4 at /Users/foobar/development/flutter • Framework revision 20e59316b8 (6 weeks ago), 2019-07-18 20:04:33 -0700 • Engine revision fee001c93f • Dart version 2.4.0 [✗] Android toolchain - develop for Android devices ✗ Unable to locate Android SDK. Install Android Studio from: https://developer.android.com/studio/index.html On first launch it will assist you in installing the Android SDK components. (or visit https://flutter.dev/setup/#android-setup for detailed instructions). If the Android SDK has been installed to a custom location, set ANDROID_HOME to that location. You may also want to add it to your PATH environment variable. [✗] Xcode - develop for iOS and macOS ✗ Xcode installation is incomplete; a full installation is necessary for iOS development. Download at: https://developer.apple.com/xcode/download/ Or install Xcode via the App Store. Once installed, run: sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer ✗ CocoaPods not installed. CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side. Without CocoaPods, plugins will not work on iOS or macOS. For more info, see https://flutter.dev/platform-plugins To install: brew install cocoapods pod setup ! Brew can be used to install CocoaPods. Download brew at https://brew.sh/. [✗] iOS tools - develop for iOS devices ✗ libimobiledevice and ideviceinstaller are not installed. To install with Brew, run: brew update brew install --HEAD usbmuxd brew link usbmuxd brew install --HEAD libimobiledevice brew install ideviceinstaller ✗ ios-deploy not installed. To install: brew install ios-deploy ! Brew can be used to install tools for iOS device development. Download brew at https://brew.sh/. [!] Android Studio (not installed) • Android Studio not found; download from https://developer.android.com/studio/index.html (or visit https://flutter.dev/setup/#android-setup for detailed instructions). [!] Connected device ! No devices available ! Doctor found issues in 5 categories.色々と未インストール(環境未整備)だと怒られているので順に入れていきます。
Xcodeをインストール
App Store
を起動し、検索窓にxcode
と入力して検索しインストールします。時間がかかるので私は一晩寝かしました。すでにコマンドラインツールはインストール済みですが、公式ドキュメントのとおりにインストールしたXcodeから設定しておきます。
command$ sudo xcode-select --switch /Applications/Xcode.app/Contents/DeveloperXcodeのライセンス承諾を行うために以下のコマンドを実行します。Enterを押した後、Spaceキーで最後まで読み、入力を求められたら
agree
とキー入力してEnterを押してライセンス条項に対し承諾をします。command$ sudo xcodebuild -licenseこの状態で以下のコマンドが実行できgitのバージョンが表示されればOKです。
command$ git --version git version 2.20.1 (Apple Git-117)iOSシミュレータの設定
以下のコマンドを実行しiOSシミュレータを起動します。
command$ open -a Simulator
Hardware
>Device
>Manage Devices
を選択し、「Install additional required components?」画面が出たらInstall
をクリックし、パスワードの入力画面が出るのでMacのアカウントのパスワードを入力しOK
をクリックします。インストールが起動したらXcodeが起動するので、一旦iOSシミュレータを終了して再度起動します。私の環境ではiPhone XRのシミュレータが自動的にスタートしました。公式ドキュメントには「64bitのiPhone5S以降をセットアップしてね」とありますが現在のiOSシミュレータではこれ以上特に作業は必要ありません。
テストアプリを作ってみる
公式ドキュメントではここで唐突にテストアプリを作る項目が出てきます。iOSシミュレータがセットアップできているかなどのチェック用でしょうか?ここでは言われたとおりに作ってみることにします。
ターミナルで適当な場所に作業用ディレクトリを作成し、そこで作業を進めます。
command$ mkdir ~/development/project $ cd ~/development/project $ flutter create my_app $ cd my_app $ flutter run No connected devices. Run 'flutter emulators' to list and start any available device emulators. If you expected your device to be detected, please run "flutter doctor" to diagnose potential issues, or visit https://flutter.dev/setup/ for troubleshooting tips.怒られました。どうやらiOSシミュレータを終了していたためのようです。iOSシミュレータを起動しiPhone XRのシミュレータが起動している状態でもう一度
flutter run
を実行してみます。
Flutter Demo Home Page
という画面がシミュレータに表示されればOKです。ターミナルでq
を入力するとアプリが終了します。iOSデバイス(実機)にデプロイするための設定
Homebrewをインストールするように書かれているのでそのとおりにします。Qiitaを読んでらっしゃるMacユーザならインストール済みと思われますので(偏見)説明は省きます。
command$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" $ brew updateiOSデバイス(実機)にデプロイするために必要なツール群を
brew
コマンドを使ってインストールしていきます。私の環境ではbrew link usbmuxd
で「リンク済み」の旨のメッセージが出ましたが特に問題ないと思われます。command$ brew install --HEAD usbmuxd $ brew link usbmuxd $ brew install --HEAD libimobiledevice $ brew install ideviceinstaller ios-deploy cocoapods $ pod setup先ほど作ったテストアプリをiOSデバイスにデプロイできるようにするためにプロジェクトにサインをします。基本公式ページのとおりですが簡単にまとめます。
- ターミナルで
open ios/Runner.xcworkspace
としてXcodeを開きます- iOSデバイスをMacに接続して「信頼するか?」など聞かれたら通常の方法でMacとiOSデバイスが通信できる状態にします
- Xcodeの左上のプルダウンが
Runner > iPhone XR
となっていたら、プルダウンをクリックして接続したiOSデバイスを選択します- 左ペインの一番上にある
Runnerプロジェクト
をクリックして選択します- Xcode 9と10は真ん中のペインの
General > Signing > Team
を、Xcode 11はSigning & Capabilities > Team
にあるTeam
メニューに自分のチーム(Apple ID)を設定します- 自分の環境では「You currently don't have access to this membership resource. To resolve this issue, agree to the latest Program License Agreement in your developer account.」と出ていたのでDev Centerにアクセス、その後
account
にメニューから移動し「The Apple Developer Program License Agreement has been updated.」と赤背景でメッセージが出ているところから「Review Agreement」をクリック、内容を確認しチェックボックスをチェック、「I Agree」ボタンをクリック、最後にXcodeに戻ってエラーメッセージの下の「Try again」ボタンをクリックしました- iOSシミュレータを終了した上でターミナルから
flutter run
を実行- 先ほどと同じ
Flutter Demo Home Page
がiOSデバイスで表示されたらOKですAndroid Studioの代わりにIntelliJ IDEA Ultimateをインストールする
公式サイトではAndroid Studioをインストールしていますが、私はIntelliJ IDEAを普段使っているのでそちらで利用可能なように設定します。
まず、
Homebrew Cask
を使ってJetBrains ToolBox
をインストールします。command$ brew cask install jetbrains-toolboxApplicationから
JetBrains ToolBox
を起動し、JetBrainsアカウント
でログイン後、IntelliJ IDEA Ultimateをインストールします。
インストールが終わったらIntelliJ IDEAを起動します。初期設定の時にAndroid開発の追加インストールがされているはずですので、Welcome画面からCreate New Project
を選択し、New Project
画面でAndroid
を選択するとConfigure Android SDK
画面が表示されるのでInstall SDK
ボタンをクリックします。
Next
をクリックしていき最後にFinish
をクリックすると${USER_HOME}/Library/Android/sdk/
以下にAndroid SDK
がインストールされます。Choose your project
画面に戻ったら今回はcancel
をクリックしてキャンセルします。Androidデバイスの設定
公式ページを見ながら順に設定していきます。
- Androidデバイスの
開発者オプション
とUSBデバッグ
を有効にします。デバイスやOSのバージョンによりますが通常はビルド番号を何回かタップすると開発者オプション
が有効になり、設定 > システム > 開発者オプション
からUDBデバッグ
のオン・オフができます- USBケーブルでMacとAndroidデバイスを接続します。
- ターミナルで
fultter devices
を実行して接続したデバイスが1 connected device:
などと表示されればOKです。表示されない場合やDevice XXXXXXXX is not authorized.
と出ている場合は再接続やデバイスの認証、USB設定などを見直してみてくださいこの状態でターミナルからIntelliJ IDEAを起動し、Androidデバイスにデプロイしてみます。以下でターミナルから開けない場合は手動で
~/development/project/my_app/android
をIntelliJ IDEAで開きます。command$ cd ~/development/project/my_app $ idea ./android最初はプロジェクトのインデックスに時間がかかります。インデックスが終わると私の環境では2つwarningが出ました。
- Warning: License for package Android SDK Build-Tools 28.0.3 not accepted.
- Warning: License for package Android SDK Platform 28 not accepted.
ライセンスについて承諾していないのが理由のようです。色々と調べると
Android SDK
のsdkmanager
で承諾することができそうです。とりあえず実行してみます。command$ ~/Library/Android/sdk/tools/bin/sdkmanager --licenses No Java runtime present, requesting install.おっとJava runtimeのインストールがまだでした。宗教上の理由でanyenvを使います。
command$ brew install anyenv $ anyenv install --init $ echo 'eval "$(anyenv init --no-rehash -)"' >> ~/.profile $ exec $SHELL -l $ anyenv install jenv $ exec $SHELL -l $ brew tap caskroom/versions $ brew cask install adoptopenjdk8 $ jenv add $(/usr/libexec/java_home -v 1.8) $ jenv global 1.8 $ java -version openjdk version "1.8.0_222" OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_222-b10) OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.222-b10, mixed mode)もう一度
sdkmanager
を起動します。command$ ~/Library/Android/sdk/tools/bin/sdkmanager --licenses色々とライセンスの承諾を聞かれるので
y
を入力していきます。
最後にAll SDK package licenses accepted
と出ればOKです。
Build
タブからBuild
を再実行してみるとandroid:successful
となりWarningは消えました。
Run > Edit configurations...
を選択し、左上の+
をクリックしてAndroid App
を選択します。Name
をmy_app
に変更、Module
にapp
を選択しApply
、OK
をクリックして画面を閉じます。
Run > Run 'my_app'
を選択すると、どのデバイスをビルドターゲットにするか聞かれるのでUSBで接続したAndroidデバイスを選択しOK
をクリックするとビルドが始まり、自動的にデバイスにデプロイされ、テストアプリが起動します。Androidエミュレータの設定
IntelliJ IDEAからAndroidエミュレータが動作するように設定します。
Tools > Android > AVD Manager
を選択+ Create Virtual Device...
をクリックPhone > Nexus 5X
を選択しNext
をクリックPie
の隣のDownload
をクリックしPie
をインストールしますPie
のインストールが終了したらFinish
をクリック- 改めて
Pie
を選択しNext
をクリックFinish
をクリックADV Manager
を一旦閉じ、Run > Run 'my_app'
を選択Select deployment target
画面にNexus 5X
があるので選択してOK
をクリック- エミュレータが自動で起動します
- エミュレータが起動すると自動的にデプロイされ、テストアプリが起動します
落ち穂拾い
fultter doctor -v
を実行して問題を確認します。command$ flutter doctor -v (中略) [!] Android toolchain - develop for Android devices (Android SDK version 29.0.2) • Android SDK at /Users/foobar/Library/Android/sdk • Android NDK location not configured (optional; useful for native profiling support) • Platform android-29, build-tools 29.0.2 • Java binary at: /Library/Java/JavaVirtualMachines/adoptopenjdk-12.0.2.jdk/Contents/Home/bin/java • Java version OpenJDK Runtime Environment AdoptOpenJDK (build 12.0.2+10) ✗ Android license status unknown. Try re-installing or updating your Android SDK Manager. See https://developer.android.com/studio/#downloads or visit https://flutter.dev/setup/#android-setup for detailed instructions. (中略) [!] Android Studio (not installed) • Android Studio not found; download from https://developer.android.com/studio/index.html (or visit https://flutter.dev/setup/#android-setup for detailed instructions). [!] IntelliJ IDEA Ultimate Edition (version 2019.2.1) • IntelliJ at /Users/foobar/Applications/JetBrains Toolbox/IntelliJ IDEA Ultimate.app ✗ Flutter plugin not installed; this adds Flutter specific functionality. ✗ Dart plugin not installed; this adds Dart specific functionality. • For information about installing plugins, see https://flutter.dev/intellij-setup/#installing-the-plugins (中略) ! Doctor found issues in 3 categories.
Android SDK
のライセンスが不明ということなので、flutter doctor --android-license
を実行してみます。command$ flutter doctor --android-licenses All SDK package licenses accepted.
Android Studio
はわざといれていないのでここでは無視します。
IntelliJ IDEA
にFlutter
とDart
のプラグインが入っていないと言われているのでインストールしてIntelliJ IDEAをリスタートします。もう一度
flutter doctor -v
を実行します。command$ flutter doctor -v (中略) [!] Android Studio (not installed) • Android Studio not found; download from https://developer.android.com/studio/index.html (or visit https://flutter.dev/setup/#android-setup for detailed instructions). (中略) ! Doctor found issues in 1 category.
Android Studio
以外のエラーが消えました。これで開発環境が揃ったことになります。まとめ
前回のインストールログをまとめてから、ちょっとしたサンプルプログラムを作っただけだったので、今度はもうちょっとちゃんとしたものを作りたいなぁと考えています(^^;
- 投稿日:2019-08-30T20:23:27+09:00
Bitriseのナンバリングを利用して、バージョンの自動インクリメントを実現(ReactNative, ios)
下記記事でFastlaneをBitriseに組み込み、MyAppへの自動デプロイを実現しました。
FastlaneのコマンドをBitriseに組み込む(ios,ReactNative)
しかし、開発の現場ですとデプロイ毎にバージョン番号を繰り上げて管理したいです。
そこで利用するのが、Bitriseのナンバリング番号です。android版はこちら↓
Bitriseのナンバリングを利用して、バージョンの自動インクリメントを実現(ReactNative, android)Bitriseのナンバリング番号とは
Build numbering and app versioning
Bitriseのworkflowが動く際に管理されている番号のことです。
実行毎に番号が繰り上がるため、この番号をバージョン番号として使用しようと思います。設定
Bitriseのをworkflowを編集します。
fastlane
フローの前にSet Xcode Project Build Number
を挿入します。
設定を記述します。
Info.plist file pathにios/アプリ名/info.plist
Build Numberに$BITRISE_BUILD_NUMBER
これに伴い下記記事で作成したファイルの
increment
の機能は不要になるため、該当部分を削除します。
fastfileの編集ios/fastlane/Fastfiledesc "Push a new release build to the App Store" lane :release do # increment機能は不要のため、削除 # increment_build_number(xcodeproj: "ReactNativePlatform.xcodeproj") match(type: "appstore") build_app(scheme: "ReactNativePlatform") upload_to_app_store( skip_waiting_for_build_processing: true ) clean_build_artifacts end実行
上記設定でBitriseが動きますと、MyAppにてバージョン番号がBitriseのナンバリング番号と一致しているのが確認できます。
参考
- 投稿日:2019-08-30T19:47:17+09:00
UICollectionViewでタブブラウザを作ってみた
タブが欲しい!!
WKWebKitを使用すると非常に簡単にブラウザを作ることができます。ええ。それはいいんですけども、タブがない。うーん、でもタブ欲しいよね。じゃあ、ライブラリはあるのかな。
ない!
...仕方ない。何かで擬似的に作るか。バージョンとか
Swift5.0
UICollectionViewでタブを擬似的に!
UICollectionViewならタブを生成してタブがどんどん増えてもスクロールすれば選択できるよね!というわけで、こんな感じに作った。
ViewController// // ViewController.swift // CollectionViewTest // // Created by Lily.Mameoka on 2019/08/18. // Copyright © 2019 Lily.Mameoka. All rights reserved. // import UIKit import WebKit let screenSize: CGSize = CGSize(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height) class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, WKUIDelegate, WKNavigationDelegate { var itemCount = 1 var selectedIndex: IndexPath = [0,0] var selectedNum = 0 var currentUrl = "https://www.google.com" var webView: WKWebView! var webTitle: String = "Google" var urlArray:[String] = ["https://www.google.com"] var webTitleArray: [String] = ["Google"] let collectionView: UICollectionView = { //セルのレイアウト設計 let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.minimumInteritemSpacing = 0 layout.minimumLineSpacing = 0 let collectionView = UICollectionView(frame: CGRect(x: 0, y: 100, width: screenSize.width, height: 50), collectionViewLayout: layout) collectionView.backgroundColor = UIColor.white //セルの登録 collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell") return collectionView }() //追加ボタンの設定 func buttonUpDate(){ let addTabButton:UIButton = UIButton(frame: CGRect(x: 0, y: 50, width: 50, height: 50)) addTabButton.backgroundColor = .white addTabButton.setTitle("+", for: .normal) addTabButton.setTitleColor(UIColor(red: 22/225, green: 157/225, blue: 202/225, alpha:0.5), for: .normal) addTabButton.addTarget(self, action: #selector(pushAddTabButton), for: .touchUpInside) self.view.addSubview(addTabButton) } override func viewDidLoad() { super.viewDidLoad() collectionView.dataSource = self collectionView.delegate = self view.addSubview(collectionView) newWeb() webView.uiDelegate = self webView.navigationDelegate = self buttonUpDate() closeButtonUpDate() } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { if webView.title != nil { let webText = webView.title as! String print("\(webText)") webTitle = "\(webText)" webTitleArray[selectedNum] = webTitle collectionView.reloadData() } else { webTitle = webView.url!.absoluteString webTitleArray[selectedNum] = webTitle collectionView.reloadData() } } //削除ボタンの定義 func closeButtonUpDate(){ let tabCloseButton:UIButton = UIButton(frame: CGRect(x: 50, y: 50, width: 150, height: 50)) tabCloseButton.backgroundColor = .white tabCloseButton.setTitle("現在のタブを削除", for: .normal) tabCloseButton.setTitleColor(UIColor(red: 22/225, green: 157/225, blue: 202/225, alpha:0.5), for: .normal) tabCloseButton.addTarget(self, action: #selector(tapCloseButton), for: .touchUpInside) self.view.addSubview(tabCloseButton) } //cellの個数設定 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return itemCount } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell let title = webTitleArray[indexPath.item] cell.setupContents(textName: title) return cell } //新規タブの処理 @objc func pushAddTabButton(sender: UIButton){ itemCount += 1//add tab collectionView.reloadData()//add tab currentUrl = self.webView.url!.absoluteString urlArray[selectedNum] = currentUrl if webView != nil { webView.removeFromSuperview()//delete old web } newWeb()//add new google urlArray += ["https://www.google.com"] webTitleArray += ["Google"] selectedNum = itemCount - 1 selectedIndex[1] = itemCount - 1 } //タブタップ時の処理 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { currentUrl = self.webView.url!.absoluteString urlArray[selectedNum] = currentUrl webTitleArray[selectedNum] = webTitle selectedIndex = indexPath selectedNum = indexPath[1] upDateWeb(index: selectedNum) collectionView.reloadData() } //タブ削除の処理 @objc func tapCloseButton(sender: UIButton){ if itemCount == 1 && selectedNum == 0 { //ラスト1つだけのタブを削除した時→リロード。googleの画面が0番目のタブにセットされる if webView != nil { webView.removeFromSuperview()//delete old web } urlArray = ["https://www.google.com"]//urlArrayは初期化 webTitleArray = ["Google"] newWeb() } else if selectedNum == 0 && itemCount != 1 { //0番目のタブを削除した時→1番目のタブに飛ぶ if webView != nil { webView.removeFromSuperview()//delete old web } itemCount -= 1 collectionView.deleteItems(at: [selectedIndex]) collectionView.reloadData() upDateWeb(index: 1) urlArray.remove(at: 0) webTitleArray.remove(at: 0) } else { itemCount -= 1 collectionView.deleteItems(at: [selectedIndex]) collectionView.reloadData() urlArray.remove(at: selectedNum)//selectされたIndexに対応するURLを削除 webTitleArray.remove(at: selectedNum) //一つ若いタブに格納されたurlでwebview selectedNum = selectedNum - 1 selectedIndex = [0, selectedNum - 1] upDateWeb(index: selectedNum) } } func newWeb(){ webView = WKWebView(frame: CGRect(x: 0, y: 150, width: screenSize.width, height: screenSize.height)) let urlString = "https://www.google.com" let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed) let url = NSURL(string: encodedUrlString!) let request = NSURLRequest(url: url! as URL) webView.load(request as URLRequest) self.view.bringSubviewToFront(webView) self.view.addSubview(webView) webView.uiDelegate = self webView.navigationDelegate = self } func upDateWeb(index: Int){ if webView != nil { webView.removeFromSuperview() } print(urlArray) print(index) webView = WKWebView(frame: CGRect(x: 0, y: 150, width: screenSize.width, height: screenSize.height)) let urlString = urlArray[index] let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed) let url = NSURL(string: encodedUrlString!) let request = NSURLRequest(url: url! as URL) webView.load(request as URLRequest) self.view.bringSubviewToFront(webView) self.view.addSubview(webView) webView.uiDelegate = self webView.navigationDelegate = self } } //cellのサイズの設定 extension ViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: screenSize.width * 2 / 5, height: 50) } }CollectionViewCell// // CollectionViewCell.swift // CollectionViewTest // // Created by Lily.Mameoka on 2019/08/18. // Copyright © 2019 Lily.Mameoka. All rights reserved. // import UIKit class CollectionViewCell: UICollectionViewCell { let tabCellLabel: UILabel = { let tabCellLabel = UILabel() tabCellLabel.frame = CGRect(x: 0, y: 0, width: screenSize.width * 2 / 5, height: 50) tabCellLabel.textColor = UIColor(red: 225/225, green: 112/225, blue: 0/225, alpha:1.0) tabCellLabel.textAlignment = .center return tabCellLabel }() override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setup() { layer.borderColor = UIColor.lightGray.cgColor layer.backgroundColor = UIColor(red: 22/225, green: 157/225, blue: 202/225, alpha:0.5).cgColor layer.borderWidth = 3.0 contentView.addSubview(tabCellLabel) contentView.bringSubviewToFront(tabCellLabel) } func setupContents(textName: String) { tabCellLabel.text = textName } }AppDelegate// // AppDelegate.swift // CollectionViewTest // // Created by Lily.Mameoka on 2019/08/18. // Copyright © 2019 Lily.Mameoka. All rights reserved. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate{ var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } }プロジェクトはこちら
今後
正直、既存のタブブラウザより不便ではあるので後で改良版を作ります。
- 投稿日:2019-08-30T15:42:50+09:00
IOSでいい感じにサイドメニューをスムーズスクロールさせる。
IOSでサイドメニューを開くとスクロールがなんかスムーズじゃない
こういうメニュー、全然スムーズに動いてくれない。
カクカク動く感じ。これでスムーズになる。
css style
overscroll-behavior-y: contain;
-webkit-overflow-scrolling: touch;
参考
iOS Safari でスムーススクロールにするやつ
https://developer.mozilla.org/ja/docs/Web/CSS/-webkit-overflow-scrolling
スクロールチェインを制御するやつ
https://developer.mozilla.org/ja/docs/Web/CSS/overscroll-behavior
- 投稿日:2019-08-30T14:14:58+09:00
iOS13から位置情報の権限周りの挙動が変わるかも
本記事は公開済みのドキュメントを元に作成しています。今後仕様変更などにより記載内容と異なる場合があります。予めご了承ください。
そして間違ってたところがあったら指摘していただけるとめちゃ嬉しいですWWDC2019ではSwiftUIやらProject Catalystなど興味深い話題がたくさん出ましたが、その影で(?)iOS13の位置情報権限周りで挙動が結構変わっていたので軽くまとめました。
何が変わったのか
- 位置情報の許可を求める画面では「常に許可」が選べなくなった
- 代わりに「1度だけ許可する」が追加された
requestAlwaysAuthorization
を呼び出した時の挙動が変わった
- 最初のポップアップで「使用中のみ許可」を選択すると、「仮の常に許可」が帰ってくるようになった
- モニタリング系APIは「常に許可」である必要がなくなった
- Beaconや大まかな位置情報の利用ハードルが若干低くなった。
つまり?
「常に許可」のハードルが高くなった
位置情報の許可を求める画面で「常に許可」が選べなくなった
iOS12では位置情報を取得する際に表示される選択肢は「常に許可」「アプリを使用中のみ許可」「拒否」の3つだったのですが、
iOS13からは「アプリを使用中は許可」「1度だけ許可」「拒否」となり、「常に許可」が選べなくなりました。
What's New in Core Locationより引用それぞれを選択した時の挙動も今までと異なります。
アプリを使用中は許可を選択した場合
リクエストする権限によって挙動が異なります。
requestWhenInUseAuthorization
で呼び出した場合いつも通りアプリが使用中のみ位置情報の取得が可能です。
アプリ側には.authorizedWhenInUse
としてステータスが返ります。
requestAlwaysAuthorization
で呼び出した場合位置情報の試用期間に入ります。
ユーザーは「アプリを使用中は許可」として選択していますが、アプリ側には.authorizedAlways
として返ります。(WWDC2019の動画では、仮の常に許可として説明されています)iOSはこの試用期間中、どこかユーザーが忙しくないタイミングでユーザーに対して1回だけ「使用中のみ許可」するか「常に許可」するかを聞いてきます。
What's New in Core Locationより引用ここでユーザーは位置情報の取得をアプリ使用中のみにすることも可能ですし、そのままにすることもできます。
ここまで見てわかるように、iOS13では真の「常に許可」にたどり着くまでのハードルが高くなっています。
今までは最初のポップアップで常に選択を選ばせれば良かったのですが、真の常に許可を得るためには1段階手順を踏まないといけないことと、ユーザーの選択次第でアプリを使用中のみに戻される可能性があります。
今まで位置情報の取得は「常に許可」しか許されなかった系アプリ(位置情報共有アプリとかそうですね)は、
アプリを使用中のみでも動作するように修正するか、ユーザーに設定から「常に許可」を選択するよう促す必要がありそうです。「1度だけ許可する」が押された場合
iOS13から新しく追加された選択肢です。
選択すると、アプリに対しては「アプリを使用中のみ許可」と同じステータス(.authorizedWhenInUse
)が返ります。
なので、ステータスからは1度だけ許可しているかどうかは判断ができません。また一時的な許可としての扱いなので、バックグラウンドに遷移してからしばらくする、あるいは次回起動時には権限が決まっていない状態に戻ります。
注意するべきなのが、バックグラウンドに遷移してからしばらくすると権限が戻る点です。
バックグラウンドで継続的に位置情報を要求している場合(allowsBackgroundLocationUpdates = true
)は戻らないので問題ないのですが、
そうでない場合は、位置情報を取得する際に毎回現在の権限をチェックする必要があります。
特に、位置情報の権限をアプリ起動時、特定のタイミングでしかリクエストしていなかった場合は結構つらみが深いですね。。。結局どういうこと?
What's New in Core Locationより引用こういうことらしいです。
モニタリング系APIは「常に許可」である必要がなくなった
iOS13では常に許可のハードルが高くなってしまった代わりに、今まで常に許可でないと使えなかったリージョン検知や、大まかな位置情報の変更の検知がアプリを使用中のみでも使用できるようになっています。
今まではワンショットで位置情報が欲しい場合は
.authorizedWhenInUse
を、リージョン系を使うなら.authorizedAlways
と機能ごとに必要な権限が分かれていましたが、
今回から使いたい機能によってどの権限が必要かを考えなくてよくなりました。ただし、アプリが使われていないタイミングでも位置情報の取得を行いたい場合は引き続きアプリは常に許可である必要があります。
感想
iOS13やべ〜〜〜〜〜〜
参考
What's New in Core Location
WWDCの動画って日本語字幕で見れるんですね。めちゃくちゃ助かりました。
CLLocationManager - Core Location | Apple Developer Documentation
- 投稿日:2019-08-30T11:18:43+09:00
iTunes Search APIを使ったWEBサービスの備忘録
Apple社が提供するiTunes Search APIを使ったWebサービスを作ったのでその際のちょっとした覚書をまとめておく。
作ったサイトはこちら
Bestap -ベスタップ- iOS,Androidの人気アプリのランキング分析サイトiTunes Search API(https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/) はAppleのiTunes上の様々なメディアを検索することのできるAPI。アプリや音楽はもちろん、映画や電子書籍の検索も可能。
取得できる情報はタイトルや作者名はもちろん、価格や星の数にリリース日、説明文やスクリーンショットまでiTunes上に表示されるほぼ全ての情報をカバーしている。APIを使用する際にはAPIのパッケージをインストールしたり、キーを登録したりするなどの手続きは不要で、APIのURLにアクセスすればJSON形式のデータを受け取ることができる。
特定のキーワードに関連するアプリを取得するときは
https:// itunes.apple.com/search?term=検索語句アプリの詳細情報を取得するときは
https:// itunes.apple.com/lookup?id=アプリのID言語や検索結果の数などはオプションで細かく指定できるので、詳しくは本家のマニュアルや関連サイトをご参照いただきたい。
アイコンやスクリーンショットのURLも網羅されているのでiOSアプリ関連の情報を集めるにはこれひとつあれば完璧といえる。
呼び出し制限は明示的にはないものの、実質的には制限があるという情報もあるので常識的な範囲での呼び出しを心がけたい。
- 投稿日:2019-08-30T10:43:49+09:00
コンテンツブロッカー設定がWebViewの異常動作の原因になることがある
対応に地味に時間がかかったので、備忘録として。
TL; DR
- コンテンツブロッカーで強力なブロック設定をされると、JavaScript(JS)無効化と同等の状態になることがある
SFSafariViewController
もその影響を受けるため、異常動作の原因になる経緯
SFSafariViewcontroller
を利用している画面での不具合について問い合わせがあった- 設定アプリ内でJSが無効化1されていると当該事象が発生することを確認
- 対処方法を案内したところ、無効化設定はしていないとの回答
- 各種設定の変更を試した結果、コンテンツブロッカーで強力なブロック設定(※)をしていると、JS無効化時と同様の事象が発生
- ※: Firefox focus2(v8.1.2)の場合、環境設定 > トラッキング防止 > 他の追跡コンテンツをブロック
- 画面内一番下の項目でデフォルトでオフ。さらに、オンにする際は正常に表示できなくなる危険性を伝えるアラートが出る
対応
このケースの場合、対応はコンテンツブロッカーの無効化をお願いすることのみ
ただし、方法は2通りある
- コンテンツブロッカーの設定を一時的に変更してもらう
- 対象となる項目は使用しているものにより異なるが、JS無効化と同等の状態になるような設定は上述の通り比較的深い階層にある
- ページを表示中にリロードボタン長押し > 「コンテンツブロッカーなしで再読み込み」 をタップ
画面が1ページしかないような場合には後者の方法が簡便だが、複数ページに渡るような導線の場合には、前者の方法での対応が良いと思われる
参考
設定 > Safari > 詳細 > JavaScript をオフ ↩
- 投稿日:2019-08-30T10:41:37+09:00
debファイルをダウンロード
Cydia等のインストーラでインストールすればdebファイルを意識する必要がありませんが、気に入ったAppをバックアップしたい場合はdebファイルを保存しておけば安心なこともあると思います。
インストーラがリポジトリからdebファイルをダウロードして、インストールが終わってもリスプリングするまでならdebファイルが削除されずに残ってるので、リスプリングする前に別な場所へ保存しとくという方法もありました。
リポジトリから直接ダウンロードする。
あるJBTweakのdebファイルを探していたら、素敵な記事を見つけたのでメモを残すことにしました。
リポジトリから直接debダウンロード | NEXTi4HACK.com
- リポジトリ
- パッケージの情報
- Packages
- Packages.gz
- Packages.bz2
- debファイル
- filename:に続く
./
以降をリポジトリURLに加えます。- ./xxxx.debだった場合次のようにつないだのがdebファイルのURLになります。
- Filza File Manager 64-bitの場合は、
./com.tigisoftware.filza_3.7.0-18_iphoneos-arm.deb
なのでdebファイルのURLは次のようになります。
- 投稿日:2019-08-30T00:42:59+09:00
Flutterウィークリー #73
Flutterウィークリーとは?
FlutterファンによるFlutterファンのためのニュースレター
https://flutterweekly.net/この記事は#73の日本語訳です
https://mailchi.mp/flutterweekly/flutter-weekly-73※Google翻訳を使って自動翻訳を行っています。翻訳に問題がある箇所を発見しましたら編集リクエストを送っていただければ幸いです。
読み物&チュートリアル
Flutterチュートリアル:従来のショッピングアプリUIをフラッターで簡単に作成
https://cybdom.tech/flutter-tutorial-shopping-app-ui/
Flutter Inを使用したFlutter GridViewチュートリアル
https://www.gotut.net/flutter-grid-view-tutorial/
FlutterのGridviewを使用して、フェードイン効果のある画像ギャラリーを作成する方法を学びます。Flutter電卓アプリを構築する
https://itnext.io/building-a-calculator-app-in-flutter-824254704fe6
Kenneth Reillyは、 Flutter段階的に電卓を作成します。Flutter iOSアプリのキーボード完了ボタンUX
https://blog.usejournal.com/keyboard-done-button-ux-in-flutter-ios-app-3b29ad46bacc
Ramankit Singhは、iOSのキーボードで[完了]ボタンを再現するソリューションを示しています。ストリームを使用したFlutterでの非同期プログラミング
https://medium.com/@bertoboh/async-programming-in-flutter-with-streams-c949f74c9cf9
Albert ObohによるFlutterでのStreamsの使用の概要Flutter認証
https://medium.com/@greg.perry/auth-in-flutter-97275b29b550
Greg Perryは、Firebase authでauthパッケージを使用する方法を詳細に分析して示します。FlutterとCustom Painterを使用してレーダーチャートを作成する
https://medium.com/@d.panaite92/building-a-radar-chart-with-flutter-and-custom-painter-384c005002f9
Dan Panaiteによるこのチュートリアルで、カスタムペインターを使用して独自のレーダーチャートを作成する方法を学びます。Flutter for the Web — Githubにデプロイする
https://medium.com/flutter-community/flutter-for-the-web-deploy-to-github-da454e4bc079
Flutter webを使用していますか?アコラ・イングのおかげで、あなたの作品を世界に見せてください。 DKB。GithubPagesにデプロイする方法を教えてくれます。Flutter iOSアプリのアイコンをプログラムで変更する
https://medium.com/flutter-community/programatically-change-ios-app-icon-in-flutter-c9e84bc541a2
iOS 10.3以降、Appleはアプリのランチャーアイコンをプログラムで変更する機能を導入しました。 Alessandro FaveroはFlutterからそれを行う方法を説明しています。MVCの神社
https://medium.com/flutter-community/shrine-in-mvc-7984e08d8e6b
MaterialサンプルアプリShrineを知っていますか? Greg Perryが、サンプルを取得してMVCアーキテクチャに変更する方法を説明します。プロバイダーとBLoCでFlutter状態管理を簡素化
https://medium.com/fluttervn/simplify-flutter-state-management-with-provider-and-bloc-dcfad49bedf2
ラムはプロバイダーライブラリをテストしており、いくつかの問題を見つけた後、プロバイダーとBLoCの組み合わせがどのようにうまく機能するかを説明します。Flutterネットワークリクエストをデバッグする3つの簡単な方法
Flutter作業するときにネットワーク要求をデバッグするのに役立ついくつかのヒント。Flutterレスポンシブデザイン:はじめに
https://www.raywenderlich.com/4324124-responsive-design-for-flutter-getting-started
JB Lorenzoは、 Flutterさまざまな画面サイズと向きを処理する方法に関する洞察を提供します。ビデオ&メディア
Flutter TDDクリーンアーキテクチャコース
https://www.youtube.com/watch?v=KjE2IDphA_U&feature=youtu.be&t=2s
コードをクリーンに保ち、テストすることは、2つの最も重要な開発プラクティスです。 Clean ArchitectureをFlutterプロジェクトに適用する方法を学びます。Flutter UI | Stadiaアプリのコンセプト
https://www.youtube.com/watch?v=-rqvZfUdSPw&feature=youtu.be&t=6395s
このビデオでは、CustomPainter、カスタムウィジェット、アニメーション、その他を使用して、レスポンシブで適応性のあるレイアウトを作成する方法を説明します。Flutterアニメーション切り替えボタン
https://www.youtube.com/watch?v=XI3LFEvagBY&feature=youtu.be&t=610s
アニメーション付きのカスタムスイッチボタンを作成する方法を学びます。Flutter Desktopの使用開始
https://www.youtube.com/watch?v=V09e5nb95lo&feature=youtu.be&t=236s
このビデオでは、 Flutterを使用してデスクトップアプリケーションを作成するための最初の手順を実行する方法を示します。AdmobをFlutterに追加する方法
https://www.youtube.com/watch?v=xSrNB6ge66Q&feature=youtu.be&t=205s
firebase_admob dartパッケージを使用してフラッターにadmobを追加する方法に関するビデオチュートリアルのコーディングセマンティクス(今週のFlutterウィジェット)
https://www.youtube.com/watch?v=NvtMt_DtFrQ&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=48
Semanticsを使用して、アプリのUIにウィジェットに関する情報を注釈するウィジェットを使用して、 Flutterアプリをより包括的にします。ライブラリ&コード
henriquearthur / flutter_native_splash
https://github.com/henriquearthur/flutter_native_splash
AndroidおよびiOSでスプラッシュスクリーンを追加するためのネイティブコードを自動的に生成します。特定のプラットフォーム、背景色、スプラッシュ画像でカスタマイズします。
ketanchoyal / extended_navbar_scaffold
https://github.com/ketanchoyal/extended_navbar_scaffold
拡張可能なフローティングナビゲーションバーを備えたカスタム拡張足場
simformsolutions / flutter_showcaseview
https://github.com/simformsolutions/flutter_showcaseview
iOSおよびAndroidで機能を紹介できるFlutterプラグイン。