🎼

完全 Objective−C のプロジェクトでウィジェット対応した

2020/12/30に公開

Swift を使ってないプロジェクトで iOS14 のウィジェットに対応したのですよ。ウィジェットはなんと SwiftUI じゃないと実装できない…つまり、Swift 使わないとダメという高難易度タスクなわけです。

ウィジェットそのものの開発方法については、たくさん記事があると思うんで割愛します。

■ ウィジェットから Objective-C のクラスを呼べるようにする

Objective-C と Swift が混在するプロジェクトだとすでに環境は整ってると思いますけど、今回は Swift を使ってない状態からスタートです。つまり、Swift 側から Objective-C のクラスを読み込めるようにしないといけません。

そのため、Bridging Header ファイルを作成します。

適当な場所に Header ファイルを作る

ウィジェット用のコードやらが置いてある場所にでも、New File... から Header ファイルを作成します。

中身は、ウィジェットから直接呼び出したいクラスのヘッダファイルの import 文を並べる感じ。

#import "AnniversaryContainer.h"

ウィジェットの Build 設定をする

本体ではなく、ウィジェット用のターゲット の Build Settings にある Objective-C Bridging Header に、さっき作ったヘッダーファイルを指定します。

もしかしたら、自動で設定されてるかもしれないけど…。

Target Membership に追加する

ウィジェット用のターゲットに、Objective-C のクラスファイルも追加しておかないと、ビルドできなかったり実行時にクラッシュします。Project から .m ファイルを選択して、ウィジェット用のターゲットにチェックを入れます。

こんなもんで、ウィジェット用の Swift コードから、Objective-C のクラスが実行できるようになりました。

■ 本体アプリから、ウィジェットを更新する

次にややこしいのが、本体のアプリ(純度100% Objective-C プロジェクト)からウィジェットの再読み込みを実行したい場合です。

Swift のコードであらわすと、このくらい簡単なやつ。本体アプリでデータの追加や編集をした場合なんかに呼び出すことが想定されています。

WidgetCenter.shared.reloadAllTimelines() 

これを素直に Objective-C のコードにしたらこんな感じでしょうか。

[[WidgetCenter sharedCenter] reloadAllTimelines];

しかし…これは動かないのです…。結構衝撃を受けた。ウィジェット関連のフレームワークである WidgetKit は、なんと Swift 環境からしか使えません。ショックだったよ…。

というわけで、やることは以下の通り

  1. reloadAllTimelines をコールする Swift クラスを本体アプリに作る
  2. 本体アプリで Swift のヘッダを読み込む
  3. 本体のアプリから上記 Swift クラスのメソッドを実行する

1. reloadAllTimelines をコールする Swift クラスを本体アプリに作る

適当に Helper.swift とかいう感じの名前をつけて、以下のようなクラスを作って、本体アプリのターゲットのほうへと追加しました。

import WidgetKit

@objcMembers final class WidgetKitHelper: NSObject {

   @objc class func reloadAllTimelines(){
      #if arch(arm64) || arch(i386) || arch(x86_64)
      if #available(iOS 14.0, *) {
         WidgetCenter.shared.reloadAllTimelines()
      }
      #endif
   }
}

はじめて Swift のファイルを追加すると、Bridging Header を作るかどうか聞かれるので、作っておきましょう。中身は空っぽですけど、問題ないです。

2. 本体アプリで Swift のヘッダを読み込む

Swift のクラスを実行できるようにします。 ターゲット名-Swift.h を import する っていう説明になるんですが…。でも、書いてある通りです。

自分のアプリの場合、ターゲットの名前が iAnniversary です。なので、Objective-C の実装ファイルのほうで以下のようにしています。

#import "iAnniversary-Swift.h"

3. 本体のアプリから上記 Swift クラスのメソッドを実行する

適当なところで、WidgetKitHelper のメソッド呼び出しをするだけです!やったね!

[WidgetKitHelper reloadAllTimelines];

■ まとめ

個人的なアプリのほうでは、アップデートもたいしてしないし Objective-C で困ってなかったので特に Swift からは遠い感じでしたけど、今後追加されるフレームワークは WidgetKit のように Objective-C から呼び出せないものもでてくるだろうなと考えると感慨深い…ではなく、ちゃんと移行も考えないといけないのかなという気持ちになりました。

Discussion