- 投稿日:2020-03-29T23:39:11+09:00
MVVMについて簡単なまとめ
Model
アプリケーションで使うデータの基本的な振る舞いやそれに関するロジックを保持
具体的には、
- データ構造の表現
- Web APIとのやりとり
- ローカルデータベースなどへの保存
ViewModel
- ModelとView、ViewController層の仲介役
- Modelからデータを受け取り、それらをUIに反映できるような形で出力
- View、ViewControllerからユーザーのアクション情報を受け取り、Modelに伝え、Modelからデータを受け取りUIに反映できるような形で出力
View、ViewController
View
- UIの表示
- データを表示するようなUIの場合、Controllerからデータを受け取り、UIに反映
- ユーザーインタラクションの認知し、必要に応じてUIを更新する。
- もしくは、そのユーザーインタラクションをした結果、何かアクションをしたいとき、ユーザーインタラクションの情報をControllerに伝達する。
- View層はデータ構造に関する一切のロジックを保持しない。
ViewController
- ViewとViewModelの仲介役を行う。
- ViewModelから受け取った出力をViewに反映させてUIを更新
- ユーザーのアクションをViewModelに伝え、ViewModelから新しい出力を受け取り、Viewに反映させてUIを更新
- 投稿日:2020-03-29T20:54:19+09:00
【Flutter】バグ解決: Bad state: Stream has already been listened to.
今回の件のGitHubはこちら
https://github.com/Tetsukick/flutter_BLoc/commit/76748e912be5896f8e998a5a50244fcb77d659b6
現象
StreamControllerを使ってBLoCパターンでstatement管理をしていたが、TabBarViewを追加して、
Tabを切り替えて、再度同じタブに復帰した際に画面の表示が崩れるようになりました。その際のerrorがこちら。
Bad state: Stream has already been listened to.解決策
StreamController
ではなく、BehaviorSubject
を使用することで解消元のソースコード
エラー解消前import 'dart:async'; class CounterBloc { // input final _actionController = StreamController<bool>(); Sink<void> get changeCountAction => _actionController.sink; //output final _countController = StreamController<int>(); Stream<int> get count => _countController.stream; int _count = 0; CounterBloc() { _actionController.stream.listen((isPlus) { if (isPlus) { _count++; } else { _count--; } _countController.sink.add(_count); }); } void dispose() { _actionController.close(); _countController.close(); } }エラー解消後import 'dart:async'; import 'package:rxdart/rxdart.dart'; class CounterBloc { // input final _actionController = BehaviorSubject<bool>(); Sink<void> get changeCountAction => _actionController.sink; //output final _countController = BehaviorSubject<int>(); Stream<int> get count => _countController.stream; int _count = 0; CounterBloc() { _actionController.stream.listen((isPlus) { if (isPlus) { _count++; } else { _count--; } _countController.sink.add(_count); }); } void dispose() { _actionController.close(); _countController.close(); } }その他
勉強不足で
StreamController
とBehaviorSubject
の違いをまだ理解できておりません。
ご存知の方いましたら、教えていただけますと幸甚です。参考記事
https://stackoverflow.com/questions/51396769/flutter-bad-state-stream-has-already-been-listened-to
- 投稿日:2020-03-29T20:24:20+09:00
[iOS]Tesseract OCRで認識させた日本語の情報を取得する
※ これは 2018/4/8,16 に個人ブログへ投稿した内容をまとめて転記したものです。
Google が出資しているという文字認識 API の iOS 用ラッパー、 Tesseract OCR iOS を使って文字認識をさせてみた備忘録です。
環境: macOS HighSierra, MacBook Pro 2016, XCode9.3準備
本体のリポジトリはここ。ただし、 CocoaPods から読ませるので、直接 Clone しない。
CocoaPods が入ってない場合は、まず CocoaPods をインストール。
終わったら下記の記事を参考にインストールしていく。
Xcode 7.0 + Swift2 でTesseract-OCR-iOSを使う(追記あり) - 今日も微速転進動かしてみる
テストコードは公式 Wiki の方を使用した。シミュレータで動かす分にはこちらのほうがシンプルで良い。
上の記事でも言っているとおり、 BridgingHeader はいらないので惑わされないように1
Tesseract OCR iOS の中身は Tesseract3.03 らしく、最新2の学習データ (tessdata) は使えない。
Github で公開されている 3.04 か、 Wiki からそれ以前のものを選んで使う。英語版 (eng.tessdata) は 3.04 そのままでも動くが、日本語版 (jpn.tessdata) は 3.04 を使うとエラーが出る。
read_params_file: parameter not found: allow_blob_divisionこれを解決するために、下記に従って学習データ内のパラメータを変更するのだが、このときビルドに使うソースは3.04を使うこと。
4.00 だと加工後の tessdata を読み込んでくれない。それから、
./configure
にパラメータをつけると私の環境では失敗してしまったので、パラメータをなくしたら上手くいった。
これはこの記事を読みながら事前にbrew link icu4c --forceしておいたからかも。
./autogen.sh ./configure make sudo make install make training sudo make training-installなお、一度 4.00 をインストールしてしまったときは、 make したディレクトリで
sudo make uninstall sudo make training-uninstallしたらアンインストールできた…気がする。
日本語を認識させてみる
日本語を認識させて結果の情報を取得してみました。
ソース
Swift 初心者なので、 API リファレンスを見ながら、 Xcode に言われるがまま書いたソースはこちら。
結果
あえてちょっと斜めにしてみましたが、いい感じに認識していますね!
技術的なメモ
認識結果とその表示
let box = block.boundingBox(atImageOf: actualImageRect.size)Tesseract では元画像のサイズを 1×1 としたときの相対位置で対象 (文字) の座標を認識しているようです。
なので、デバイスの画面に収まるように画像を自動レイアウトした場合は、レイアウト後のサイズを取得し、それをatImageOf
に指定して変換してもらう必要があります。
レイアウト後の画像の実サイズを取得する Extension、UIImageView.ContentClippingRect
は下記より。
How to find an aspect fit image’s size inside an image viewExtensionって最高ですね…!
文字のまとまり
let blocks = tesseract.recognizedBlocks(by: G8PageIteratorLevel.symbol)
G8PageIteratorLevel
で認識結果のまとまりの大きさを指定できます。
意味深な定数名が付いていますが、もちろん日本語の形態素解析をして云々なんてしていないので、一通り試してみました。block: ブロック。段落を複数認識したら、そのまとまりということかな?
- 投稿日:2020-03-29T18:01:56+09:00
iOSのWebViewをMacでデバッグする
はじめに
以前Webブラウザー作っていた時、一番困ったのが「Safariで動くのにこのブラウザーでは動かない!!」というお問い合わせでした。
そんな時、お世話になった"Webインスペクタ"の使い方を記事にすることで、誰か救われたらいいなーと思い書くことにしました。必要なもの
- Mac端末(当時Yosemiteでやってたので、それなりに古くても大丈夫だと思います)
- iOS端末(当時iOS 8でやってた記憶があるので、それ以降ならできます)
- MacとiOSを接続する設備(MacによってはUSB typeCしかないので変換アダプタだったり必要かと)
手順
1.iOSの[設定]アプリでSafariのWebインスペクタを有効化
3.MacとiOS端末を接続して、Mac Safariの開発メニューを表示すると・・・
こんな感じで表示されます!!!
今回はSafariを例に出していますが、UIWebView/WKWebViewでコンテンツ表示しているアプリはリストアップされます。その際はDebugビルドしたアプリでご利用くださいね!(Releaseビルドしたアプリは表示されません)例
例えば、qiita.comを選んで、Webインスペクタの要素タブでヘッダーの部分を選択すると、iOS端末側で該当箇所にスモークがかかり、示してくれます。
その他にも、
- デバッガタブでJavaScriptにブレークポイント張ってステップ実行
- コンソールログ確認
- ネットワークタブで、発生した通信の確認
- ストレージタブで記録されてるCookieの確認
などなど、いろいろなことができるようになります。
なぜか表示されない・・・そんな時
- 開発メニュー内に接続したiOS端末が表示されない
→MacのSafariを開いた状態でiOS端末を接続した時、うまく行かないケースが私の手元で何度か見られたので、その時はMacのSafariを再起動してみてください。私の手元では解消しました。
(iOS端末の再接続では解決せず)
- 開発メニューで接続したiOS端末は表示されたが、アプリが表示されない
→Debugビルドしたアプリを使ってますか?(Releaseビルドしたアプリは表示されません)
- 投稿日:2020-03-29T15:12:54+09:00
ARKit 3.5のScene Reconstructionサンプルのコードを読む
ARKit 3.5とLiDAR搭載の新型iPad Proが出ましたね。これらを試せるAppleの公式サンプル「Visualizing and Interacting with a Reconstructed Scene」のソースコードを読んでみたメモです。
どんな感じのサンプルかはこちらのツイートの動画がよくわかります。
First look at the iPad Pro LiDAR Scanner pic.twitter.com/kwkl1YBy2n
— Tim Field (@nobbis) March 25, 2020Scene Understandingの設定
ARView.Environment.SceneUnderstanding
構造体のoptions
(型はARView.Environment.SceneUnderstanding.Options
)に.occlusion
と.physics
を追加することでオクルージョンと物理計算を有効化。arView.environment.sceneUnderstanding.options = [] // Turn on occlusion from the scene reconstruction's mesh. arView.environment.sceneUnderstanding.options.insert(.occlusion) // Turn on physics for the scene reconstruction's mesh. arView.environment.sceneUnderstanding.options.insert(.physics)
SceneUnderstanding
オブジェクトはARView
のenvironment
プロパティ(型はARView.Environment
)が持つsceneUnderstanding
プロパティにセットする。https://developer.apple.com/documentation/realitykit/arview/environment/sceneunderstanding/options
デバッグ用にメッシュを可視化
公式サンプルではメッシュが深度に応じて色分けされた見事なビジュアライゼーションが実現されている。
どうやっているのかというと、
ARView
のdebugOptions
プロパティに.showSceneUnderstanding
を追加するだけ。arView.debugOptions.insert(.showSceneUnderstanding)ちなみに
ARSCNView
ではこれができない。Appleの公式サンプルのかっちょいいメッシュのビジュアライゼーションも、ARView.DebugOptionsには追加されたshowSceneUnderstandingというオプションで実現されている。
— Shuichi Tsutsumi (@shu223) March 28, 2020
これに相当するオプションがARSCNViewには追加されていない?ARKit+SceneKit路線は今後メンテされないと思ったほうがよさそう。 pic.twitter.com/IXWvP3QpqVScene Reconstructionの有効化
ARView
の自動設定をオフにし、ARWorldTrackingConfiguration
のsceneReconstruction
プロパティに.meshWithClassification
を指定する。デフォルト以外に.none
と.mesh
(メッシュのClassificationを行わない)がある。arView.automaticallyConfigureSession = false let configuration = ARWorldTrackingConfiguration() configuration.sceneReconstruction = .meshWithClassification configuration.environmentTexturing = .automatic arView.session.run(configuration)タップでClassification結果を可視化
Classificationというのはそのメッシュが壁なのか床なのかテーブルなのか、という分類を示す情報で、ARMeshClassificationというenumで定義されている。
public enum ARMeshClassification : Int { case none = 0 case wall = 1 case floor = 2 case ceiling = 3 case table = 4 case seat = 5 case window = 6 case door = 7 }https://developer.apple.com/documentation/arkit/armeshclassification
公式サンプルでは、画面(
ARView
)タップでraycastによる(.estimatedPlane
との)当たり判定を行い、classificationの判定結果をテキスト表示している。if let result = arView.raycast(from: tapLocation, allowing: .estimatedPlane, alignment: .any).first { let resultAnchor = AnchorEntity(world: result.worldTransform) ... }ここらへんのコードが実はおもしろくて、ここまで簡単にScene Reconstructionやメッシュの可視化ができるのでClassificationの結果も
ARMeshAnchor
のプロパティから取り出して終わりでしょ、と思いきや、そんなプロパティはなく、意外とめんどくさい処理をやっている。https://developer.apple.com/documentation/arkit/armeshanchor
ARMeshAnchorからARMeshClassificationを得る
具体的には
ARMeshAnchor
のgeometry
プロパティに入っているARMeshGeometry
オブジェクトのclassification
プロパティから取り出しているのだが、これがまだARMeshClassification
型ではなくて、ARGeometrySource
型なのである。var vertices: ARGeometrySourcehttps://developer.apple.com/documentation/arkit/armeshgeometry
で、
ARGeometrySource
というのが割とめんどくさいクラスで、そのデータをMTLBuffer
というMetalのバッファに保持している。open class ARGeometrySource : NSObject, NSSecureCoding { /** A Metal buffer containing per-vector data for the source. */ open var buffer: MTLBuffer { get } ... }https://developer.apple.com/documentation/arkit/argeometrysource
サンプルではこのバッファをGPUではなくCPUでほじくり、とりだした生の数値から
ARMeshClassification
を初期化している。extension ARMeshGeometry { ... /// To get the mesh's classification, the sample app parses the classification's raw data and instantiates an /// `ARMeshClassification` object. For efficiency, ARKit stores classifications in a Metal buffer in `ARMeshGeometry`. func classificationOf(faceWithIndex index: Int) -> ARMeshClassification { guard let classification = classification else { return .none } assert(classification.format == MTLVertexFormat.uchar, "Expected one unsigned char (one byte) per classification") let classificationPointer = classification.buffer.contents().advanced(by: classification.offset + (classification.stride * index)) let classificationValue = Int(classificationPointer.assumingMemoryBound(to: CUnsignedChar.self).pointee) return ARMeshClassification(rawValue: classificationValue) ?? .none }タップ位置に近いメッシュのfaceを抽出
前項の処理で説明してないことがあって、
classificationOf(faceWithIndex:)
メソッドではfaceのindexを引数に渡し、そのfaceについてのclassifiation結果を取り出している。メッシュの中にたくさんある中で、タップした位置に近いfaceを取り出してそのclassification結果を可視化しているわけだ。
その「タップした位置に近い(5cm以内)場所にあるfaceを取り出す」実装はこうなっている。
for anchor in meshAnchors { for index in 0..<anchor.geometry.faces.count { // Get the center of the face so that we can compare it to the given location. let geometricCenterOfFace = anchor.geometry.centerOf(faceWithIndex: index) // Convert the face's center to world coordinates. var centerLocalTransform = matrix_identity_float4x4 centerLocalTransform.columns.3 = SIMD4<Float>(geometricCenterOfFace.0, geometricCenterOfFace.1, geometricCenterOfFace.2, 1) let centerWorldPosition = (anchor.transform * centerLocalTransform).position // We're interested in a classification that is sufficiently close to the given location––within 5 cm. let distanceToFace = distance(centerWorldPosition, location) if distanceToFace <= 0.05 { ... } } }この計算をするためには、
ARMeshGeometry
のvertices
プロパティとfaces
プロパティを使用する必要があり、これまたARGeometrySource
型。Metalバッファから数値を読み出すために、ARMeshGeometry
のextensionとして次のようなメソッドが実装されている。extension ARMeshGeometry { func vertex(at index: UInt32) -> (Float, Float, Float) { assert(vertices.format == MTLVertexFormat.float3, "Expected three floats (twelve bytes) per vertex.") let vertexPointer = vertices.buffer.contents().advanced(by: vertices.offset + (vertices.stride * Int(index))) let vertex = vertexPointer.assumingMemoryBound(to: (Float, Float, Float).self).pointee return vertex } ... func vertexIndicesOf(faceWithIndex faceIndex: Int) -> [UInt32] { assert(faces.bytesPerIndex == MemoryLayout<UInt32>.size, "Expected one UInt32 (four bytes) per vertex index") let vertexCountPerFace = faces.indexCountPerPrimitive let vertexIndicesPointer = faces.buffer.contents() var vertexIndices = [UInt32]() vertexIndices.reserveCapacity(vertexCountPerFace) for vertexOffset in 0..<vertexCountPerFace { let vertexIndexPointer = vertexIndicesPointer.advanced(by: (faceIndex * vertexCountPerFace + vertexOffset) * MemoryLayout<UInt32>.size) vertexIndices.append(vertexIndexPointer.assumingMemoryBound(to: UInt32.self).pointee) } return vertexIndices } func verticesOf(faceWithIndex index: Int) -> [(Float, Float, Float)] { let vertexIndices = vertexIndicesOf(faceWithIndex: index) let vertices = vertexIndices.map { vertex(at: $0) } return vertices } func centerOf(faceWithIndex index: Int) -> (Float, Float, Float) { let vertices = verticesOf(faceWithIndex: index) let sum = vertices.reduce((0, 0, 0)) { ($0.0 + $1.0, $0.1 + $1.1, $0.2 + $1.2) } let geometricCenter = (sum.0 / 3, sum.1 / 3, sum.2 / 3) return geometricCenter } }この実装はたぶんめちゃくちゃ多くの人がコピーして使うと思う。サンプルの鑑。
関連
- 投稿日:2020-03-29T14:40:18+09:00
[悲報]Xcode 11.4 でナビゲーションバーのタイトルの色が黒に固定される(*条件あり)
それはXcodeをアップデートした直後のことでした・・・
俺氏「ん?!ナビゲーションタイトルの文字が黒にしかならんやんけ?」
発生条件
- navigation controllerのBar Tintを変更している場合
上記場合に、Title Colorを変更しても、タイトルの色は黒固定になっちゃいます。
Bar Tintをデフォルトにしとけばタイトルカラーを変更できます。
デフォルトカラー以外では変更できないから超不便?
解決法
- 多分Xcode11.4のバグ、アップデートを待ちましょう←
- Xcode11.3.1にダウングレードする(多分これがいい(小並感))
- おーばーふろーで無理やり変更してる人もいた
それではみなさん楽しいバグライフを!?
- 投稿日:2020-03-29T14:40:18+09:00
【悲報】Xcode 11.4 でナビゲーションバーのタイトルの色が黒に固定される(*条件あり)
それはXcodeをアップデートした直後のことでした・・・
俺氏「ん?!ナビゲーションタイトルの文字が黒にしかならんやんけ?」
発生条件
- storyboardでnavigation controllerのBar Tintを変更している場合
上記場合に、Title Colorを変更しても、タイトルの色は黒固定になっちゃいます。
Bar Tintをデフォルトにしとけばタイトルカラーを変更できます。
デフォルトカラー以外では変更できないから超不便?
解決法
- 多分Xcode11.4のバグ、アップデートを待ちましょう←
- Xcode11.3.1にダウングレードする(多分これがいい(小並感))
- おーばーふろーで無理やり変更してる人もいた
それではみなさん楽しいバグライフを!?
- 投稿日:2020-03-29T14:10:40+09:00
【初心者向け】【まとめ】iOSアプリを作ろうとしている方がまず見るべきQiitaの記事をまとめてみました【Swift】
iOSアプリのためのまとめ記事が欲しかった
iOSエンジニアになって早8ヶ月。
最近ようやく慣れてきたものの、初めは実装するのにとっても時間がかかりました。
というのは、実装についてまとまったオールインワンの記事があまりなかったことがもしかしたら原因なのかもと思っています。(自分でもっと勉強しろって話かもしれないですが…)
なので、今回は、自身が開発をしていく上で参考にした記事をまとめてみたいと思います。
これからiOS開発をする方に参考になればと思っています。
参考になるかもしれないソースコード
この記事を作っていくにあたって、以下の記事を参考にして実装したものを載せています。
これを見ながら記事と共に勉強していただければ幸いです。
https://github.com/taichi6930/iOSAppBase/tree/master
早速見るべき記事
ライフサイクル
iOSアプリがどのように動いているか、といった感じです。
作る際にここがわかっていないと「??」となりまくってしまいます(自分がそうでした)。アプリ作成前に一度読んでおくと良いと思います。
iOSアプリのライフサイクル
https://qiita.com/KenNagami/items/766d5f95940c76a8c3cdUIViewControllerのライフサイクル
https://qiita.com/motokiee/items/0ca628b4cc74c8c5599dAppDelegate,UIViewController,UIViewのライフサイクル/iOS/Swift
https://qiita.com/kayo311/items/4710c4ac02a191652a96iosアプリ ViewControllerのライフサイクル
https://qiita.com/usutan/items/71760df10e8523166babページ遷移について
ページ間での移動についてです。
ここも全然分からなかったので、最初に読んでおいた方がいいと思います。
(ページ遷移しないと、ただのワンページアプリになってしまうのでw)同じ/異なるStoryboardでの画面遷移
https://qiita.com/kedarui/items/97b5cc1410d9c61933d5Swiftのページ遷移【Navigation Controller】
https://qiita.com/ryu1_f/items/4a0e452e94c9ba609220TableView
画像のようにcellが縦方向に続いていくViewです。
自身のソースコードではセルの生成とタップ時の処理を記載しました。
(例が少ないので足していく予定です…)参考になった方々の記事です↓
SwiftでTableViewを使ってみよう
https://qiita.com/pe-ta/items/cafa8e20029047993025UITableViewの使い方 【Swift4.2 , Xcode10】
https://qiita.com/abouch/items/3617ce37c4dd86932365UITableViewのデリゲートメソッドまとめ
https://qiita.com/kagemiku/items/22b74010365723c5c4feCollectionView
画像のようにcellが横方向に続いていくViewです。
自身のソースコードでは、セルの生成とタップ時の処理を記載しています。
TableViewと同様に作成すれば良かったので、まずはTableViewを作成してみてください!
WebView
アプリ内でWebページが見れるViewです。
今回はWKWebViewを使用して作成しました。
cookie処理などは入れていないので、今後行っていければいいなと思っています。
参考になった方々の記事です↓
WebKit View(WKWebView) を実装
https://qiita.com/MdRk/items/34912e7ba43568f15905WKWebViewについてのまとめ
https://qiita.com/s_emoto/items/dc3d61626155f5cf83e7
(まだ実装出来ていないので今後していきたい…)生体認証
端末によってはFaceIDやTouchIDでログインすることができるアプリがあります。
端末によってtouchIDだったりFaceIDだったり変化させています。
そこまで難しくなかったので実装してみました。
参考になった方の記事↓
【iOS 11】LocalAuthenticationでFace IDとTouch IDの認証を実装する
https://qiita.com/MilanistaDev/items/b0cd432290d18f336766Alert表示
画像を見れば明らかですがアラート表示についてです。
参考になった方々の記事↓
【Swift】アラートを表示する(Alert/ActionSheet)
https://qiita.com/funacchi/items/b76e62eb82fc8d788da5最後に
この記事は自分のために作ったみたいなところがあります。
今後もメモのような感じで残せたらと思います。何か不足だったり指摘だったり、こんなの載せて欲しいというのがあったら是非気軽に言っていただきたいです!
- 投稿日:2020-03-29T12:59:41+09:00
Vue.js×TypeScriptでのテキストコピー(iOS対応)
Vue.js×TypeScriptで「ワンタップでテキストをコピーする」ボタンを作ったらハマりポイントがたくさんありました。
生jsやjQueryでの解決策はたくさん見つかりましたが、Vue.js×TSは見つからなかったのでメモです。やりたいこと
フォームに文字入力した時に、飾り文字を追加した文章を出力して、ワンタップでコピーできるようにする。
完成したコード
<template lang="pug"> .CopyText button(@click.prevent="copyTexts") span.copy-message クリップボードにコピー .formatted-text span.recipe span#copy-text {{formattedTitle}}<br> </template> <script lang="ts"> import { Recipe } from "../../components/molecules/RecipeTitle.vue"; import Vue, { PropType } from "vue"; export default Vue.extend({ props: { recipe: { type: Object as PropType<Recipe>, default: {} } }, computed: { //inputで入力した内容ではなく、ここでフォーマットしたテキストがコピー対象 formattedTitle(): string { return this.recipe.title ? `【${this.recipe.title}】` : ""; } }, methods: { //iOSの判定 isIOS() { const agent = window.navigator.userAgent; return agent.indexOf("iPhone") != -1 || agent.indexOf("iPad") != -1; }, //コピー copyTexts(): void { if (this.isIOS()) { //iOSの場合 const doc: HTMLInputElement = document.getElementById( "copy-text" ) as HTMLInputElement; const selected = window.getSelection(); const range = document.createRange(); range.selectNodeContents(doc); selected!.removeAllRanges(); selected!.addRange(range); document.execCommand("copy"); } else { //それ以外 const formattedText = `${this.formattedTitle}`; navigator.clipboard.writeText(formattedText); } } } }); </script>参考:Javascriptによるコピー機能(クロスブラウザ対応)
ハマった部分の解説
iOSでのコピー
jsでコピーをしようと思ったらnavigator.clipboardを使用するのが一般的かと思います。
ユーザーエージェントなどの情報を扱うNavigatorインターフェイスにclipboardプロパティを追加して、writeText()メソッドを呼び出すことで、テキストがコピーできます。
navigator.clipboard.writeText(text);しかし、このnavigator.clipboardはiOSの10以降、textareaなど一部のタグからしかコピーできないなど仕様が変わっています。
今回はinputに入力した文字ではなく、フォーマットをかけたテキストをコピーするため、まさにこの条件に引っかかり、iOSのsafariとchromeで動作しませんでした。
参考: Copy to clipboard using Javascript in iOS
そのため、iOSとそれ以外でコピーの処理を変える必要があります。
iOSかどうかの判定
isIOS() { const agent = window.navigator.userAgent; return agent.indexOf("iPhone") != -1 || agent.indexOf("iPad") != -1 || agent.indexOf("iPod") != -1; }navigator.userAgentを使います。
今回はブラウザではなくiOSかどうかだけ判定するので、上記のようにしてみました。
iOS用のコピー
iOSのコピーは、コピーしたい文章を選択→コピーの実行という流れで行います。
const doc = document.getElementById("copy-text"); const selected = window.getSelection(); const range = document.createRange(); range.selectNodeContents(doc); selected.removeAllRanges(); selected.addRange(range); document.execCommand("copy");ユーザーはワンタップするだけですが、内部の動作はマウスなどで文章選択→コピーをするのと同じです。
2行目のwindow.getSelectionはselectionオブジェクトを取得するものです。
selectionオブジェクトは、ユーザーが選択した範囲のDOMに関する情報を持つことができます。
3行目のcreateRangeはdocument中のテキストやノードに関する情報を持つrangeオブジェクトを作成します。
rangeオブジェクトを作成しただけでは何も情報を持っていないため、4行目のrange.selectNodeContents(doc)で、最初に取得した要素を渡します。
5行目は2行目に取得したselectionオブジェクトが現在持っているrangeに関する情報をあらかじめ削除する処理です。文章がすでに選択されてselectionオブジェクトに情報が設定されている場合、この後の処理が無視されるので先に削除してしまいます。
これにより、6行目でselectionオブジェクトに作成したrangeオブジェクトを追加することができます。
最後のdocument.execCommand()はhtmlのdocumentオブジェクトを操作するコマンドを実行します。copyは選択範囲をクリップボードにコピーするコマンドです。
これでiOSでもテキストコピーができるようになりました!
参考: memo: テキスト全選択の JavaScript コードが動かなくなったので修正した
TSで"Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Node'"
上記のコードはType Scriptを使うと以下の部分でエラーを吐きます。
//Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Node' const doc = document.getElementById("copy-text");document.getElementById() はHTMLElement型もしくはnullを返しますが、nullを返す可能性があるとTSがエラーを出すようです。
そのため、返り値がHTMLElement型であることを明示的に示します。
const doc: HTMLInputElement = document.getElementById("copy-text") as HTMLInputElement;TSでObject is possibly 'null'エラー
rangeの削除、追加部分でも型エラーが出ます。
selected.removeAllRanges(); //Object is possibly 'null' selected.addRange(range); //Object is possibly 'null'これはselectedの部分がnullの可能性があることで出るエラーです。
そこで、!をつけて、selectedがnullでもundefinedでもないことを推論させます。ただ、この方法はESlintで"Forbidden non-null assertion"の警告が出ます。
- 投稿日:2020-03-29T06:22:43+09:00
【Xcode】CocoaPods で複数ライブラリを導入したらハマった話
はじめに
個々のサンプルプロジェクトでは問題なく動いていたのに、1つのプロジェクトにライブラリをまとめたらハマってしまったので、原因と対策をメモしておきます。
発端
イーサリアム用のライブラリと、AWS 用のライブラリのサンプルをそれぞれ試して導入の目処がたったので、下記の Podfile にて、開発中のプロジェクトへ導入しました。
Podfilesource 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.2' target 'AppEnv' do use_frameworks! pod 'web3swift' pod 'AWSAppSync' pod 'AWSS3' endで、サンプルのコードを開発プロジェクトへ取り込んでコンパイルしたところ、web3swift 関連コードでエラーが発生。「あれ?」と思ってフレームワークを見てみると、なぜか頭が大文字にリネーム(?)された Web3swift に差し変わっていてポカンとなりました。
原因
pod install のログをよくみてみたらヒントがありました。
$ pod install
Analyzing dependencies
Downloading dependencies
Installing AWSAppSync (3.1.0)
Installing AWSCore (2.13.1)
Installing AWSS3 (2.13.1)
…省略…
Installing Starscream (3.0.6)
Installing SwiftRLP (1.2)
Installing secp256k1_swift (1.0.3)
Installing web3swift (2.1.2)
Generating Pods project
Integrating client projectweb3swift のバージョンが (2.1.2) です。
サンプルでは最新の (2.2.1) だったので、古いバージョンがインストールされたことになります。(※ web3swift が Web3swift にリネームされたように見えたのは、(2.1) → (2.2) のタイミングで、たまたまフレームワーク名が変更されていたからのようでした)
で、2つのサンプルプロジェクトの Podfile.lock を見比べたところ、Starscream が両ライブラリから参照されており、このライブラリのバージョンを AWS 側へあわせるために、web3swift のバージョンが下げられてしまっていたようです。
回避策
コンパイルを通すため、web3swift の最新版がインストールされるように試した結果が下記となります。
まず、インストール済みの関連ファイル(Pods フォルダ、Podfile.lock, プロジェクトのワークスペース)を一旦削除し、下記の内容で web3swift だけをインストールしました。
Podfile(AWSの2ライブラリはコメントアウト)target 'AppEnv' do use_frameworks! pod 'web3swift' # pod 'AWSAppSync' # pod 'AWSS3' end$ pod install
Analyzing dependencies
Downloading dependencies
Installing BigInt (4.0.0)
Installing CryptoSwift (1.0.0)
Installing PromiseKit (6.8.5)
Installing Starscream (3.1.1)
Installing secp256k1.c (0.1.2)
Installing web3swift (2.2.1)
Generating Pods project
Integrating client projectこれで、最新版の、(2.2.1) がインストールされました。
つづいて、AWS の2ライブラリを追加でインストール。Podfile(AWSのコメントを外して有効に)target 'AppEnv' do use_frameworks! pod 'web3swift' pod 'AWSAppSync' pod 'AWSS3' end$ pod install
Analyzing dependencies
Downloading dependencies
Installing AWSAppSync (2.15.0)
Installing AWSCore (2.12.7)
Installing AWSS3 (2.12.7)
Installing ReachabilitySwift (4.3.1)
Installing SQLite.swift (0.12.2)
Generating Pods project
Integrating client projectStarscream と web3swift に変化がなく、AWS 関連のライブラリだけがインストールされていることがわかります。
で、コンパイルも無事通りました。
AWS側のバージョンが若干古くなってしまいましたが、テストした結果、欲しい機能への影響はなかったので一件落着です。
まとめ
異なるライブラリを CocoaPods で一気に導入する際は、ライブラリ間の相性により、全てのライブラリを最新バージョンにできないことがあるようです。もし、期待するバージョンがインストールされなかった場合は、優先度の高いライブラリ側へ合わせて、インストールの順番の調整することで妥協点を探りましょう。
補足
後から導入するライブラリを pod update でインストールするとどうなるでしょうか?
今回の例だと、こんな感じになります。
$ pod update
Analyzing dependencies
Downloading dependencies
Downloading dependencies
Installing AWSAppSync (3.1.0)
Installing AWSCore (2.13.1)
Installing AWSS3 (2.13.1)
…省略…
Installing Starscream 3.0.6 (was 3.1.1)
Installing SwiftRLP (1.2)
Installing secp256k1_swift (1.0.3)
Installing web3swift 2.1.2 (was 2.2.1)
Removing secp256k1.c
Generating Pods project
Integrating client projectpod update により、導入済みのライブラリが「アップデート」され、Starscream が (3.1.1) → (3.0.6) に、 web3swift が (2.2.1) → (2.1.2) に、それぞれデグレードされています(※そのかわり、AWSAppSync と AWSS3 が最新になっています)。
今回のように、既存のライブラリのバージョンを変えられたくない場合、 pod update の利用は控えておきましょう。