EY-Office ブログ

今年も来ました 『 ”〜〜アプリ”はもう利用できません』今回は手強いぞ

昨年一昨年 も書いたように、今年も開発ライセンスでデプロイしたアプリの有効期限が1年が切れました!

私は以下のような自分専用のiOSアプリを作り使っています、

  • TodoTasks : Todo管理アプリで、自分に使いやすいRedmineのフロントエンドiOSアプリ
  • WebArchives : ブラウザーで気になったページをPDFに変換しFirebaseに保存・閲覧するiOSアプリ

アプリはもう利用できません Bing Image Creatorが生成した画像を使っています

毎度のことだけど

開発ライセンスでデプロイしたアプリの有効期限が1年です。ただし、最新のXcodeで再コンパイル、iOSデバイスで実行すればiOSデバイスにデプロイされます。
今回もiOS18用にコンパイル・実行したところ、TodoTasksは問題無く動作しました。しかしWebArchivesで問題が発生しました。😵

WebArchivesに付いて

WebArchivesは、大きく3つのコードからできています。

  1. Firebaseに保存してあるPDFの一覧を表示し、選択したPDFの表示
  2. 指定されたURLのWebページを表示し、PDFKitを使いPDFファイルを作成しFirebaseに保存する
  3. SafariのアクションメニューにWebArchivesを追加し、メニューをクリックすると現在表示しているWebページのURLを渡し、WebArchivesアプリを起動し2.の機能を呼出すSafari App Extension

地道にデバッグすると、3.のSafari App ExtensionからWebArchivesアプリを起動する部分で失敗していました。

修正

EXSinkLoadOperator loadItemForTypeIdentifier:completionHandler:expectedValueClass:options:] nil ...というエラーが発生しています。ネットを検索するとApple Developer ForumsStack 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をアップデートするのは勉強になりますね。😅

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ