「UIPasteboardをもっと理解する」の後編です。前編はこちらになります。
UIPasteboardをもっと理解する (前編) - 騒音のない世界 BLOG
後編は具体的な動作例とハマりがちな注意点について見ていきます。
具体的な動作を見る
ペーストボードの中身をログに吐いて、具体的な動作を見ていきます。環境はシミュレータ(10.2)です。ログに吐くコードはこんな感じです。
func printPb(_ pb: UIPasteboard) { for item in pb.items { for (key, value) in item { print("\(key): \(value)") } } }
UITextViewやUITextFieldからテキストをコピー
画面を長押しして出るメニューからCopyを選んでHogeという文字をコピーします。
public.utf8-plain-text: Hoge com.apple.flat-rtfd: <72746664 00000000 ...> Apple Web Archive pasteboard type: <3c21444f 43545950 ...>
複数の値が入りました。1つずつ見ていきます。
最初のpublic.utf8-plain-text
はutf-8エンコーディングのプレーンテキストのようです。エンコーディングごとにUTIが違うことがわかります。2つめのrtfdはRich Text Format Directory
というやつで、画像なども含められるAppleのリッチテキストフォーマットです。3つめのWeb Archive
というのはSafariでつかうフォーマットで、htmlや画像、音などリソースも含めたウェブページを保存するフォーマットです。
このように、標準のUITextViewなどからコピーすると複数のフォーマットでコピーされることがあります。
写真アプリでコピー
標準の写真アプリで写真をコピーしてみます。
com.apple.mobileslideshow.asset.localidentifier: <31303645 39394131 ...> public.jpeg: <UIImage: 0x608000092390>, {4288, 2848}
こちらも複数の値が入りました。
最初のものはよくわかりませんが、com.apple.mobileslideshow
というのが写真アプリのバンドルIDなので、おそらく写真アプリの内部でのみ用いるデータフォーマットかと思われます。2つめは単にjpegフォーマットです。
また、テキスト、写真ともにUIからコピーするとそれまで保持していた値はクリアされるようです。
コードでコピー
コードから画像と文字列をコピーしてみます。
print("===") UIPasteboard.general.image = UIImage(named: "sample") printPb(UIPasteboard.general) print("===") UIPasteboard.general.string = "Hoge" printPb(UIPasteboard.general) print("===")
結果はこうなりました。
=== public.jpeg: <UIImage: 0x6080002809b0>, {512, 512} com.apple.uikit.image: <UIImage: 0x60800009fd10>, {170.66666666666666, 170.66666666666666} public.png: <UIImage: 0x60000009f630>, {512, 512} === public.utf8-plain-text: Hoge ===
image
プロパティに画像をセットするとjpeg, pngに加えてcom.apple.uikit.image
というものがコピーされました。元データのフォーマットによらず複数のフォーマットでコピーされます。ドキュメントを見てもpngとjpgで保存される旨が書いてあります。
テキストは単にセットするとUTF8のプレーンテキストになりました。
[2017.03.30 追記]
iOS8系, 9系だとimage
プロパティに画像をセットした時にcom.apple.uikit.image
しかコピーされません。この場合iOS8系のメモ帳アプリに貼り付けられませんでした。他にも貼り付けられないアプリがあるかもしれないので、直接items
プロパティに代入したほうが良いかもしれません。
ペースト先の処理を考える
コピーしたデータをUI操作によりペーストできるかどうかは、ペースト先のアプリが対応しているUTIにより決まります。ペースト先のアプリはペースト操作が実行されるとペーストボードから対応UTIのデータ取得を試み、あればそのデータを取得してUIに反映します。
ドキュメントにも注意点として書かれていますが、自分でコピー処理を実装する場合、ペースト先のことを考えてさまざまなフォーマットでコピーしておくと親切です。例えば、画像をコピーするためにpublic.png
にのみ保存していると、public.jpeg
にしか対応していないアプリにはペーストできないことになり、ユーザが混乱してしまう可能性があるので注意が必要です。UITextViewや写真アプリでコピーした際に複数のフォーマットでコピーされるのもそのような理由があるものと思われます。
間違えがちなポイント
特定フォーマットの存在チェック
ドキュメントによると、stringプロパティやimageプロパティのnilチェックによってペーストボード内にデータがあるかどうかをチェックしてはいけません。代わりにiOS10から追加されたhasStrings
などを使うと良いようです。これはリソースを無駄に消費しないようにするためのようです。
注意点として、hasStrings
はstring
の有無ではなくstrings
の有無と対応する、ということがあります。string
は1つめのPasteboard Item
からしか文字列を返さないのに対し、strings
はペーストボード内のすべてのPasteboard Item
からすべての文字列を返します。なので2つめ以降のPasteboard Item
に文字列が入っている場合、hasStrings
がtrueでもstring
はnilになることがあります。下記のコードで再現します。
let pb = UIPasteboard.general pb.image = UIImage(named: "sampleImage") // 1つめのPasteboard Itemは画像を入れる pb.addItems([["public.utf8-plain-text" : "hoge"]]) // 2つめのPasteboard Itemは文字列を入れる print("hasStrings: \(pb.hasStrings)") print("string: \(pb.string)")
結果
hasStrings: true string: nil
string
の有無を調べたければcontains(pasteboardTypes: [String])
を使うと良いです。こちらは先頭のPasteboard Item
しかチェックしません。文字列のUTIはUIPasteboardTypeListString
という配列から取得できるので、contains(pasteboardTypes: UIPasteboardTypeListString as! [String])
のように書けます。先程の例に適用すると、この戻り値はfalseとなります。
ペーストボードからデータ削除
UIPasteboard delete
などで検索すると空文字をセットするコードがたくさん出てきますが、これはあまりよくないかと思います。細かい話ではありますが、空文字は文字列としてペーストボードに保存されてしまうため、UITextViewで長押しした際のメニューで無駄にPaste
が出てしまいます。
データ削除はitems
プロパティを直接操作することで可能です。全て削除する場合は単にUIPasteboard.general.items.removeAll()
とすれば良いです。先程のメニューはこうなります。
おわりに
2記事に渡ってUIPasteboardについてまとめてみましたが、いかがでしたでしょうか。何か新しい発見があれば幸いです。