昨年 も 一昨年 も書いたように、今年も開発ライセンスでデプロイしたアプリの有効期限が1年が切れました!
私は以下のような自分専用のiOSアプリを作り使っています、
- TodoTasks : Todo管理アプリで、自分に使いやすいRedmineのフロントエンドiOSアプリ
- WebArchives : ブラウザーで気になったページをPDFに変換しFirebaseに保存・閲覧するiOSアプリ
Bing Image Creatorが生成した画像を使っています
毎度のことだけど
開発ライセンスでデプロイしたアプリの有効期限が1年です。ただし、最新のXcodeで再コンパイル、iOSデバイスで実行すればiOSデバイスにデプロイされます。
今回もiOS18用にコンパイル・実行したところ、TodoTasksは問題無く動作しました。しかしWebArchivesで問題が発生しました。😵
WebArchivesに付いて
WebArchivesは、大きく3つのコードからできています。
- Firebaseに保存してあるPDFの一覧を表示し、選択したPDFの表示
- 指定されたURLのWebページを表示し、PDFKitを使いPDFファイルを作成しFirebaseに保存する
- SafariのアクションメニューにWebArchivesを追加し、メニューをクリックすると現在表示しているWebページのURLを渡し、WebArchivesアプリを起動し2.の機能を呼出すSafari App Extension
地道にデバッグすると、3.のSafari App ExtensionからWebArchivesアプリを起動する部分で失敗していました。
修正
EXSinkLoadOperator loadItemForTypeIdentifier:completionHandler:expectedValueClass:options:] nil ...
というエラーが発生しています。ネットを検索するとApple Developer ForumsやStack Overflowに対処方法らしきものが出ていましたが、上手くいきません。
そこで見つけたRésoudre l’erreur _EXSinkLoadOperator de NSItemProviderというフランス語のページを見てみるとエラーを起こすコードと対応したコードが書かれていました!
記事本文は読んでませんが、記事内のコードを参考に自分のコードを少しずつ修正してみたところ動きました❗
エラーになる旧コード
import UIKit
import WebKit
import MobileCoreServices
class ActionViewController: UIViewController, WKNavigationDelegate {
private var webViewURL: String?
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = CGSize(width: 200, height: 100)
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first,
itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (itemUrl, error) in
if let url = itemUrl as? URL {
self.webViewURL = url.absoluteString
}
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let webURL = self.webViewURL {
let url = "com.ey-office.apps://web-archives/\(webURL)"
if !openURL(URL(string: url)!) {
print("-- Can't invoke \(url)")
}
}
self.extensionContext?.completeRequest(returningItems: self.extensionContext?.inputItems, completionHandler: nil)
}
@objc private func openURL(_ url: URL) -> Bool {
var responder: UIResponder? = self
while responder != nil {
if let application = responder as? UIApplication {
return application.perform(#selector(openURL(_:)), with: url) != nil
}
responder = responder?.next
}
return false
}
}
修正後のコード
この修正で本当に良いのか自信はありませんが、自分用アプリなので動けばOKです。😃
修正内容は
- ①
UIApplication.shared.open
をSafari App Extensionで使うための、おまじない - ② ③の
UTType.url.identifier
を使うためにのおまじない、iOS14以下の場合は無視 😃 - ③
"public.url"
をiOS14で導入されたUTType.url.identifier
に変更 - ④ 従来のコードでは実行時に無理矢理Objective-C的な方法で
openURL
を呼び出していたのを、①のおまじないのおかげでUIApplication.shared.open
が使えます
import UIKit
import WebKit
import MobileCoreServices
import UniformTypeIdentifiers
@available(iOSApplicationExtension, unavailable) // ← ①
class ActionViewController: UIViewController, WKNavigationDelegate {
private var webViewURL: String?
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = CGSize(width: 200, height: 100)
if #available(iOSApplicationExtension 14.0, *) { // ← ②
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first as? NSItemProvider,
// ↓ ③
itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
itemProvider.loadItem(forTypeIdentifier:UTType.url.identifier) { [weak self] itemUrl, error in
guard let self else { return }
if let url = itemUrl as? URL {
self.webViewURL = url.absoluteString
}
}
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let webURL = self.webViewURL {
let url = "com.ey-office.apps://web-archives/\(webURL)" // ↓ ④
UIApplication.shared.open(URL(string: url)!, completionHandler: { result in
if !result {
print("-- Can't invoke \(url)")
}
})
}
self.extensionContext?.completeRequest(returningItems: self.extensionContext?.inputItems, completionHandler: nil)
}
}
まとめ
iOSのAPIはdepreciated(非推奨)になっても直ぐにはなくなりませんが、将来のOSアップデートで無くなります。
また今回はObjective-C的なAPI呼出しが動作しなくなりました、iOS(macOSも)は元々OBjective-Cという動的オブジェクト指向言語で書かれていましたが、アプリケーション開発用にSwiftという静的なオブジェクト指向言語を導入されてからもObjective-C的なAPI呼出しが許されています。ただし徐々に非推奨になって行くのでしょうかね?
毎度毎度のことですが、年1回のライセンス切れでiOSをアップデートするのは勉強になりますね。😅