- 投稿日:2020-09-26T19:37:24+09:00
日本語表示におけるUILabelのbyCharWrappingとbyWordWrappingの違い
はじめに
Swift の UILabel には折り返しや省略表示を指定するための lineBreakMode というプロパティが用意されています。指定できるモードの中に折り返し位置を単語区切りにする byWordWrapping と、文字区切りにする byCharWrapping があるのですが、byWordWrapping を指定しても日本語の文章は単語区切りで表示されません。それではbyCharWrapping を指定しても byWordWrapping を指定しても違いはないのか、気になったので調べてみました。
環境
Xcode Version 12.0
Swift 5.3byCharWrapping
lineBreakMode に byCharWrapping を指定して表示したところ画像のようになりました。
わかりやすいようにUILabel の背景を黄色にしています。
byWordWrapping
次に byWordWrapping を指定して同じ文章を表示してみました。
byWordWrapping を指定すると行頭に句読点や括弧の終わり、小文字、伸ばし棒などがこないように調整されるようです。こちらのほうが読みやすいですね。
まとめ
byCharWrapping と byWordWrapping のどちらを指定しても良いという場合は禁則処理が考慮されている byWordWrapping を使用するのが良いようです。
■参考サイト
https://developer.apple.com/documentation/uikit/nslinebreakmode
- 投稿日:2020-09-26T18:48:32+09:00
Xcodeでショートカットを自作し 煩雑な pod install 作業をなくそう
はじめに
業務で同時に複数のプロジェクトを見ることがあり、平均すると1日1回ぐらいterminalを開いてpod-installのコマンドを叩いています。本記事はiOSアプリ開発者が良く使うCocoaPodsのコマンドpod-installの作業を楽にするtipsです。XcodeにはBehaviorsと言う機能があり、様々な動作やイベントを設定・追加できます。 その機能を利用して、自動でterminalを開き所定のdirectoryに移動してpod-installコマンドを叩いてくれるショートカットキーを作成します。
開発環境
Xcode12 (Xcode11でも動くと思いますが未検証)
Cocoapodsセットアップ
1. Scriptを用意
Script をダウンロード
#!/bin/sh osascript <<END tell application "Terminal" if not (exists window 1) then reopen activate do script "cd `pwd`; pod install" in window 1 end tell END2. 実行権限の付与
権限の確認
$ ls -l Pod-Install.sh -rw-rw-r--@ 1 yasuradodo staff 158 Sep 19 19:40 Pod-Install.shもし実行権限がなければ、実行権限
x
の付与$ chmod u+x Pod-Install.sh再度権限の確認
$ ls -l Pod-Install.sh -rwxrw-r--@ 1 yasuradodo staff 158 Sep 19 19:40 Pod-Install.sh3. XcodeのBehaviorsでCustom commandを作成
XcodeのPreferencesからBehaviorsを選択し、左下にある+をタップしてCustom commandを作成します。
ショートカットには好きなキーを登録しましょう。筆者はshift + command + p
にしました。
これで、Xcode上で登録したショートカットでいつでも自動的にpod-installが実行できます。終わりに
今回はpod-installだけでしたが、Carthage, SourceTree, SwiftLintなど様々な物に対応でき作業効率を向上できます。
他にも便利なBehaviorsの使い方などあれば、教えていただきたいです![]()
参考文献
https://medium.com/@abhishekbedi/never-type-pod-install-again-ever-eb55386eef59
https://github.com/JeaSungLEE/Awesome-Xcode-Behaviors
- 投稿日:2020-09-26T17:47:38+09:00
【Flutter入門】短期集中講座やったので要点を整理してみた
Android/iOS向けアプリ作りたいけどなんかいい開発環境ないかなぁ。
Electronのスマートフォン版あったら便利なんだけどなぁ。
Flutter?聞いたことはあるけどDartが不人気なことで有名だしなぁ。
(念の為改めて調査してみる)
は?Dartめっちゃいいじゃん!なにこのC言語系の正当進化版みたいな可読性!?何が不人気なの?? →完全にただの記憶違いでしたm(_ _)m
Flutterもデスクトップ・モバイル・Webをカバーした理想的な環境っぽいなぁ。
よし、まずは入門だ!
(最終更新:2020.09.27)作業した環境
- OS:macOS Catalina バージョン10.15.7
集中講座の元動画様(英語)
Flutter Crash Course
この記事の位置づけとして、基本的に元動画様に沿って進めていって、補助として個人的に記録しておきたいと思った情報を日本語でわかりやすくざっくりまとめた感じです。作るもの
シンプルな単語ペアジェネレータ。
ランダムに生成された単語ペアをリスト表示する。
気に入った単語ペアはタップで印をつけることができ、別ページで一覧を確認できる。Flutterとは
Google製UIツールキット。
一つの共通化されたコードをもとにモバイル・Web・デスクトップ向けのネイティブクロスプラットフォームアプリが開発できる。
パフォーマンスが極めて高い。Dartとは
Flutterが採用しているオブジェクト指向型プログラミング言語。
UIプログラミングに最適化されている。
すべてのプラットフォームで高速に動作する。
文法的には、例えるならJavaの要素を持つJavaScript。Widgets
DartではすべてのパーツがWidgetである。
マテリアルデザインを採用している。
Scaffold(土台)、AppBar、Container、Image、Icon、などなど。
StatelessとStatefulがある。
build関数で作成する。Stateless Widgets
状態を持たない静的なWidgetのこと。
実行中変化せず、自身を再描画する必要がないUIに使われる。Statefull Widgets
状態を持っている動的なWidgetのこと。
実行中に変化し、自身を再描画する必要があるUIに使われる。開発環境構築
ざっくりと今回必要な環境をまとめておきます。
Flutter本体
- Flutterから環境にあったFlutterをインストール(圧縮ファイルを解凍して任意の場所に配置)
- flutter/binにパスを通す
Visual Studio Code
- Visual Studio Codeからインストール
- Flutterエクステンションを追加
- Dartエクステンションを追加
Xcode
- AppStoreから最新のXcodeをインストール
- 下記コマンドを実行してXcodeを設定
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
Android Studio
- Android Studioから最新のAndroid Studioをインストール
- Flutterプラグインを追加
- 仮想デバイスを作成
開発環境構築チェック
テンプレートアプリを動かして開発環境が構築されたことを確認します。
※ここまでで詳細な手順やトラブルシューティングは省いているので、適宜自分で調べながらになるかと思います。ターミナルで作業ディレクトリに移動して下記コマンドを実行
flutter create wordpair_generator cd wordpair_generator code .★ターミナルから
code
コマンドが使えない場合は一度VSCodeを開いて、Cmd+Shift+P shell と入力 Shell Command: Install 'code' command in PATH を選択とすると使えるようになる。
wordpair_generatorプロジェクトをVSCodeで開けたら、右下の「No Device」をクリックして「Start iOS Simulator」を選択し、シミュレータを立ち上げます。
上部のメニューから、
Run > Start Debugging > Dart & Flutter
と選択してデバッグ起動します。テンプレートアプリが無事起動し、右下の+ボタンをクリックしたら画面中央の数字が増えていく、という挙動を確認できればOKです。
コーディング準備
コードを打ち込んで行く前に、いくつか準備しておきます。
私の場合、Dartのおすすめセッティングとかいうものを適用した結果、保存のたびに強制フォーマットされてめちゃくちゃやりにくかったのでコマンドパレット
Cmd+Shift+P
からopen settings
と打って設定ファイル(json)を開き、設定を一部OFFにしました。また、動画とは順番が前後しますが、事前に依存パッケージをインストールしておくとスムーズかもしれません。
pubspec.yaml# 〜(省略)〜 dependencies: flutter: sdk: flutter english_words: ^3.1.5 # この行を追加 # 〜(省略)〜コーディング(main.dart)
エントリポイントのある、メインファイルです。
これだけだとまだ動きません。main.dartimport 'package:flutter/material.dart'; import './random_words.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData(primaryColor: Colors.purple[900]), home: RandomWords() ); } }コーディング(random_words.dart)
画面の構成やランダムな単語ペアの生成・表示といったロジックを担当するファイルです。
random_words.dartimport 'package:flutter/material.dart'; import 'package:english_words/english_words.dart'; class RandomWords extends StatefulWidget { @override RandomWordsState createState() => RandomWordsState(); } class RandomWordsState extends State<RandomWords> { final _randomWordPairs = <WordPair>[]; final _savedWordPairs = Set<WordPair>(); Widget _buildList() { return ListView.builder( padding: const EdgeInsets.all(16), itemBuilder: (context, item) { if (item.isOdd) { return Divider(); } final index = item ~/ 2; if (index >= _randomWordPairs.length) { _randomWordPairs.addAll(generateWordPairs().take(10)); } return _buildRow(_randomWordPairs[index]); } ); } Widget _buildRow(WordPair pair) { final alreadySaved = _savedWordPairs.contains(pair); return ListTile( title: Text(pair.asPascalCase, style: TextStyle(fontSize: 18.0)), trailing: Icon( alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null), onTap: () { setState(() { if (alreadySaved) { _savedWordPairs.remove(pair); } else { _savedWordPairs.add(pair); } }); } ); } void _pushSaved() { Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) { final Iterable<ListTile> tiles = _savedWordPairs.map((WordPair pair) { return ListTile( title: Text(pair.asPascalCase, style: TextStyle(fontSize: 16.0)) ); }); final List<Widget> divided = ListTile.divideTiles( context: context, tiles: tiles ).toList(); return Scaffold( appBar: AppBar(title: Text('Saved WordPairs')), body: ListView(children: divided) ); } ) ); } Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('WordPair Generator'), actions: <Widget>[ IconButton( icon: Icon(Icons.list), onPressed: _pushSaved) ]), body: _buildList() ); } }特に気になった文法
Dartの文法はJavaやJavaScriptの流れを組んだような、素直でわかりやすい文法だと感じていますが、初見で特に気になったりわからなかったものについてまとめます。
- 「~/」除算、整数(int)の結果を返す
- 「_」アンダースコア始まりの識別子でプライベートアクセス指定を表す
トラブルシューティング
- 実行したままコードをいじるとエラーが出て何も表示されなくなる
- 原因はわかりませんが、実行中のアプリを再起動すると解決しました。
あとがき
Dartについて、よく考えられた直感的な命名の各種機能がそろっていて、文法も素直で堅実な印象で書いていて楽しいと感じました。
ただ、UIに最適化された言語だからなのか、コンストラクタにコンストラクタをどんどん重ねて作っていくやりかたがやりやすいので、気をつけないとついつい"ダーティー"なコードを書いてしまいがちとも思いました。
そこがまたいいのかもしれませんね。
- 投稿日:2020-09-26T17:10:47+09:00
複数のXcodeバージョンを共存させている時に切り替えを楽にするための小さな工夫
Mac内に複数バージョンのXcodeを共存させる方法に関してはWeb上にいくつもありまして、こちらの記事などが分かりやすいかと思います。
【iOS開発】Xcodeの旧バージョンをインストールし、最新版と共存させる方法上の記事にて切り替えのコマンドを紹介していただいていますが、なかなか長いコマンドですし、都度Macのパスワードを入力しなければならないです。
頻繁にバージョンを切り替える必要があると、面倒臭く感じます。以下の方法で、エイリアスによってコマンドがごく短くなりますし、いちいちパスワードを入力する手間も省けます。
私はzshを使っていますので
.zshrc
にエイリアスを記述しています。
bashを使っている方は.bashrc
と読み替えてください。環境としては、最新バージョンであるXcode 12と、Xcode 11.3が入っている状態です。
.zshrcalias xcnew='echo {Macのパスワード} | sudo xcode-select --switch /Applications/Xcode.app' alias xc113='echo {Macのパスワード} | sudo xcode-select --switch /Applications/Xcode11.3.app' alias xcver='xcodebuild -version'
xcnew
をたたくと最新バージョンに一発で切り替わります。xc113
をたたくと11.3に以下同文です。xcver
をたたくと現在のバージョンが表示されます。要は、エイリアスのコマンドにMacのパスワードを設定してしまい、
sudo xcode-select
に引き渡す、というだけの単純なアイデアなのですが…
皆様の生産性の向上に少しでも寄与できたら幸いです。
- 投稿日:2020-09-26T17:10:31+09:00
iOS14でのデフォルトブラウザ/メーラー変更時のcanOpenURL==false問題の整理と横展開調査結果
前提環境
- Xcode 11.3.1
- Swift 5.1.3
- iOS 14.0 / 14.0.1
問題事象
ポイント:
- 問題の事象はiOS 13 SDKでビルドしてもiOS 14の端末では発生する。
- 14.0から発生しているが14.0.1で解消していない(すなわちiOSのバグではなく仕様変更?)。
サンプルコード:
let url = URL(string: "https://qiita.com")! guard UIApplication.shared.canOpenURL(url) else { return } UIApplication.shared.open(url, options: [:], completionHandler: nil)
- iOS 13以下で上のコードを実行すると、Safariが起動する。
- iOS 14.0 / 14.0.1でデフォルトブラウザをSafari以外(Chromeなど)に設定し上のコードを実行すると、何も起こらない。
- iOS 14.0 / 14.0.1でデフォルトブラウザがSafariに設定されていれば、iOS 13以下と同様にSafariが起動する。
<デフォルトブラウザをChromeに変更する手順>
1. iOS 14の端末で、Chromeをインストールする。
2. 設定>Chromeを選択する。
3. ブラウザの一覧が表示されるので、Chromeを選択する原因
iOS 14.0以降でデフォルトブラウザをSafari以外(Chromeなど)に設定すると、
UIApplication.shared.canOpenURL()
がfalseを返却するため。回避策
Info.plistのLSApplicationQueriesSchemesに"https"および"http"を設定すると回避できます。
関連事象
iOS 14においてはデフォルトメーラーについても変更できるようになりました。
"mailto"についても同様に、LSApplicationQueriesSchemesに追加しないと、デフォルトメーラーが変更されている場合にはUIApplication.shared.canOpenURL()
がfalseを返却します。横展開調査
以上の事項は、文末の「参考リンク」の記事で得られた有益な情報です。
執筆者の方には深く感謝を申し上げたいです。一方この項は、私が独自に追加調査をした結果になります。
電話アプリの"tel"および"telprompt"スキーム
"tel"はApple URL Schemesにてドキュメント化されているスキームです。
"telprompt"はドキュメント化されていないスキームです。いずれも、LSApplicationQueriesSchemesに追加しなくても、iOS 14でも電話アプリが起動します。
ちなみに、「将来、電話アプリのデフォルトが変更できるようになったら?」という疑問があったので、LSApplicationQueriesSchemesに"tel"を追加してみたところ、追加してもちゃんと動作します(当たり前?)。
Walletアプリの"shoebox"スキーム
ドキュメント化されていないスキームです。
LSApplicationQueriesSchemesに追加しなくても、iOS 14でもWalletアプリが起動します。設定アプリ
こちらのスキームは文字列ではなく
UIApplication.openSettingsURLString
となりますが、(当然ながら)LSApplicationQueriesSchemesに追加しなくても、iOS 14でも設定アプリが起動します。検証結果の整理
- iOS 14では、カスタムスキームに加えて、デフォルトを変更できるアプリのスキームもInfo.plistに定義しないとダメです。
- ドキュメント化されているスキーム、ドキュメント化されていないけど動くスキーム、いずれも、現時点でブラウザとメーラー以外は影響を受けていない模様です。
- Xcode 12 (iOS 14 SDK)でビルドしたアプリだけではなく、Xcode 11 (iOS 13 SDK)でビルドしたアプリでもiOS 14上での挙動は同じです。
参考リンク
【iOS14】デフォルトブラウザを変更した時にcanOpenURLがfalseになる問題
iOS 14対応で気をつけるべきこと
- 投稿日:2020-09-26T17:10:31+09:00
iOS14でデフォルトブラウザ/メーラー変更時にcanOpenURLがfalseになる問題の整理と横展開調査結果
前提環境
- Xcode 11.3.1
- Swift 5.1.3
- iOS 14.0 / 14.0.1
問題事象
サンプルコード:
let url = URL(string: "https://qiita.com")! guard UIApplication.shared.canOpenURL(url) else { return } UIApplication.shared.open(url, options: [:], completionHandler: nil)
- iOS 14.0 / 14.0.1でデフォルトブラウザをSafari以外(Chromeなど)に設定し上のコードを実行すると、ブラウザが起動しない。
- Xcode 11 (iOS 13 SDK) でビルドしてもiOS 14の端末では発生する。
- 14.0から発生しているが14.0.1で解消していないということは、すなわちiOSのバグではなく仕様変更?
<デフォルトブラウザをChromeに変更する手順>
1. iOS 14の端末で、Chromeをインストールする。
2. 設定>Chromeを選択する。
3. ブラウザの一覧が表示されるので、Chromeを選択する原因
iOS 14.0以降でデフォルトブラウザをSafari以外(Chromeなど)に設定すると、
UIApplication.shared.canOpenURL()
がfalseを返却するため。回避策
Info.plistのLSApplicationQueriesSchemesに"https"および"http"を設定すると回避できます。
関連事象
iOS 14においてはデフォルトメーラーについても変更できるようになりました。
"mailto"についても同様に、LSApplicationQueriesSchemesに追加しないと、デフォルトメーラーが変更されている場合にはUIApplication.shared.canOpenURL()
がfalseを返却します。横展開調査
以上の事項は、文末の「参考リンク」の記事で得られた有益な情報です。
執筆者の方には深く感謝を申し上げたいです。一方以下は、私が独自に追加調査をした結果になります。
電話アプリの"tel"および"telprompt"スキーム
"tel"はApple URL Schemesにてドキュメント化されているスキームです。
"telprompt"はドキュメント化されていないスキームです。いずれも、LSApplicationQueriesSchemesに追加しなくても、iOS 14でも電話アプリが起動します。
ちなみに、「将来、電話アプリのデフォルトが変更できるようになったら?」という疑問があったので、LSApplicationQueriesSchemesに"tel"を追加してみたところ、追加してもちゃんと動作します(当たり前?)。
Walletアプリの"shoebox"スキーム
ドキュメント化されていないスキームです。
LSApplicationQueriesSchemesに追加しなくても、iOS 14でもWalletアプリが起動します。設定アプリ
こちらのスキームは文字列ではなく
UIApplication.openSettingsURLString
となりますが、(当然ながら)LSApplicationQueriesSchemesに追加しなくても、iOS 14でも設定アプリが起動します。検証結果の整理
- iOS 14では、カスタムスキームに加えて、デフォルトを変更できるアプリのスキームもInfo.plistに定義しないとダメです。
- ドキュメント化されているスキーム、ドキュメント化されていないけど動くスキーム、いずれも、現時点でブラウザとメーラー以外は影響を受けていない模様です。
参考リンク
【iOS14】デフォルトブラウザを変更した時にcanOpenURLがfalseになる問題
iOS 14対応で気をつけるべきこと
- 投稿日:2020-09-26T15:53:03+09:00
SceneKitに平面のラベルを貼る方法
SceneKitに平面のラベルを貼って情報を出す方法のメモ。
GameViewController.swift
に次のコードを追加する。GameViewController.swiftclass LabelScene: SKScene { override public init(size: CGSize){ super.init(size: size) self.scaleMode = SKSceneScaleMode.resizeFill let label = SKLabelNode(fontNamed: "Chalkduster") label.text = "test" label.fontSize = 65 label.fontColor = .blue label.position = CGPoint(x:frame.midX, y: label.frame.size.height) self.addChild(label) } required init?(coder aDecoder: NSCoder) { fatalError("Not been implemented") } }
GameViewController.swift
のviewDidLoad
の最後にコードを追加する。GameViewController.swiftscnView.overlaySKScene = LabelScene(size:scnView.bounds.size)これで、画面上にラベルが表示されます。
- 投稿日:2020-09-26T15:41:37+09:00
通信処理で重くなり過ぎないようにするには
API呼び出しはコストのかかる処理で、呼び過ぎるとユーザーの回線に余計な負担を与える。また時間がかかるため、反応が遅いと感じられUXに悪影響を与えることがある。そのためどこでどう呼ぶかを考える必要がある。
呼ぶAPIの数の制限
例えばアプリなどで一画面で呼ぶAPIが多すぎると遅くなってしまう。そのため、1画面で呼ぶAPIは2〜3個までなどある程度基準を設け、一度に呼ぶAPI数が多くなり過ぎないように設計する。
回線状況の考慮
できれば、ユーザーがそのアプリ等を使う状況を考慮に入れ、通信回線がどのくらい良いかも念頭に置いておくと良い。例えば、電車の案内のアプリであれば、電車内とりわけ地下鉄など電波状況の悪い場所で使う場合もあることから、そのような場面であまり時間のかかる処理を入れるのは好ましくない。
APIを呼ばなくて済むのではないか検討
同じデータを取ってくるだけなのに何度もAPIを呼ぶのは無駄である。そのため、一度取ってきたデータをデバイスのメモリに一時保存、あるいはストレージに恒久保存して対処する事ができないかを検討する。
また一部のデータが欲しいだけなのに、多くのデータを取ってくるAPIを呼ぶなどもやはり無駄となる。
APIを呼んでいる最中の対応
APIを呼んでいる最中、UIまでも止まってしまう(タップとかクリックとかユーザーの反応を受け付けなくなる)とフリーズしたかのような印象をユーザーに与えてしまう。そのため、通信処理とUI処理のスレッドを分けるなどして対処する。
また、通信処理が終わるまでの間、100ms~200ms程度異常かかるようであればローディングの表示(グルグル回るアイコン、またはゲージが徐々に満タンに近づくバーなど)をするのが望ましい。この場合、通信の開始時にローディング表示を開始する。そして通信が成功しようと失敗しようと、通信が終了すればローディング表示を止めることになる。
参考
MdN Design -[優れたUXを目指して]アプリの性能について:第3回
Android Developers - Keeping your app responsive
- 投稿日:2020-09-26T13:58:18+09:00
Xcode 12 「Double-quoted include」エラーの対処法
Xcodeを12にアップデートすると、エラーが大量に。。
アプリをビルドすると、GoogleDataTransport Groupから大量のエラーが発生しました。
- Double-quoted include "pb.h" in framework header, expected angle-bracketed instead - Double-quoted include "pb_common.h" in framework header, expected angle-bracketed instead - Double-quoted include "pb_decode.h" in framework header, expected angle-bracketed instead - Double-quoted include "pb_encode.h" in framework header, expected angle-bracketed instead -
ダブルクオートが含まれているのが、エラーの原因か??
解決法
project>Pods>Build Setting>Quoted include in..
. を以下のように変える
これでDouble-quotedはエラーとみなされなくなりました!
参考
- 投稿日:2020-09-26T05:30:10+09:00
iOS14でのDatePickerの挙動について(SwiftUI)
iOS14でのDatePickerの挙動について(SwiftUI)
前回書かせていただいたiOS14(UIKit)でのDatePickerに関してですが、ついでにSwiftUIでも調べたので書かせていただきました。
今回は従来のものから何が追加さたのかというところと実際の使い方のCodeも載せておきます。環境
macOS Catalina 10.15.6
Xcode Version 12.0.1はじめに
まずはじめにSwiftUIでのDatePickerの使い方から
struct ContentView: View { @State private var selectionDate = Date() var body: some View { DatePicker("タイトル", selection: $selectionDate) } }これだけでDatePicerを使うことができます。
さすがSwiftUI!デフォルトだとこんな感じです。
これだと流石に使い物にならんのでちょっと修正を加えますstruct ContentView: View { @State private var selectionDate = Date() var body: some View { DatePicker("タイトル", selection: $selectionDate) // これを入れることによってラベルを表示させなくします。 .labelsHidden() } }これで一旦検証はできそうです。
ちなみに別途Pickerの上にタイトルを入れたい場合
struct ContentView: View { @State private var selectionDate = Date() var body: some View { VStack { Text("タイトル") .font(.title) DatePicker("タイトル", selection: $selectionDate) .labelsHidden() } } }このようにすることでタイトルを付けれるようになります。
他にも良い実装はありますが今回は割愛します。本題
では実際にSwiftUIではどのようにしてStyleやModeを変更するか確認します。
まずUIKitではenumで新しく定義されていました。
public enum UIDatePickerStyle : Int { case automatic = 0 case wheels = 1 case compact = 2 @available(iOS 14.0, *) case inline = 3 }swiftUIではまずこのように定義してあります。
extension View { /// Sets the style for date pickers within this view. @available(tvOS, unavailable) @available(watchOS, unavailable) public func datePickerStyle<S>(_ style: S) -> some View where S : DatePickerStyle }使い方としましては、
DatePicker("タイトル", selection: $selectionDate) .datePickerStyle(CompactDatePickerStyle())このように定義することでStyleの変更ができます。
Style
公式ドキュメントを見るといくつかStyleがあります。
struct DefaultDatePickerStyle struct CompactDatePickerStyle struct WheelDatePickerStyle struct FieldDatePickerStyle struct StepperFieldDatePickerStyle struct GraphicalDatePickerStyleこの中の
FieldDatePickerStyle
StepperFieldDatePickerStyleこの二つは macOS 10.15でのみ使えるとのことです。
iOS14で追加されたものは
GraphicalDatePickerStyle
になります。
DefaultDatePickerStyle
CompactDatePickerStyle
WheelDatePickerStyleこちらの3つはiOS 13.0から追加されたものになります。
では実際の表示を確認してみます。
- DefaultDatePickerStyle
デフォルトだとこんな感じです。
動きはタップすると、カレンダーがポップアップします。
動き的にはUIKitと同じなので割愛します。
- CompactDatePickerStyle
こちらはdatePickerStyleだけ指定してもDefaultDatePickerStyleの挙動となんら変わりありません。
- WheelDatePickerStyle
こちらは従来通りのweelになります。
- GraphicalDatePickerStyle
こちらはデフォルトだとUIKitでもあった、圧縮されてしまう現象が起きています。
.frame
でいい感じに調整が必要です。Mode
次にMode指定をしてみます。
DatePicker("タイトル", selection: $selectionDate, displayedComponents: .hourAndMinute)displayedComponents
- .date
- .hourAndMinute
上記を指定できます。
DatePicker("タイトル", selection: $selectionDate, displayedComponents: .date) .datePickerStyle(DefaultDatePickerStyle()) .labelsHidden()DatePicker("タイトル", selection: $selectionDate, displayedComponents: .hourAndMinute) .datePickerStyle(DefaultDatePickerStyle()) .labelsHidden()上記の挙動としては
- DefaultDatePickerStyle
- CompactDatePickerStyle
共に同じ表示でした。
DatePicker("タイトル", selection: $selectionDate, displayedComponents: .date) .datePickerStyle(WheelDatePickerStyle()) .labelsHidden()DatePicker("タイトル", selection: $selectionDate, displayedComponents: .hourAndMinute) .datePickerStyle(WheelDatePickerStyle()) .labelsHidden()こちらに関してはやはり通常で、日にち表示なのか、時間表示七日の違いでした。
DatePicker("タイトル", selection: $selectionDate, displayedComponents: .date) .datePickerStyle(GraphicalDatePickerStyle()) .labelsHidden()こちらに関してはTimeを消したとしてもやはり圧縮されてしまいます。
.frame(width: 300, height: 500)フレーム指定してあげると、表示が正常になります。
が、サイズによってバグります(widthを400にしたらバグった)
これは使うのは厳しそうですね。。DatePicker("タイトル", selection: $selectionDate, displayedComponents: .hourAndMinute) .datePickerStyle(GraphicalDatePickerStyle()) .labelsHidden()時間のみだと、カレンダーのTimeの部分だけ表示されるようになりました。
まとめ
今回はSwiftUIでの挙動を調べてみました。
使用する場合によってかもしれませんが、
GraphicalDatePickerStyleに関しては、UIKit同様バグというか表示に癖があるので、現段階ではどうしてもということがない限りあまり使わない方実装を考えた方が良さそうです。
正直あまり使わないかな〜とも思ったりもしてます。まあ、宣言的にCodeをかけるのはやはりSwiftUIの強みだし、SwiftUIは書いてて楽しいので、
今後よくなることを期待して今回の調査を終えたいと思います。次回はiOS14の位置情報の変更に関しての挙動でも書こうかと思います。
読んでいただきありがとうございました。
UIKit版はこちら
iOS14でのUIDatePickerの挙動について(UIKit)
- 投稿日:2020-09-26T03:53:43+09:00
AVFoundationでカメラを使う最小構成
毎回調べている気がするので。
必要なプロパティ。
var captureSession = AVCaptureSession() var previewView = UIView() var previewLayer:AVCaptureVideoPreviewLayer? var videoDataOutput = AVCaptureVideoDataOutput() // VideoDataの場合 var photoOutput = AVCapturePhotoOutput()// PhotoDataの場合設定。
let device = AVCaptureDevice.default(for: AVMediaType.video) let deviceInput = try! AVCaptureDeviceInput(device: device!) captureSession.addInput(deviceInput) captureSession.addOutput(videoDataOutput) previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait previewView.layer.addSublayer(previewLayer!) captureSesion.startRunning()撮影。
VideoDataの場合、デリゲートメソッド内でフレームを取得。
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) }Photoの場合、capturePhotoしてデリゲートメソッド内で処理。
self.photoOutput?.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let imageData = photo.fileDataRepresentation() { let uiImage = UIImage(data: imageData) } }
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-09-26T03:53:04+09:00
iOS 14 対応
- 投稿日:2020-09-26T03:05:48+09:00
動物を認識して自動撮影するカメラを作る
犬・猫がフレーム内に現れたら自動でシャッターを切る機能を作ります。
手順
Visionに動物認識コンピュータービジョンリクエストがあります。
リクエスト結果でフレームに動物がいるか判別し写真を撮るように設定します。let animalRequest:VNRecognizeAnimalsRequest = { let request = VNRecognizeAnimalsRequest(completionHandler: { (request, error) in guard let animalObservation = results.first as? VNRecognizedObjectObservation else { return } // animalObservationがあれば、動物がいるので、シャッターを切る self.avCapturePhotoOutput.capturePhoto(with: settings, delegate: self as! AVCapturePhotoCaptureDelegate) }) request.revision = VNRecognizeAnimalsRequestRevision1 //リビジョン1では、認識できるのは犬・猫のみです。 return request }()captureOutputデリゲートメソッド内で、上記のリクエストを実行し、フレームを解析します。
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: orientation, options: [:]) do { try imageRequestHandler.perform([animalRequest]) } catch { print(error) } }応用
同じ手法で他のリクエストを使うと、人間や任意の物体を認識して写真を撮れます。
Observationには、犬猫の場所を示すBoundingBoxも含まれていますので、そこにオートフォーカスしたりもできます。
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-09-26T02:41:31+09:00
Xcodeで Library not found for -l "ライブラリ名" と言われた時の対処法
- 投稿日:2020-09-26T01:08:03+09:00
ARKitとAVFoundationは何秒で切り替えられるのか
ARKitとAVFoundation両方の恩恵を受けたい
Gif:ARKitを中断してAVFoundationでキャプチャ、すぐARに戻る
ARKitの世界・顔認識のトラッキングを使いながら、高画質の画像をキャプチャしたいこともあるかもしれません。
ARKitでは1920*1440が最高サイズなので、「AVFoundationでキャプチャすればいいんじゃね?」「ARKitとAVFoundation同時に使えるのかな?」と僕はなりました。調べたところ、
ARKitとAVFoundationのセッションは、同時には立ち上げられません。
では、素早く切り替えれば、何秒かかるのか。やってみました。
*iPhone11 iOS14で実験。実験手順
トラッキング状態を保存して、ARセッションを一時停止し、AVCaptureSessionをスタート。
sceneView.session.getCurrentWorldMap { [self] worldMap, error in time = 0.0 // 裏でTimerでtimeを加算しています。0に戻してここからスタート。 sceneView.session.pause() map = worldMap avCaptureSession.startRunning() //AVFoundationスタート }実験1、AVCaptureVideoDataOutputで画像を取得する
captureOutputデリゲートメソッド内で画像を取得し、一枚撮ったらすぐにAVCaptureSessionを止めて、ARセッションを再開。
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) let image = UIImage(ciImage: CIImage(cvImageBuffer: pixelBuffer!)) print(time) avCaptureSession.stopRunning() let configuration = ARWorldTrackingConfiguration() configuration.initialWorldMap = map sceneView.session.run(configuration, options: []) }0.15秒でキャプチュア。
しかし、AVFoundationでキャプチャした画像は、セッション立ち上げ直後なので暗い。
最初5フレーム落としてキャプチャしたところ、
綺麗に3840*2160で撮れて0.29秒でした。
画面の停止は0.5秒程度(シャッターを切るくらい)の体感でした。実験2、AVCapturePhotoCaptureで画像を取得する
写真用のアウトプットで撮ってみました。
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let imageData = photo.fileDataRepresentation() { let uiImage = UIImage(data: imageData) print(time) // 0.50 self.captureSession.stopRunning() let configuration = ARWorldTrackingConfiguration() configuration.initialWorldMap = map sceneView.session.run(configuration, options: []) }こちらは綺麗に写真が撮れてキャプチャまで0.5秒でした。
しかし、写真のシャッターを切ってしまうと、ARKitの復帰が遅く、7秒程度画面が止まったままでした。
結果
キャプチャの仕方 キャプチャまでの秒数 画面停止秒数 AVCaptureVideoDataOutput 0.3 0.5 AVCapturePhotoCapture 0.5 7.0 結論
実用には、VideoDataOutputで最初の数コマ落として撮るのがギリギリ、普通のシャッターぐらい中断感覚で使えるかなあ、という意見です。
ちなみに、ARWorldTrackingでデバイスの傾きデータをとったところ、セッション切り替え以前と以後で0.05ラジアン程度ずれていました。デバイス固定したらズレなかったので、キャプチャしている間の0.3秒の僕の手ブレがそれくらいということですね。
追記:ARPositionalTrackingConfigurationというデバイス位置をとるだけの構成があって、ワンチャンAVFoundationと併用できんじゃね?とやってみたら無理でした。
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-09-26T00:48:11+09:00