20211129のSwiftに関する記事は9件です。

【Swift】PDFViewで前後のページに移動する

SwiftのPDFKitで前後のページに移動する方法。 PDFKitの導入 設定画面のGeneralのFrameworks,Libraries, and Embedded ContentのプラスボタンからPDFKit.frameworkを追加。 コードで実装する際は以下の通り。 let pdfView = PDFView() pdfView.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height) pdfView.autoScales = true pdfView.displayMode = .singlePage pdfView.displayDirection = .horizontal pdfView.displaysPageBreaks = true pdfView.usePageViewController(true) 各プロパティの詳細は以下をご覧ください。 前後のページに移動 PDFViewで前後のページに移動する際は以下の処理。 //前のページに戻る pdfView.goToPreviousPage(self) //後のページに進む pdfView.goToNextPage(self) 参考 https://qiita.com/taketomato/items/8102a80cd4ce516523de https://dev.classmethod.jp/articles/ios-11-pdfkit/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIViewControllerの一部だけをSwiftUIで構築した話

前書き ACCESS Advent Calender 今年の1日目は@tonionagauzziです。 1月に娘が生まれて、コロナ禍での育児1年生でしたが、会社や周囲の手厚いサポートによって無事に過ごせました。この場を借りて、1年間お世話になった皆さんにお礼を申し上げます。 そしてようやく念願叶い、両家に顔見せに行った帰りの飛行機で、明後日投稿日じゃん!って気づいてスマホで記事書いてます。便利な時代になりましたね! やったこと UIKitで書かれた歴史の長いiOSアプリがあります。最近、その Deployment Target が13.0に上がったので、UIViewControllerの一部だけにSwiftUIを導入しました。 また、SwiftUIで、URLは青下線表示で押したらSafariが開くようにしました。 このような Key-Delimiter-Value 形式のViewです。 SwiftUIを導入した狙いは、 要素数(縦の行数)が不定なので、SwiftUIのForEachで手軽に可変にしたい UIKitと比べて20%以下のコード量で同じ内容を記述できるので、開発効率を上げたい 単純なViewでデザイン制約がないので、SwiftUI導入のきっかけ作りをしたい でした。 上に貼ったスクショはプレビューなので、実際は上下に従来のUIKitのパーツが並んでいる想定です。 説明 1. UIViewController上の一部だけをSwiftUIにする まず、URLは置いといて、UIViewControllerにSwiftUIを埋め込む部分のコードを載せます。 ポイントは3つです。 UIHostingControllerにSwiftUI Viewを紐付け、UIKitのViewControllerにaddChildする SwiftUI Viewを空のUIViewにaddSubViewする 2の親子が密接するようConstraintを設定する こう書くと面倒くさそうですがコード量は大したことないです。 Item.swift struct Item: Hashable { var name: String var value: String } ExtraView.siwft import SwiftUI let ITEM_LIST_FOR_PREVIEW = [ Item(name: "名前", value: "tonionagauzzi"), Item(name: "SNS", value: "https://twitter.com/tonionagauzzi"), Item(name: "Motto", value: "You can decide you're happy or not.") ] struct ExtraView: View { var itemList: [Item] = [] @ViewBuilder var body: some View { GeometryReader { geometry in VStack { ForEach(itemList, id: \.self) { item in HStack(alignment: .top) { Text(item.name) .frame(maxWidth: geometry.size.width * 0.12, alignment: .leading) Text(":") Text(item.value) .frame(maxWidth: .infinity, alignment: .leading) }.padding(.bottom, 1) } } .padding(.horizontal, 10) } } } struct ExtraView_Previews: PreviewProvider { static var previews: some View { ExtraView( itemList: ITEM_LIST_FOR_PREVIEW ) } } MyViewController.swift import SwiftUI class MyViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 省略 let extraViewController: UIHostingController<ExtraView> = UIHostingController( rootView: ExtraView( itemList: itemList ) ) addChild(extraViewController) // extraView は、あらかじめ Xib/Storyboard で空の UIView として Auto Layout 配置しておく extraView.addSubview(extraViewController.view) extraViewController.didMove(toParent: self) extraViewController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ extraViewController.view.widthAnchor.constraint( equalTo: extraView.widthAnchor, multiplier: 1 ), extraViewController.view.heightAnchor.constraint( equalTo: extraView.heightAnchor, multiplier: 1 ), extraViewController.view.centerXAnchor.constraint( equalTo: extraView.centerXAnchor ), extraViewController.view.centerYAnchor.constraint( equalTo: extraView.centerYAnchor ) ]) } ) didMoveは忘れがちですが、処理の終了を通知するもので、しないとviewWillAppearなどライフサイクルに関係するメソッドが呼ばれなくなる可能性があるので、忘れないようにしましょう。 2. URLはClickableにする そしてURL対応。リンクを検出したら青文字・下線付きにして押せるようにしたいので、ExtraViewを以下のように作り変えました。 ExtraView.swift struct ExtraView: View { var itemList: [Item] = [] @ViewBuilder var body: some View { GeometryReader { geometry in VStack { ForEach(itemList, id: \.self) { item in HStack(alignment: .top) { Text(item.name) .frame(maxWidth: geometry.size.width * 0.12, alignment: .leading) Text(":") if item.isUrl { Button(action: item.actionUrl) { Text(item.value) .underline() .foregroundColor(Color.blue) } .buttonStyle(PlainButtonStyle()) .frame(maxWidth: .infinity, alignment: .leading) } else { Text(item.value) .frame(maxWidth: .infinity, alignment: .leading) } }.padding(.bottom, 1) } } .padding(.horizontal, 10) } } } struct ExtraView_Previews: PreviewProvider { static var previews: some View { ExtraView( itemList: ITEM_LIST_FOR_PREVIEW ) } } extension Item { private var url: URL? { return URL(string: self.value) } fileprivate var isUrl: Bool { if let url = url { return UIApplication.shared.canOpenURL(url) } return false } fileprivate var actionUrl: () -> () { return { if let url = url { UIApplication.shared.open(url) } } } } 変えたのは、if item.isUrlで開けるURLかどうかを判定する部分と、必要なExtensionの追加です。 ちなみに.underline()は1つ目に書かないとValue of type 'some View' has no member 'underline'というエラーに嵌ってしまいます。 おわりに そろそろ東京の大地が見えてきたので、書き終わります。 このように、SwiftUIはView1つから簡単に置き換えることが可能なので、大規模プロジェクトでもできるところから導入しましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】個人的によく使うアプリ起動時の分岐処理

私がアプリ起動時の分岐処理によく使う方法です。 以下の3パターンに分けています。 初回起動時 更新時 通常起動 コード サンプルコードはSwiftUIですがUIKitでも問題ありません。 SwiftUIをimportすればAppStorageが使えますし、 AppStorageを使わずにUserDefaultsを直接扱っても良いです。 import SwiftUI enum LaunchStatus { case FirstLaunch case NewVersionLaunch case Launched } class LaunchUtil { static let launchedVersionKey = "launchedVersion" @AppStorage(launchedVersionKey) static var launchedVersion = "" static var launchStatus: LaunchStatus { get{ let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" let launchedVersion = self.launchedVersion self.launchedVersion = version if launchedVersion == "" { return .FirstLaunch } return version == launchedVersion ? .Launched : .NewVersionLaunch } } } struct ContentView: View { init(){ switch LaunchUtil.launchStatus { case .FirstLaunch : self.addSampleData() self.showTutorial() case .NewVersionLaunch : self.showUpdateText() default: break } } var body: some View { //略 } //略 } 解説 AppStorage(UserDefaults)に初期値を設定し、 起動時に現在のバージョンを取得します。 以下が現在のバージョンの取得です。アプリバージョンを取得できます。 Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" 2つの値を比較して以下の通りに条件分岐します。 初期値なら初回起動 現在の値と異なれば更新時 同じ値なら通常起動 サンプルコードでなんとなくわかると思いますが、 私は初回起動時にCoreDataへのサンプルデータの追加とチュートリアル表示、 更新時には更新内容の表示をよく行います。 ブログ記事の紹介 本記事は最低限の内容にしています。 コードと簡単な説明があれば十分で、余談や細かい説明はあるだけ邪魔という人も多いと思うので (私もよくあります) UserDefault版のコード AppStorageとUserDefaultの関係 もう少し具体的な実用例 などを見たい場合はブログ記事も見てみて下さい。 AdMobの初期化処理もこの辺で行うので一緒に紹介しています。 こちらはAppStorageについてです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xcode13から出るConditional downcast from 'String?' to 'String' does nothingについて

はじめに Xcode13 で以下のような不思議な警告が出るようになってたので共有しときます(原因わかる方いたらぜひ教えて下さい)。 Conditional downcast from 'String?' to 'String' does nothing 現象 やっていることは hoge?.first という String?? から String? にしているだけです。 let hoge: [String?]? = ["hoge"] let fuga = hoge?.first as? String print(fuga!) こちらのコードは Xcode12.5.1 では警告が出ないですが Xcode13.0 だと警告が出るようになっていました。 対応 とりあえず下記のように書くと警告は出ませんでした。 パターン1 let hoge: [String?]? = ["hoge"] let foo = hoge?.first let piyo = foo as? String print(piyo!) パターン2 let hoge: [String?]? = ["hoge"] let fuga = hoge?.first ?? nil print(fuga!) パターン3 let hoge: [String?]? = ["hoge"] let fuga = hoge?.first?.flatMap { $0 } print(fuga!) 不思議なのは警告がパターン1の foo as? String では出ないけど hoge?.first as? String で出ること(なんでだろう)。 おわりに けっきょく原因はわかりませんでしたがなんか不思議だなあということで記事にしてみました。 なんかわかる方いればぜひ教えて下さい Xcode のバージョンアップで今まで問題なかった書き方に警告が出るのはわりとよくある
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SwiftUI】GeometryReaderを使うと座標がずれる

現象 環境はXCode13.1。 座標や縦・横幅の取得に便利なGeometryReaderを使った時に、階層下のVStack内で生成したUI部品の座標がずれたので解決策をメモ。 HStackやZStackでは未検証。 以下のようなコードを書いていた。(サイズなどは適当) GeometryReader { geometry in VStack() { Button(action: {}){ Text("Button") .foregroundColor(Color.white) .padding(.all, 10.0) .background(Color.blue) } } } おや...? 明らかに中央から表示がずれている...? 原因 VStackに色をつけてみるとこんな感じになる。 見やすさのためにheightを指定してやるとこんな感じ。 なぜかWidthが画面幅より狭くなっている... 解決策 と、いうことでVStackを画面いっぱいまで広げてやればいいので、以下のようにコードを変更。 GeometryReader { geometry in VStack() { Button(action: {}){ Text("Button") .foregroundColor(Color.white) .padding(.all, 10.0) .background(Color.blue) } .frame(width: geometry.frame(in:.local).width, height: /*適当に*/) .background(Color.red) } } 無事解決した。 geometry自体の横幅は画面サイズいっぱいまで広がっているので、原因をなかなか特定できなかった。 これは仕様なのかバグなのか...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Couchbase Lite機能解説:添付ファイル

はじめに Couchbase Liteは、標準のJSONデータ型をサポートするだけでなく、画像、音声、ビデオ、PDFファイルなどを含むバイナリデータもサポートします。JSONドキュメントは、「添付ファイル」と呼ばれるバイナリデータに関連付けることができます。 添付ファイル操作API利用例 ここでは、バイナリデータの添付ファイルを作成、および取得する方法について説明します。 添付ファイル作成 開発者は、blobデータを作成するためにblob APIを使用する必要があります。ドキュメントは、複数の添付ファイルまたはBLOBに関連付けることができます。 以下は、このAPIの使用法を示すSwiftコードスニペットです。 let mutableDoc = MutableDocument.init(id: "user::priya") if let profileImage = UIImage(named: "profile.jpg"), let imageData = UIImageJPEGRepresentation(profileImage, 0.75) { let blob = Blob(contentType: "image/jpeg", data: imageData) mutableDoc.setBlob(blob, forKey: "image") } // … Add other properties mutableDoc.setString("user", forKey: "type") mutableDoc.setString("Priya", forKey: "name") mutableDoc.setString("priya.rajagopal@couchbase.com, forKey: "email") do { try? db.saveDocument(mutableDoc) } catch { print("Error in saving document : (error)") } 内部表現 ドキュメントがCouchbase Liteで作成されると、内部的には次のようになります。 { "email": "priya.rajagopal@couchbase.com", "image": { "length": 3888349, "digest": "sha1–4xlj1AKFgLdzcD7a1pVChrVTJIc=", "content_type": "image/jpeg", "@type": "blob" }, "type": "user", "name": "Priya", "id": "user::priya", "rev": "1–1c8502034001b333cc469fe8c4c39e112eedf8a3" } 添付ファイル取得 更新アタッチメントがあなたのCouchbase Liteのアプリにオーバー同期された場合、使用するBlob APIをデータを取得します。これは、このAPIの使用法を示すSwiftコードスニペットです。 if let doc = db.document(withID: "user::priya") { let blobValue = doc.blob(forKey:"image")?.content // use the blobValue } Couchbase Lite添付ファイル仕様 保存場所とサイズ Couchbase Liteでは、添付ファイルは、対応するドキュメントを含むCouchbase Liteデータベースインスタンスに保存されます。これは、添付ファイルへの参照を保持する関連メタデータを含むドキュメントとは別に保存されます。同じ添付ファイルが複数のドキュメントで共有されている場合、添付ファイルの1つのインスタンスのみがデータベースに保存されます。 Couchbase Liteでは、添付ファイルのサイズに制約はありません。添付ファイルがCouchbase Serverと同期されないことが保証されている限り、添付ファイルのサイズは問題となりません。 Couchbase Serverと同期した場合の制約 保存場所とサイズ Couchbase Serverでは、添付ファイルは対応するドキュメントと同じCouchbase Serverバケットに保存されます。これは、添付ファイルへの参照を保持するメタデータを含むドキュメントとは別に保存されます。同じ添付ファイルが複数のドキュメントで共有されている場合、添付ファイルの1つのインスタンスのみがバケットに保存されます。 JSONドキュメントに1つ以上の添付ファイルを添付できます。ドキュメントに関連付けることができる添付ファイルの数に厳しい制限はありません。ただし、添付ファイルのメタデータはドキュメントの拡張属性に保存されるため、添付ファイルの数は、ドキュメントごとに許可される同期メタデータのサイズによって制限されます。添付ファイルのメタデータは100〜200バイトの範囲をとり、同期メタデータのサイズはドキュメントあたり1 MBに制限されているため、ドキュメントに関連付けることができる添付ファイルの数には実際的な制限があります。 添付ファイルの最大サイズは20MBです。これは、Couchbase Serverのドキュメントサイズの制限によるものです。 同期の条件とプロセス レプリケーションプロトコルは、添付ファイルが更新された場合にのみ添付ファイルを同期するように最適化されています。これは、関連付けられたJSONドキュメント内の他のデータが更新された場合でも、Couchbase Liteクライアントによってプッシュまたはプルされないことを意味します。 関連するすべての添付ファイルが正常に同期されるまで、ドキュメントはSync GatewayまたはCouchbase Liteによって永続化されません。そのため、関連するドキュメントがない孤立した添付ファイルとして存在する時間が生じる可能性があります。その後の同期で、添付ファイルが既に保持されていることが認識され、ドキュメントのみが同期されます。 Sync Gateway APIとの併用 添付ファイルをCouchbase Liteクライアントで利用するだけでなく、Sync Gateway APIから利用(作成・変更)することが可能です。ただし、この場合、添付ファイルの内部表現に違いが生じます。そして、異なる方法で作成されたデータ間の整合性を保つために、開発者が考慮しなければならない要素が生じます。 詳しくは、以下のブログを参照してください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] Compositional Layouts で Readable Width の対応をする

前置き 多くの会社で Compositional Layouts を使っているところが増えてきたのではないでしょうか? 今回は Compositional Layouts におけるiPad対応(Readable Width)を考えてみました。実際のリリースしているプロダクションの事例を元に紹介していきたいと思います。 考察 1. 既存の方法 実装方法によって対応方法が変わってきます。 ① デフォルトのUITableView のセルは cellLayoutMarginsFollowReadableWidth を true にすることで対応可能です。 tableView.cellLayoutMarginsFollowReadableWidth = true ② Interface Builder で行う場合、親のViewでFollow Readable Width にチェックをつけ、Constraintsを調整することで対応が可能です。 ③ Autolayout で行う場合、readableContentGuide に設定することで対応できます。 NSLayoutConstraint.activate([ tableView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor) ]) ↓ 具体的な実装事例はこちらを参考にすると良いでしょう 2. 既存の問題点 Compositional Layoutsを導入した画面に既存の方法を取り入れた場合、先程の ② or ③ で UICollectionView の全体に Constraints を設定することになるかと思います。 この方法は、数行のコードで済み、メンテナンスしやすいですが、スクロールのタップ範囲が狭まってしまうという問題があります。 動作例 この場合、左右スクロールバーより外は触っても何もスクロールが起きません。 まだ、iPadのデザイン対応が間に合っていない場合に、恒久対応としてはすぐに導入するくらいの温度感が良いでしょう。 3. Compositional Layouts での対応方法 Compositional Layouts は コードのframeでレイアウトを設定していくので、これに合わせて幅を調整していくことになります。 // 画面幅 let viewWidth = view.frame.width // 端末ごとの readable width を取得 let readableWidth = view.readableContentGuide.layoutFrame.width // 左右のマージン let margin = (viewWidth - readableWidth) / 2 // 横幅の最大値 let dimension: NSCollectionLayoutDimension = .absolute(readableWidth) 画面から readableContentGuide の layoutFrame が取得でき、ここから各端末の readable width を取得することができます。また、中央寄せにするために、左右のマージンを差分から計算します。 実際に設定するコード例としてはこのような形です。 let item = NSCollectionLayoutItem(layoutSize: .init( widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1) )) let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init( widthDimension: .absolute(readableWidth), // readableな横幅を指定する heightDimension: .absolute(100) ), subitem: item, count: 3) let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin) // 中央寄せ section の contentInsets を調整することで中央寄せにしています。 プロダクションでの画面例 このように frame を操作することで Compositional Layouts でも iPad の readable width に対応することが可能となります。 4. iOS14 以降 ここまで実装方法を書いてきましたが、iOS14 以降の Compositional Layouts では contentInsetsReference で設定するだけで済みます。 終わりに iPad 対応は後回しにされがちですが、そこまで手間もかからず実装できるので、是非試してみて欲しいです。 皆さんも iPad 対応をすることで、快適な iPad ライフを実現しましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最小単位でFluxアーキテクチャを試す

概要 Fluxアーキテクチャを最小単位で実装してみます。 今回はRxSwiftを用いてFluxのサンプルを実装します。 環境 Xcode 13.1 以下の内容はXcodeのPlayground環境を使ってます。 環境を作るときは以下のリンクを参考にしました。 Fluxアーキテクチャの登場人物 以下にFluxアーキテクチャの登場人物を簡単に紹介します。 View 画面表示とユーザーイベントをハンドリングします。 Store アプリケーションの状態を管理します。 Action Storeを変更できる唯一の存在です。 Dispatcher Actionは必ずDispatcherを通してStoreへ渡されます。 具体的にはDispatcherにdispatchというメソッドを実装し、 Actionを渡せるようにします。 Fluxアーキテクチャとは? 2014年にFacebookが発表したアーキテクチャです。 特徴はデータの流れが単一方向であることです。 データは以下のような流れをたどります。 1. Viewでイベントが発生する 2. イベントをトリガーにActionCreatorがActionを作成する 3. Dispatcherを通してActionがStoreへ渡される 4. 渡されたActionをStoreが解釈して自身を更新する 5. Storeを監視しているViewが更新される サンプルを作成する 実際にサンプルを作ってみます。 Storeを実装する アプリケーションの状態は一つなのでシングルトンで実装します。 FluxPlayground import RxSwift import RxCocoa final class Store { static let shared: Store = .init() private let _number = BehaviorRelay<Int>(value: 0) var number: Int { return _number.value } var numberObservable: Observable<Int> { return _number.asObservable() } private init() {} } Actionを実装する StoreはActionを解析して自身を更新します。 アプリケーションの規模が大きくになるにつれて種類が増えるため、 enumで表現します。 FluxPlayground enum Action { case updateState(Int) } Dispatcherを実装する 二つの関数を実装します。 1. StoreにActionを登録するための関数 register(callback:) 2. Actionを実行するための関数 dispatch() FluxPlayground final class Dispatcher { static let shared: Dispatcher = .init() private let _action = PublishRelay<Action>() private init() {} func register(callback: @escaping (Action) -> ()) -> Disposable { return _action.subscribe(onNext: callback) } func dispatch(_ action: Action) { _action.accept(action) } } StoreにActionを登録する Actionに応じてStoreが更新されるように初期化処理を記述します。 FluxPlayground final class Store { static let shared: Store = .init() private let _number = BehaviorRelay<Int>(value: 0) var number: Int { return _number.value } var numberObservable: Observable<Int> { return _number.asObservable() } // 以下追加 private init(dispatcher: Dispatcher = .shared) { dispatcher.register { action in switch action { case .updateState(let int): self._number.accept(int) } } } } 全体のコードです。 FluxPlayground import RxSwift import RxCocoa enum Action { case updateState(Int) } final class Store { static let shared: Store = .init() private let _number = BehaviorRelay<Int>(value: 0) var number: Int { return _number.value } private init(dispatcher: Dispatcher = .shared) { dispatcher.register { action in switch action { case .updateState(let int): self._number.accept(int) } } } } final class Dispatcher { static let shared: Dispatcher = .init() private let _action = PublishRelay<Action>() private init() {} func register(callback: @escaping (Action) -> ()) -> Disposable { return _action.subscribe(onNext: callback) } func dispatch(_ action: Action) { _action.accept(action) } } print("Initial State: \(Store.shared.number)") // => Initial State: 0 Dispatcher.shared.dispatch(.updateState(2)) print("Next State: \(Store.shared.number)") // => Next State: 2 Storeの初期状態が0です。 その後、Actionをディスパッチすることで状態が更新されることが確認できました。 参考リンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Xcode】ファイルを作成する時のSwift FileとCocoa Touch Classの違い

初投稿です。 基本備忘録ですが、同じ疑問を持った方の為になれば幸いです。 今回はXcodeでファイルを作成する時に選択する「Swift File」と「Cocoa Touch Class」の違いをまとめてみました。 結論から申しますと、ファイル作成時にテンプレートがあるかないかだけの違いみたいです。 まず「Swift File」から見ていきましょう。 Swift File 「Swift File」を作成すると下記のような記述がされたファイルが作成されます。  ※コメント部分は省略しています。 import Foundation 上記のようにこの一行だけ記述されたファイルが作成されます。 「Cocoa Touch Class」 次に「Cocoa Touch Class」はどうでしょう。 「Cocoa Touch Class」を作成する場合は作成時にSub class(継承するクラス)を指定する必要があります。 ここではUITableViewControllerを指定しています。  ※ここでもコメント部分は省略しています。 import UIKit class TableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() } override func numberOfSections(in tableView: UITableView) -> Int { return 0 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 0 } } 上記の場合はUITableViewControllerを継承したので、必須なメソッドであるnumberOfSectionsとnumberOfRowsInSectionを元から記述してくれているということですね。 まとめ 「Swift File」は何も記述されていない白紙のファイルを作成したい時、「Cocoa Touch Class」は用途に応じて、継承させたいクラスがある時に使うというような解釈でいいと思います。 ここ違うよ!という場所などがありましたら、ご指摘いただければと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む