20210726のSwiftに関する記事は6件です。

Buttonを押すとSegmentが減る(UISegmentedControl)

今回の内容 Buttonを押すとSegmentが増える(UISegmentedControl)の続きになります。 機能説明 Buttonを押すと、UISegmentedControlのSegmentが減る。 コードと簡単解説 Segmentを削除するときは、右側から削除していきたいので現在のsegmentCountとsegmentContentsArrayの最大値が同じなら、.removeSegment(at: segmentCount, animated: true) で一番右のSegmentを削除します。 .segmentContentsArray.remove(at: segmentCount)の後にsegmentCount -= 1をすることで、segmentContentsArrayの最大値とsegmentCountを同じにしておかないと、次にSegmentを増やす時に見た目がおかしくなります。 Segmentを削除するときも、増やす時と同様にframeのwidthを引いています。 ViewController @IBAction func minusSegment(_ sender: Any) { if segmentControl.segmentContentsArray[segmentCount] == String(segmentCount){ segmentControl.uiSegmentControl.removeSegment(at: segmentCount, animated: true) segmentControl.segmentContentsArray.remove(at: segmentCount) segmentCount -= 1 segmentControl.uiSegmentControl.frame = CGRect(x: segmentControl.uiSegmentControl.bounds.minX, y: segmentControl.uiSegmentControl.bounds.minY + 100, width: segmentControl.uiSegmentControl.frame.width - 35, height: segmentControl.uiSegmentControl.frame.size.height) } } 終わり 昨日のUISegmentedControlを増やすだけのアプリに減らす機能を作りました。 ご指摘、ご質問などありましたら、コメントまでお願い致します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[SwiftUI]iOS14から用意されたMapを使って動的なMapを開発してみる

投稿の経緯 SwiftUIでMapを表示するにはこちらにもあるようにUIViewRepresentableを使ってカスタムViewを作成しなければなりませんでした。 しかし、iOS14からMapというSwiftUI用のViewが標準で搭載され、簡易的に実装することができるようになったので、個人開発中のアプリで使ってみたので、投稿します。 環境 Swift version 5.4.2 Xcode version 12.5.1 MapKitをインポート SwiftUIファイルを作成し(今回はMapView)、MapKitをインポートします。 import MapKit 必要な変数の準備 今回MapKitを扱うにあたって使用するイニシャライザはCLLocationCoordinate2DとMKCoordinateSpanです。なおこちら2点を扱うにあたってMKCoordinateRegionという状態変数が必要です。 各イニシャライザを要約して説明すると... CLLocationCoordinate2D 表示領域の中心位置を緯度経度で決める MKCoordinateSpan 緯度・経度それぞれに対する表示領域をそれぞれの縮尺(単位は度)で指定します。1度で約111kmあるそうなので比較的小さい値を指定することが多いと思います。例えば、100m を縮尺で表すと 0.0009 くらいになります。 また、引数として緯度経度を保存するDouble型の変数を用意します。 緯度 latitude 経度 longitude ?以下ここまでのコードです? import SwiftUI import MapKit struct MapView: View { @State private var region = MKCoordinateRegion() // 座標領域 var coordinate: CLLocationCoordinate2D? // 表示領域の中心位置 var latitude: Double // 緯度 var longitude: Double // 経度 /////以下省略///// } Mapを使ってマップを表示 続いてMapを使ってViewに反映させるので、まずはコードを見ていただきます。 Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $userTrackingMode, annotationItems: [ PinItem(coordinate: .init(latitude: latitude, longitude: longitude)) ], annotationContent: { item in MapMarker(coordinate: item.coordinate) }) 少し複雑に見えますが、難しくはないのでひとつひとつ解説します。 coordinateRegion MKCoordinateRegionの状態変数をバインディング指定します。(@State or @Published) interactionModes ユーザー操作の許可を以下の3つから設定します。 .pan スワイプ(ドラッグ)による操作を許可。 .zoom ダブルタッチ or ピンチ操作による拡大・縮小の操作を許可。 .all .pan と .zoom の両方を許可。 showsUserLocation trueでユーザーの現在位置を表示。 userTrackingMode マップがユーザーの現在位置を追跡させるどうかを状態変数で管理します。 .follow ユーザーを追跡します。 .none ユーザーの追跡を停止します。 以下のように状態変数を準備して、指定してあげれば良いです。 @State private var userTrackingMode: MapUserTrackingMode = .none annotationItems マップ上で表示するピンの位置を緯度経度で指定します。ピンはIdentifiableに準拠した構造体で定義し、CLLocationCoordinate2D型の緯度経度を管理させます。 struct PinItem: Identifiable { let id = UUID() let coordinate: CLLocationCoordinate2D } annotationContent annotationItemsのピンのデザインを決めます。MapMarker、MapPinの二択です。 ?以下ここまでのコードです? import SwiftUI import MapKit struct PinItem: Identifiable { let id = UUID() let coordinate: CLLocationCoordinate2D } struct MapView: View { @State private var region = MKCoordinateRegion() // 座標領域 @State private var userTrackingMode: MapUserTrackingMode = .none var coordinate: CLLocationCoordinate2D? // 表示領域の中心位置 var latitude: Double // 緯度 var longitude: Double // 経度 var body: some View { Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $userTrackingMode, annotationItems: [ PinItem(coordinate: .init(latitude: latitude, longitude: longitude)) ], annotationContent: { item in MapMarker(coordinate: item.coordinate) }) } } 引数で取得した緯度経度を使って表示領域の中心位置と縮尺を決める private func setRegion(coordinate: CLLocationCoordinate2D) { region = MKCoordinateRegion(center: coordinate, span: MKCoordinateSpan(latitudeDelta: 0.0009, longitudeDelta: 0.0009) ) } プライベートな関数を用意して引数にCLLocationCoordinate2Dを用意し、受け取った値は、状態変数regionのcenterに指定。(ここで表示領域の中心位置が決まる) 次に、spanにMKCoordinateSpanを指定し縮尺度合いを決定する。 そして、先ほど記述したMapに.onAppearを指定し、MapViewが呼ばれたら、まずsetRegionが呼ばれるように調整する。 ※.onAppearはswiftでいうviewDidAppearのような扱いです。 ?以下ここまでのコードです? import SwiftUI import MapKit struct PinItem: Identifiable { let id = UUID() let coordinate: CLLocationCoordinate2D } struct MapView: View { @State private var region = MKCoordinateRegion() // 座標領域 @State private var userTrackingMode: MapUserTrackingMode = .none var coordinate: CLLocationCoordinate2D? // 表示領域の中心位置 var latitude: Double // 緯度 var longitude: Double // 経度 var body: some View { Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $userTrackingMode, annotationItems: [ PinItem(coordinate: .init(latitude: latitude, longitude: longitude)) ], annotationContent: { item in MapMarker(coordinate: item.coordinate) }) .onAppear { setRegion(coordinate: CLLocationCoordinate2D(latitude: latitude, longitude: longitude)) } } // 引数で取得した緯度経度を使って動的に表示領域の中心位置と、縮尺を決める private func setRegion(coordinate: CLLocationCoordinate2D) { region = MKCoordinateRegion(center: coordinate, span: MKCoordinateSpan(latitudeDelta: 0.0009, longitudeDelta: 0.0009) ) } } 別ファイルからMapViewを呼び出す MapView(latitude: <Double>, longitude: <Double>) となるので、latitudeに緯度を、longitudeに経度を指定してあげれば、MapViewの.onAppearが呼ばれ、setRegionの中で表示領域の中心位置と、縮尺が決定され、Viewが更新されます。 お知らせ 現在、iOS開発案件を業務委託で募集中です(副業)。TwitterDMでご依頼をお待ちしています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] 構造体とクラスについて理解する

概要 今回は、Swiftの構造体・クラスについて復習したので、アウトプットしていきます。 構造体とは 構造体は値型の一種であり、ストアドプロパティの組み合わせによって1つの値を表すことができます。 簡単に言えば、継承不可のクラスのようなものであり、色々な種類の互いに関連するデータをまとめたものです。 また、以下のような特徴を持っています。 ・辞書型とは違い、同じ構造のオブジェクトを複数生成可能 ・クラスと違い参照不可 ・クラスは参照型に対し、構造体は値型 ・イニシャライザも関数も使用可能 定義方法 では早速、構造体の定義方法を見ていきましょう。 定義方法は以下のようにします。 Swift struct 構造体名 { 構造体の定義名 } 定義方法だけを見ても分からないと思いますので、サンプルコードをいくつか見ていきましょう! サンプルコード 次の例では、Personの情報を構造体で定義し、myPersonでインスタンス化をして、Personの情報を取り出しています。 Swift struct Person { var Name: String var height: Int var age: Int } // 構造体内のプロパティに名前、身長、年齢を定義し、インスタンス化している var myPerson = Person(Name: "山田", height: 180 , age: 27) // 上記のように値を定義すると、以下のような形で値を取り出せます print("私の名前は\(myPerson.Name)です。 身長は\(myPerson.height)㎝で年齢は\(myPerson.age)です。") // -実行結果- // 私の名前は山田です。 身長は180㎝で年齢は27です。 次は、メソッドを使用していきましょう。 次の例では、構造体の中にメソッド(fullName)を定義しています。 Swift struct Person { var firstName: String var lastName: String func fullName() -> String { return firstName + lastName } } // 構造体内のプロパティに名前を定義します var myPerson = Person(firstName: "山田", lastName: "太郎") // 上記のように値を定義すると、以下のような形で値を取り出せます print("氏名:\(myPerson.fullName())") // -実行結果- // 氏名:山田太郎 次は、init(イニシャライザ)を使用していきましょう。 イニシャライザは型のインスタンスを初期化します。全てのプロパティはインスタンス化の完了までに値が代入されていなければならないため、プロパティ定義時に初期化する必要があります。 次の例では、SimplePlus型に定義したinitを用いて、SimplePlus型のインスタンスを初期化しています。 Swift struct SimplePlus { var plus: Int // 引数(構造体を使うときに指定された値をもらって、計算する) init(num1: Int, num2: Int) { // 計算する plus = num1 + num2 } } // 以下のような形で値を取り出せます var answer1 = SimplePlus(num1: 3, num2: 2) print(answer1.plus) // 実行結果 // 5 補足として、引数の型が違ったら複数書けます。 Swift struct SimplePlus { var plus: Int init(num1: Int, num2: Int) { plus = num1 + num2 } // 引数の型が違ったら複数書ける init(num1: String, num2: String) { plus = Int(num1)! + Int(num2)! } } // 以下のような形で値を取り出せます var answer1 = SimplePlus(num1: 3, num2: 2) print(answer1.plus) var answer2 = SimplePlus(num1: "3", num2: "4") print(answer2.plus) // 実行結果 // 5 // 7 さらに、補足としてメソッド内のストアドプロパティの変更には、mutatingキーワードを用いることで変更できます。 Swift struct Person { var height: Int var weight: Int init(height: Int, weight: Int) { self.height = height self.weight = weight } mutating func someMethod() { height = 180 weight = 60 } } var myPerson = Person(height: 150, weight: 60) myPerson.someMethod() myPerson.height // 180 myPerson.weight // 60 構造体のメソッド内のストアドプロパティの変更はコンパイラによってチェックされ、ストアドプロパティの変更を含んでいるのにもかかわらず、mutatingキーワードがついていない場合はエラーとなります。 Swift struct Person { var height: Int var weight: Int init(height: Int, weight: Int) { self.height = height self.weight = weight } // mutatingキーワードが付いていない場合はコンパイルエラーとなる func someMethod() { height = 180 weight = 60 } } 構造体とクラスの違い クラスとは? クラスは構造体と同様の構造を持つ型です。構造体との大きな違いは2つあり、一つは参照型であること、もう一つは継承が可能であることです。 それでは、定義方法を見ていきましょう。 クラスの定義は以下の通りです。 Swift class クラス名 { クラスの定義 } 先程と同様に、定義方法だけを見ても分からないと思いますので、早速サンプルコードをいくつか見ていきましょう! サンプルコード 次の例では、HamburgerクラスにメソッドsayNakamiを定義しています。 Swift class Hamburger { var nakami = "パティ" func sayNakami () { print("中身は"+nakami+"です") } } var hamburger = Hamburger() hamburger.sayNakami() // 実行結果 // 中身はパティです ここまでは、構造体とあまり違いがないように見えますね。 では、次は継承の例をみていきましょう! 次の例では、HamburgerクラスがJunkFoodクラスを継承しています。 HamburgerクラスはJunkFoodクラスを継承しているので、JunkFoodクラスで定義されているnakami、junkFoodプロパティやdetailJunkFood()メソッドが自動的に利用可能になります。 Swift class JunkFood { let nakami: String var junkFood: String { return "ハンバーガー" } init(nakami: String) { self.nakami = nakami } func detailJunkFood() { print("junkFood: \(junkFood)") print("nakami: \(nakami)") } } class Hamburger: JunkFood { let count: Int init(nakami: String, count: Int) { self.count = count super.init(nakami: nakami) } } let hamburger = Hamburger(nakami: "チーズ", count: 2) print("\(hamburger.nakami)","\(hamburger.count)枚") hamburger.detailJunkFood() // 実行結果 // チーズ 2枚 // junkFood: ハンバーガー // nakami: チーズ 構造体とクラスの違いを深堀する 最後に、構造体とクラスを定義して、どのような挙動を示すか見ていきましょう! 次の例では、構造体は、colorStruct2.colorを変更しても、colorStruct1は変更しない。クラスの場合は、colorClass2.colorを変更したら、colorClass1も変更されるという挙動を示しています。 Swift // 構造体(値型) struct ColorStruct { var color:String } // クラス(参照型) class ColorClass { var color:String init(color:String) { self.color = color } } var colorStruct1 = ColorStruct(color: "青") var colorStruct2 = colorStruct1 colorStruct2.color = "黒" print("colorStruct1:\(colorStruct1.color)、colorStruct2:\(colorStruct2.color)") // 出力結果 // colorStruct1:青、colorStruct2:黒 var colorClass1 = ColorClass(color: "青") var colorClass2 = colorClass1 colorClass2.color = "黒" print("colorClass1:\(colorClass1.color)、colorClass2:\(colorClass2.color)") // 出力結果 // colorClass1:黒、colorClass2:黒 まとめ ここまで、長々と構造体とクラスについて述べてきましたが、結局、どういう時にどっちを使えばいいか迷ってしまいますよね。 実は、その問題はApple公式の見解が発表されています。詳しく知りたい方は、是非ご覧ください! https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes 結論を言うと。基本的にはAppleは構造体を推奨しています。 Appleの見解によると、以下の3つの場合の時はクラスを使用した方が良さそうですね。 ・Objective-CのAPIを使ったり、そのAPIを含む標準フレームワークのクラス(UIViewControllerなど)を継承したい場合 ・モデル化したデータをオブジェクト間で統一的に変更(参照渡しが適切)しやすくしたい場合 ・データが大きい場合 上記の内容を知っておくだけでも、構造体とクラスの使い分けの判断が容易になりそうですね! -- 今回は、Swiftの構造体とクラスについて復習し、まとめてみました。 構造体とクラスはどっちを使えばいいんだろうと悩みがちなので、アウトプットすることによって頭の中を整理することができたので良かったです。 今後、コードを書くときは構造体が使えるところはないか常に意識していきたいですね。 参考文献 ・Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 ・Swiftにおける構造体(struct)vsクラス(class)問題に向き合ってみた ・Choosing Between Structures and Classes
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUI Tutorialsから学ぶSwiftUI〜その1〜

はじめに 本記事は、Apple公式のSwiftUI Tutorialsを参考として内容をまとめた記事です。 英語が苦手な方や、概要をさっと理解したい方を対象に本記事を執筆しております。 注釈コメントを入れたプロジェクトファイルはこちら(GitHub) Creating and Combining Views Creating and Combining Views 3つのコードエディタ SwiftUIで記述するプログラムは、「ソースエディタ」「キャンバス」「インスペクタ」の3箇所から編集することができる。 ソースエディタ(source editor) キャンバス(canvas) インスペクタ(inspector) インスペクタは、ソースエディタまたはキャンバスで、編集対象を⌘(command) + ⌥(control)を押したままクリックすると表示することができる。 ビュー(view) ビューは、アプリケーション画面を構成する「画面部品」を指し、Viewプロトコルに準拠する構造体の内部で記述する。 また、ビュー毎に用意された修飾子(modifier)を付与することで、自由自在に変更することができる。 ただし、ビューがもつbodyプロパティは一つのビューしか返せないため、複数ビューをもたせる場合は後述するスタックでグループ化する必要がある。 基本的なビューと、その修飾子を以下に表す。 ビュー(内容) 修飾子 内容 共通 foregroundColor 色 frame サイズ padding パディング Text(ラベル) font フォント lineLimit 表示行数 truncationMode 省略位置 fixedSize 水平・垂直方向のサイズ固定 Image(画像) resizable サイズの可変化 aspectRatio アスペクト比 renderingMode 色の可変化 clipShape フレームの図形 overlay 上乗せするビュー shadow 影の半径 Button(ボタン) border 枠線 Path(2次元図形) stroke 線色 fill 塗りつぶし色 Spacer(スペーサー) List(リスト) Section(セクション) NavigationView(ナビゲーションビュー) navigationBarTitle ナビゲーションバーのタイトル NavigationLink(遷移先) TabView(タブビュー) tabItem タブ ScrollView(スクロールビュー) TextField(テキストフィールド) textFieldStyle テキストフィールドのスタイル スタック(Stack) スタックは、複数のビューをx, y, z方向にグループ化する。 主なスタックは、以下の通り。 スタック 方向 内容 HStack x 全てのビューを水平方向にグループ化して表示 LazyHStack x 画面内のビューを水平方向にグループ化して表示 VStack y 全てのビューを垂直方向にグループ化して表示 LazyVStack y 画面内のビューを垂直方向にグループ化して表示 ZStack z 全てのビューを垂直方向にグループ化して表示 MapKit MapKitは、ビューにマップを埋め込むのに用いるフレームワークであり、マップを表すMapビューと、マップに関連する各構造体を提供する。 座標(coordinate) MapKitにおける座標は、座標を表すCLLocationCoordinate2Dと、縮尺を表すMKCoordinateSpanで構成されるMKCoordinateRegionで表現する。 値型データのバインド($) 値型データを代入する場合、通常は変数の値が代入されるが、参照する値型データに$を付与することで、変数として代入できる。 Building Lists and Navigation Building Lists and Navigation ビュー(view), モデル(model), リソース(resource) プロジェクトファイルの管理において、ファイルの用途・機能によって以下のように分類するのが望ましい。 分類 用途・機能 ビュー ビューを表すswiftファイル モデル ビューに反映するデータ構造を記述するswiftファイル リソース データを格納・記述するファイル(jsonやXMLなど) JSON解析 JSON解析は、以下の手順で行う。 Bundleからファイルパス(=URLオブジェクト)を取得 Data型オブジェクトにJSONデータを格納 JSON解析を行うJSONDecoderクラスを用いたデコード処理の実施 ここで、JSONデータの解析結果を格納する構造体は、 DecodableとEncodableの2つの構造体を包含するCodableに準拠させることで、 JSONデータのキーと同名である構造体のプロパティに値を格納することができる。 また、JSONデータを格納した構造体リストを走査する場合は、Hashable・Identifiableプロトコルに準拠させる。 定義 // 1. Bundleからファイルパスを取得 Bundle.main.url( forResource name: String?, withExtension ext: String? ) -> URL? // パラメータ // name: リソースファイル名 // ext: ファイル拡張子(通常はnil) // -> extをnilに設定した場合は、拡張子も含めてファイル名を記述 // 2. Data型オブジェクトの生成・格納 // -> 全項目イニシャライザ(memberwise initializer)によって、optionsパラメータの記述は不要 Data(contentsOf url: URL, options: Data.ReadingOptions = []) throws // パラメータ // url: ファイルパスを表すURLオブジェクト // 3. JSONDecoderを用いたデコード処理 JSONDecoder#decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable // パラメータ // type: 返却型 // -> 通常は"<返却型>.self"と記述し、<返却型>自身の型を指定 // data: JSONデータを格納するData型オブジェクト サンプルコード(定型文) JSON解析 // JSON解析処理 func load<T: Decodable>(_ filename: String) -> T { let data: Data // ファイルパスの取得 // -> プロジェクトファイルはBundleに格納される guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else { fatalError("Couldn't find \(filename) in main bundle.") } // JSONファイル -> Data型 への変換 do { data = try Data(contentsOf: file) } catch { fatalError("Couldn't load \(filename) from main bundle:\n\(error)") } // Data型 -> Codable型に準拠した構造体 への変換(JSON解析) // -> <返却型>リストの各プロパティに一対一で対応するJSONデータが代入 do { let decoder = JSONDecoder() return try decoder.decode(T.self, from: data) } catch { fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") } } 表示デバイスに応じた動的プレビュー 画面サイズの異なるデバイスに応じた動的プレビューを行う場合、ビューを描画するループ処理を行うForEach構造体と、 キーパス(key path)の基点となるインスタンス自身を表すアイデンティティキーパス(\.self)を用いて、デバイス毎にビューを生成する。 サンプルコード デバイスに応じた動的プレビュー struct LandmarkList: View { ... } struct LandmarkList_Previews: PreviewProvider { static var previews: some View { // デバイスに応じた動的プレビュー // -> Identifiableに準拠していないリストを走査する場合は、キーパス式で記述 // <= リスト・セット・辞書などのコレクションを走査する場合、各要素は"id"プロパティをもつ必要がある // Identifiableに準拠しない場合は、アイデンティティキーパス(\.self)を用いてidを指定 ForEach(["iPhone SE (2nd generation)", "iPhone XS Max"], id: \.self) { deviceName in LandmarkList() // プレビューするデバイスの指定 .previewDevice(PreviewDevice(rawValue: deviceName)) // プレビュー名の指定 .previewDisplayName(deviceName) } } } Handling User Input Handling User Input Combine Combineは、値の変更を監視するPublisher、値を更新するOperator、更新後の値を取得するSubscriberを提供する フレームワークであり、「値の変更」を非同期イベントとして監視し、値の変更に伴う更新処理を自動で行う。 値の変更を監視するデータには@Publishedプロパティラッパを付与し、 監視データを保持するクラスをfinalクラスとしてObservableObjectプロトコルに準拠させる。 プロパティラッパ(property wrapper) プロパティラッパを用いることで、値型データとして扱われる構造体(ビュー)の内部でプロパティの値にアクセスすることができる。 SwiftUIで用意されているプロパティラッパは、以下の通り。 プロパティラッパ データ型 読み書き 管理ビュー プロパティ 値型 読み込み - @State 値型 読み書き ビュー自身 @AppStorage 値型 読み書き アプリケーション自身 @SceneStorage 値型 読み書き シーン自身 @Binding 値型 読み書き 外部ビュー @StateObject 参照型 - ビュー自身 @ObservedObject 参照型 - 親ビュー @EnvironmentObject 参照型 - ビュー自身外部ビュー @Environment 値型・参照型(環境値) 読み込み ビュー自身
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[SwiftUI] Picker 内の ForEach で id: \.self した時のバグ

selection が index の値になってしまいます。 struct MyView: View { @State var selection = 10 let items = [10,20,30] var body: some View { VStack { Text("selection: \(selection)") Picker(selection: $selection, label: Text("")) { ForEach(0..<items.count, id: \.self) { index in Text("\(items[index])") .tag(items[index]) } } } } } 下の様に、IdentifiableなものをForEachすれば直りました。 struct MyView: View { @State var selection = 10 struct Item: Identifiable { var id = UUID() var num:Int init(_ num: Int) { self.num = num } } let items = [Item(10), Item(20), Item(30)] var body: some View { VStack { Text("selection: \(selection)") Picker(selection: $selection, label: Text("")) { ForEach(items) { item in Text("\(item.num)") .tag(item.num) } } } } } バージョン Swift 5.4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftによる依存関係逆転の原則

10章のメモ 依存関係逆転の原則はUseCaseOutputPortによって実現 Use Case が見ているのはあくまで「入出力のデータを受け取れるオブジェクト」であり、 Presenter の具体的なクラスではない // インターフェイスアダプター(Controller / Presenter) -> Use Case // Use Case(内) protocol UseCaseOutputPort: AnyObject { func useCaseDidUpdate(value: Int) } protocol UsecaseInputPort { func update(something: Int) } final class UseCase: UsecaseInputPort { private weak var output: UseCaseOutputPort? init(output: UseCaseOutputPort) { self.output = output } func update(something value: Int) { // (値を使ったアプリケーション固有の処理) // (ここでは Entity 層の処理・データも使える) // Output 経由で Presenter へ通知(逆方向) output?.useCaseDidUpdate(value: value) } } // インターフェイスアダプター(外) final class Presenter: UseCaseOutputPort { func useCaseDidUpdate(value: Int) { print("UI 更新(\(value))") } } final class Controller { private let useCaseInput: UsecaseInputPort init(input: UsecaseInputPort) { useCaseInput = input } func received(something value: Int) { // Input 経由でUse Case を呼び出し(順方向) useCaseInput.update(something: value) } } // 円の構築 let useCase: UseCase = .init(output: Presenter()) let controller: Controller = .init(input: useCase) // 処理開始 controller.received(something: 10)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む