MENU

【 SwiftUI 】備忘録

目次

フレームワークとは?

 簡単にイメージすると import 〜 の “〜” 部分に入るものがFrameworkです。

import SwiftUI // ←ここです!

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Hello, world!")
        }
        .padding()
    }
}

 他にもFoundationやUIKitなどよく目にするのではないでしょうか。

つまり、SwiftUI, Foundation, UIKitなどはフレームワークと言うことになります。

“Charts”というフレームワークは、アプリ制作の際にグラフなどを安易に作り出してくれるものです。

import SwiftUI
import Charts

Appleのドキュメントを見てみましょう。リンク先の「テクノロジー」に記載されているのは、ウェブサービスやフレームワークなどですね。試しにどれかフレームワークを開いてみてください。

各項目には詳細な内容が記載されています。前述したChartsなら、グラフなどが簡単に作れる機能が搭載されていると書いてあるはずです。

各フレームワークには、それぞれのテーマに沿った機能が搭載されていて、私たちはそのテーマ機能が必要なときにインポートして使用します。つまり、自分が作成しているアプリケーションに必要な機能(Framework)をその都度、搭載(import)していくことになります。

まとめ

・フレームワークとは、開発に必要なテーマに沿った機能が搭載されたもの。
・必要なフレームワークを搭載することで、開発効率を向上させることができる。

参考

Apple Inc.(2023).「technologies」.Apple Developer Documentation.https://developer.apple.com/documentation/technologies , (参照 2023-08-13)

delegateを利用してUIKitの機能をSwiftUIで利用する

整理しておきたいこと

  • 2023年、XcodeでインターフェースをStoryboardにしてプロジェクトを作成すると、AppDelegate.swiftファイルがデフォルトで作成されます。しかし、インターフェースをSwiftUIにしてプロジェクトを作成する際には、このファイルは作成されません。
  • インターフェースの違いで表示の有無があるAppDelegate.swiftとは一体何なのか?
  • delegateで何ができるのかがわからない。
  • delegateを使用するということは、SwiftUIでUIKitの機能を利用することに等しいのか?

このテーマを取り上げたきっかけ

SwiftUIで作成したアプリケーションにGoogle AdMob広告を実装する際、delegateに関する知識が必要になりましたが、その時はあまり理解ができていませんでした。今後、公開したアプリに他の機能も実装する必要が出てきたため、delegateについてより深く理解する必要性を感じた次第です。

UIKitとSwiftUIを比較

UIKitとは

OS、iPadOS、tvOSアプリのユーザーインターフェース(UI)を構築・管理するためのフレームワークです。
・Xcodeでプロジェクトを作成する際、インターフェースには主にStoryboardを使用します
・開発言語はSwiftです。
SwiftUIが登場する以前から使われており、機能が非常に充実しています
・プロジェクト作成時にAppDelegate.swiftファイルがデフォルトで含まれます
クラスベースでUIを定義します

その他は下記を参照

参考:公式ドキュメンテーション / UIKit

SwiftUIとは

AppleのすべてのOS(iOS、iPadOS、macOS、watchOS、tvOS)に対応した、宣言的にアプリのUIと動作を記述するためのフレームワークです。
・Xcodeでプロジェクトを作成する際、インターフェースはSwiftUIを使用します
・開発言語はSwiftです。
比較的新しく登場したフレームワークで、UIKitに比べるとまだ使用できる機能が限定的です
・プロジェクト作成時にAppDelegate.swiftファイルは含まれません
構造体ベースでUIを定義します

その他は下記参照

参考:公式ドキュメンテーション / SwiftUI

2つの違いとは

インターフェースの違い:XcodeでのUI構築方法や操作感が異なります。これは、たとえるなら表計算ソフトとしてMacのNumbersを使うか、WindowsのExcelを使うか、といった違いに似ています。


大元の定義の違い:根本的なアーキテクチャが異なるため、SwiftUIではAppDelegate.swiftファイルがデフォルトで存在しません。(なぜ存在しないのかは今後の課題として深掘りします。)

ライフサイクルの違い:アプリのライフサイクル管理の仕組みにも違いがあります。(これも今後の課題として詳しく調べていきます。)

2つのフレームワークを比較してわかったdelegateの詳細と正体

UIKitでは頻繁に利用される概念です。

delegateデザインパターンの一種です。

・Google AdMobのように、外部のフレームワーク(例えばGoogleのSDKなど)を実装する際に、delegateの使用が必須となるケースがあります。

delegateの学習教材や例題は、UIKitおよびStoryboardを用いたものが多く見られます。このため、SwiftUIのみを学習してきた者にとっては、UIKitやStoryboardの例題だけでは理解が難しいと感じることがあります。

「delegate(デリゲート)とは、あるクラスで行いたい処理の一部を他のクラスに任せたり、任せた処理を指定したクラスに通知する仕組みです。」

藤 治仁・小林 加奈子・小林 由憲. SwiftUI 対応 たった2日でマスターできるiPhone アプリ開発集中講座 Xcode 13/iOS 15/Swift 5.5対応. ソシム株式会社、2021、p320.

SwiftUIを中心に学習している方へ

delegateって、結局何なの?

delegateは、簡単に言うと**「代理人」**のことです。

アプリの中で、ある役割を持つAさんがいます。このAさんは、普段の仕事は自分でこなしますが、特定の難しい仕事や、他の場所で起こった出来事に対する対応は、自分ではやらずに信頼できるBさんに**「代理」**としてお願いする、という仕組みなんです。

この「Bさん」にあたるのがdelegateです。


SwiftUI学習者がつまずきやすいポイント

1. UIKitとの関係

SwiftUIを学習していると、あまりdelegateという言葉に出会わないかもしれません。なぜなら、delegateUIKit(SwiftUIよりも前からある、iOSアプリ開発の基本的なフレームワーク)で非常によく使われるデザインパターンだからです。

まるで、ずっとMacを使っていた人が、突然Windowsの操作方法を覚えるような感覚に似ています。基本的な考え方は同じでも、やり方やルールが少し違うので、最初は戸惑うかもしれませんね。

2. コードの見た目

UIKitでのdelegateの実装は、クラスや特定の「プロトコル」と呼ばれるルールを使って書かれます。SwiftUIのコードは構造体が中心なので、見慣れない書き方だと感じるかもしれません。

3. 具体的な例が少ない

いざdelegateについて学ぼうとすると、インターネットや書籍にある例題の多くがUIKitやStoryboardを使ったものです。これは、delegateがUIKitの文脈で発展してきたためです。

SwiftUIを中心に学んできた人にとっては、これらの例題をそのままSwiftUIに当てはめて理解するのが難しい場合があります。


なぜdelegateが必要になるの?

SwiftUIでアプリを作っていても、Google AdMobのような外部のSDK(Software Development Kit)や、既存のUIKitの機能を組み合わせたい場面が出てくることがあります。そういった場合、多くは「特定のイベントが起きたら教えてね」というやり取りのためにdelegateを使う必要が出てくるんです。


delegate理解のためのイメージ・例題


プロトコル:「契約書」
これは「Bigカンパニー」と「下請け会社」の間で交わされる**「契約書」や「業務委託の条件」**に当たります。
「ピザの材料はこうする」「カレーの製造工程はこうする」といった具体的な指示や、下請け会社がBigカンパニーに報告すべき内容(例:「製造が完了しました」「問題が発生しました」)が明記されています。


任せるクラス:「Bigカンパニー」
大元の企業であり、製品の企画や販売といった主要業務を行います。
しかし、新製品(ピザとカレー)の**「仕入れ」や「製造」といった具体的な作業は、自社では行わず、信頼できる下請け会社に「任せる」**ことにします。
Bigカンパニーは、この「契約書(プロトコル)」に従って業務を遂行できる下請け会社を探し、その業務を委託します。


任されるクラス:「下請け会社」
Bigカンパニーから仕事を引き受ける**「代理人」**です。
「契約書(プロトコル)」の内容に従って、ピザやカレーの「仕入れ」と「製造」を実際に行います
作業の進捗や結果、あるいは何か問題が発生した場合には、「契約書」に定められた方法でBigカンパニーに**報告(コールバック)**します。


シチュエーション
Bigカンパニーは食品関係の大企業で、この度、ピザとカレーの新製品を発売することにしました。今回の商品はすべて、下請け会社に仕入れと製造を任せ、Bigカンパニー自身のブランドで売り出す計画です。
Bigカンパニーは、まず**「新製品製造に関する業務委託契約書(プロトコル)」**を作成します。ここには、材料の品質基準、製造工程、進捗報告の方法などが細かく記載されます。
次に、Bigカンパニーは、この契約書の内容を遂行できる下請け会社(任されるクラス)を見つけ、その会社に契約を**「委任(delegateを設定)」**します。
下請け会社は、契約書に従って製造を進め、完了時や問題発生時にはBigカンパニーに決められた方法で報告します。Bigカンパニーは、その報告を受けて次のアクション(例:販売開始)に移ることができます。

Bigカンパニーは約束事を下請け会社とシェアする必要がある。

protocol Yakusokugoto {
    func shiire() //仕入れ
    
    func seizou() //製造
}


Bigカンパニー

class BigCompany {
    var delegate: Yakusokugoto? = nil
    func kekka() {
        if let dg = self.delegate {
            
            print("商品が開発されました。仕入れたものと製造されたものを教えてください")
            dg.shiire()
            dg.seizou()
        } else {
            print("商品は開発されていません")
        }
    }
}

下請け会社

class shitaukeCompanyAAA: Yakusokugoto {
    func shiire() {
        print("トマトとチーズを仕入れる")
    }
    
    func seizou() {
        print("トマトたっぷりピザを開発")
    }
}

class shitaukeCompanyBBB: Yakusokugoto {
    func shiire() {
        print("スパイスを仕入れる")
    }
    
    func seizou() {
        print("香り引き立つカレーを開発")
    }   
}

説明会(実行)

let product = BigCompany()

product.delegate = shitaukeCompanyAAA()
product.kekka()

product.delegate = shitaukeCompanyBBB()
product.kekka()

/*
商品が開発されました。仕入れたものと製造されたものを教えてください
トマトとチーズを仕入れる
トマトたっぷりピザを開発
商品が開発されました。仕入れたものと製造されたものを教えてください
スパイスを仕入れる
香り引き立つカレーを開発
*/

Swift の protocol は func 〜( ) 以下の{ } には 機能を実装しない。

その実装しない機能の内容は下請け会社へ丸投げする。

つまり、下請け会社を変えることでいくつもの異なる実装を簡単に実現することができる。

「デリゲートは、そのオブジェクトがプログラム内のイベントに遭遇したときに、別のオブジェクトに代わって、またはそれと連携して行動するオブジェクトです。」

Apple Inc. (2012). 「Objective-Cプログラミングの概念 デリデートとデータソース」. ドキュメンテーションアーカイブ.


https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html , (参照2023-08-15)

参考

Apple Inc.(2023).「UIApplication」.Apple Developer

Documentation.https://developer.apple.com/documentation/uikit/uiapplication , (参照 2023-08-13)

各KitをSwiftUIで利用するためには

各KitSwiftUI備考
MapKitMapUIViewRepresentable プロトコルでラップして利用
UIImagePickerController任意の構造体UIViewControllerRepresentable プロトコルでラップして利用

参考

Apple Inc(2023). Apple Developer.

Documentation.https://developer.apple.com/documentation/technologies (参照 2023-08-17)

iPhoneのカメラ機能を例題にSwiftUIでUIKitの機能を利用する

全体のイメージ

内容・詳細

構造体

SwiftUIでUIKitの機能を利用する「橋渡し」

SwiftUIでUIKitの機能(例えばカメラ機能)を使いたい時、構造体がその「橋渡し」の役割を担います。この構造体は、**UIKitのビューコントローラー(UIKItの画面を管理するもの)をSwiftUIのビューとして表示するための「大枠」**になります。

この構造体が特定のプロトコルに準拠し、適切な設定を行うことで、内部的にdelegateパターンを利用してUIKitの機能が使えるようになります。

具体的な仕組み

  • 構造体がUIViewControllerRepresentableプロトコルに準拠することで、UIKitのUIImagePickerControllerクラス(カメラや写真ライブラリの機能を提供するもの)をSwiftUIの画面に組み込むことができるようになります。
  • このUIViewControllerRepresentableプロトコルが、SwiftUIとUIKitの間で情報をやり取りする際の**「契約」**のような役割を果たします。
  • そして、その契約の一部として、UIKit側のイベント(例: 写真が選択された、キャンセルされたなど)をSwiftUI側に伝えるために、delegateパターンが内部的に利用されることになります。

ポイントの補足

  • 「構造体が呼ばれることでdelegateパターンの処理があり」という表現は少し曖昧かもしれませんね。正確には、UIViewControllerRepresentableを実装した構造体がUIKitのビューコントローラーを「表現」し、そのUIKitのビューコントローラーが通常持つdelegateプロパティなどを通じて、特定のイベントをSwiftUI側で受け取る仕組み、といった形になります。
  • UIViewControllerRepresentableは、UIKitのビューコントローラーをSwiftUIでラップするためのプロトコルであり、このプロトコルを実装することで、SwiftUIの世界からUIKitの機能にアクセスできるようになります。

このように考えると、SwiftUIとUIKitの間での連携や、delegateが果たす役割がより明確になったのではないでしょうか。

プロトコル

これが条件・約束事です。

詳しい内容は下記のようになっています。

@MainActor public protocol UIImagePickerControllerDelegate : NSObjectProtocol {

    
    @available(iOS 2.0, *)
    optional func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])

    @available(iOS 2.0, *)
    optional func imagePickerControllerDidCancel(_ picker: UIImagePickerController)
}

Bigカンパニー

 //UIImagePickerControllerクラスのインスタンスを生成
        let myImagePickerController = UIImagePickerController()
BigカンパニーはUIImagePickerControllerクラスとなります。

Bigカンパニーの下請け

class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate
クラス CoordinatorがBigカンパニーの下請けとなります

まとめ

Bigカンパニーは

@MainActor open class UIImagePickerController : UINavigationController, NSCoding {
...
    
    weak open var delegate: (UIImagePickerControllerDelegate & UINavigationControllerDelegate)?

    ...
}

上記のコードでわかるようにUIImagePickerControllerDelegateプロトコルが内容としてあります。


下請けさんは

CoordinatorクラスとしてUIImagePickerControllerDelegateプロトコルに準拠して、

func imagePickerController と func imagePickerControllerDidCancel 処理の内容を記載している状態です。


プロトコルは

上記で登場しているように UIImagePickerControllerDelegate です。


あとは実装ですが

//delegate設定
        myImagePickerController.delegate = context.coordinator

最上図の実装と記載されている内容です。

参考

藤 治仁・小林 加奈子・小林 由憲. SwiftUI 対応 たった2日でマスターできるiPhone アプリ開発集中講座 Xcode 13/iOS 15/Swift 5.5対応. ソシム株式会社、2021、p304-p335.

イメージでとらえる

UIKitのカメラ機能をSwiftUIで利用可能にする

GoogleMobileAdsをSwiftUIで利用可能にする

比較

いかがでしょうか。登場する役者は同じではないでしょか?

補足ですが、一つ目のBigカンパニーはUIImagePickerControllerクラスになります。
どのに定義されているかですが、UIKitの機能として定義されています。Xcodeで選択して、コマンドキーで”Jump to Definition”してみてください。そこへとぶことができます。
プロトコルも同様です。

実装

それでは、実際に実装していきます。

下記に参考と照らし合わせながら、進めていきます。

Step 1 ファイル作成

ファイルの作成。任意の名前.swiftで ファイルを作成します。
(”コマンド + N” で ”Swift File” を作成できます。)

Step 2 広告の幅を決める。

作成したファイルに下記のコードを入力します。

作成したファイルに下記のコードを入力します。

// Delegate methods for receiving width update messages.

protocol BannerViewControllerWidthDelegate: AnyObject {
  func bannerViewController(_ bannerViewController: BannerViewController, didUpdate width: CGFloat)
}

続いて、下記の追加してください。プロトコルとは分けてください。

class BannerViewController: UIViewController {
  weak var delegate: BannerViewControllerWidthDelegate?

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    // Tell the delegate the initial ad width.
    delegate?.bannerViewController(
      self, didUpdate: view.frame.inset(by: view.safeAreaInsets).size.width)
  }

  override func viewWillTransition(
    to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator
  ) {
    coordinator.animate { _ in
      // do nothing
    } completion: { _ in
      // Notify the delegate of ad width changes.
      self.delegate?.bannerViewController(
        self, didUpdate: self.view.frame.inset(by: self.view.safeAreaInsets).size.width)
    }
  }
}

以上でプロトコルとBigカンパニーの定義が終わりました。

Step 3 構造体の作成

必要なコードを追加します。同じファイル内ですが、プロトコルとBigカンパニーとは分けて作成してください(上記のイメージのように)

struct BannerView: UIViewControllerRepresentable {
    @State private var viewWidth: CGFloat = .zero
    private let bannerView = GADBannerView()
//    private let adUnitID = "実際のナンバーを記載してください"
//下記はテスト
    //バナー広告
//    private let adUnitID = "ca-app-pub-3940256099942544/2934735716"
    //アダプティブバナー広告
    private let adUnitID = "ca-app-pub-3940256099942544/2435281174"
    
    
    func makeUIViewController(context: Context) -> some UIViewController {
        let bannerViewController = BannerViewController()
        bannerView.adUnitID = adUnitID
        bannerView.rootViewController = bannerViewController
        bannerView.delegate = context.coordinator
        bannerViewController.view.addSubview(bannerView)
        // Tell the bannerViewController to update our Coordinator when the ad
//        2023/-7-12
        NSLayoutConstraint.activate([
            bannerView.topAnchor.constraint(
            equalTo: bannerViewController.view.safeAreaLayoutGuide.topAnchor),
          bannerView.centerXAnchor.constraint(equalTo: bannerViewController.view.centerXAnchor),
        ])
        // width changes.
        bannerViewController.delegate = context.coordinator
        
        return bannerViewController
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        guard viewWidth != .zero else { return }
        
        // Request a banner ad with the updated viewWidth.
        bannerView.adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(viewWidth)
        bannerView.load(GADRequest())
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
      }
    
    internal class Coordinator: NSObject, BannerViewControllerWidthDelegate, GADBannerViewDelegate {
        let parent: BannerView

        init(_ parent: BannerView) {
          self.parent = parent
        }

        // MARK: - BannerViewControllerWidthDelegate methods

        func bannerViewController(_ bannerViewController: BannerViewController, didUpdate width: CGFloat) {
          // Pass the viewWidth from Coordinator to BannerView.
          parent.viewWidth = width
        }
        
        // MARK: - GADBannerViewDelegate methods

            func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {
              print("\(#function) called")
            }

            func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: Error) {
              print("\(#function) called")
            }

            func bannerViewDidRecordImpression(_ bannerView: GADBannerView) {
              print("\(#function) called")
            }

            func bannerViewWillPresentScreen(_ bannerView: GADBannerView) {
              print("\(#function) called")
            }

            func bannerViewWillDismissScreen(_ bannerView: GADBannerView) {
              print("\(#function) called")
            }

            func bannerViewDidDismissScreen(_ bannerView: GADBannerView) {
              print("\(#function) called")
            }
      }
    
}

あとは、これを呼び出すだけです。

Step 4 呼び出し

下記のように、表示した場所でBannerView()を生成してください。

struct ContentView: View {

  var body: some View {
    BannerView()
  }
}

まとめ

その他の広告表示方法も下記の参考資料より確認できます。

まだまだ勉強中です。誤りがありましたらご連絡していただけたら幸いです。

参考

Google for Developers. “SwiftUI”. Google AdMob MobileAds SDK(iOS)https://developers.google.com/admob/ios/swiftui?hl=ja (参照 2023-08-17)

藤 治仁・小林 加奈子・小林 由憲. SwiftUI 対応 たった2日でマスターできるiPhone アプリ開発集中講座 Xcode 13/iOS 15/Swift 5.5対応. ソシム株式会社、2021、p304-p335.

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次