Open1
SwiftUI: LPLinkMetadata.iconProviderにアイコンを渡したい
アクティビティシート?シェアシート?の話です。アプリで「送る」みたいなアイコンを押すと画面下部に表示される他のアプリにデータを送るシート。
あれにアイコンが表示されたりされなかったりしてるので(前に上下反転して表示されてたこともありました)、ちゃんとアイコンを表示させようと思って調べてたんですね。
ざっくり流れは以下の感じです。
-
UIActivityViewController
をUIViewControllerRepresentable
でラップする。 -
UIActivityItemSource
に従った自前のActivityItemSource
を実装する。 - 自前のソースクラスに
activityViewControllerLinkMetadata(:)
を実装する。
アイコンの指定部分は以下のような感じです。
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
let metaData = LPLinkMetadata()
// (中略)
let iconURL = self.getIconURL()
metaData.iconProvider = NSItemProvider(contentsOf: iconURL)
return metaData
}
以下の記事を参考にしました。
問題は、このNSItemProvider(contentsOf: iconURL)
にどのURLを指定するかです。
いくつかの記事で、以下のようにバンドルからアイコンファイル名が取れるよ、みたいなのを読みました。
func getIconFileName(bundle: Bundle = .main) -> String? {
guard let icons = bundle.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any] else {
print("Could not find icons in CFBundleIcons")
return nil
}
guard let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any] else {
print("Could not find icons in CFBundlePrimaryIcon")
return nil
}
guard let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String] else {
print("Could not find icons in CFBundleIconFiles")
return nil
}
guard let iconFileName = iconFiles.last else {
print("CFBundleIconFiles is empty")
return nil
}
return iconFileName
}
ですが実際に動かすとうまく行きません。
具体的に言うと、
- iPhoneの場合はこの関数で「AppIcon60x60」という文字列が返ってきます。
- じゃあ、てことで
Bundle.main.url(forResource: withExtension:)
を使って取得しようとしても、ないよって言われてしまいます。 - iPadの場合は「AppIcon76x76」です。これもないよと言われます。
で、よくよく調べると、以下の記事に「AppIcon60x60@2x.png」「AppIcon76x76@2x~ipad.png」でメインバンドルに保存されている、って見つけました。
で、取れたファイル名に、"@2x"ってつけてメインバンドルからURLを取得すると、うまく行くんですね。
ただ、これをハードコーディングするとヤな感じですね。
なので、メインバンドルのPNGファイルを検索して、ファイル名の先頭でマッチングして、それで取得するようにしました。
func getIconFileName(bundle: Bundle = .main) -> String? {
guard let icons = bundle.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any] else {
print("Could not find icons in CFBundleIcons")
return nil
}
guard let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any] else {
print("Could not find icons in CFBundlePrimaryIcon")
return nil
}
guard let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String] else {
print("Could not find icons in CFBundleIconFiles")
return nil
}
guard let iconFileName = iconFiles.last else {
print("CFBundleIconFiles is empty")
return nil
}
let paths = bundle.paths(forResourcesOfType: "png", inDirectory: nil)
for path in paths {
let nsStringPath = path as NSString
let fileName = (nsStringPath.lastPathComponent as NSString).deletingPathExtension
if fileName.hasPrefix(iconFileName) {
return fileName
}
}
return iconFileName
}
ちなみに、これはAppIconがSingle Sizeの場合の話です。
All Sizesの場合も同じ処理で済む可能性はありますが、検証はしていません。