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

【Swift】キーボード表示時にviewを移動させる処理で詰まったこと(メモ)

症状 textFieldをタップした際にいい感じにviewを上に移動させたいが下記のような状態になってしまう。 本当は上に動くようにしたいのに、viewが下に動いてしまって何も見えなくなってしまっています・・・ この部分の処理はこんな感じ↓(ViewdidLoadでsetUpNotificationForTextField()を読んで使用している) func setUpNotificationForTextField() { let notificationCenter = NotificationCenter.default // キーボードが出る時に呼ばれる notificationCenter.addObserver(self, selector: #selector(self.keyboardWillShow(_ :)), name:UIResponder.keyboardWillShowNotification, object: nil) // キーボードが隠れる時に呼ばれる notificationCenter.addObserver(self, selector: #selector(self.keyboardWillHide(_ :)), name:UIResponder.keyboardWillHideNotification, object: nil) } // キーボードが出てきた時の処理 @objc func keyboardWillShow(_ notification: Notification) { // キーボードのMinYを取得 let keyboardFrame = (notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue guard let keyboardMinY = keyboardFrame?.minY else { return } // doneBtnのmaxYを取得 let btnMaxY = doneBtn.frame.maxY // 動きを登録 let distance = btnMaxY - keyboardMinY + 60 let transform = CGAffineTransform(translationX: 0, y: -distance) // アニメーション UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [], animations: { self.view.transform = transform }) } //キーボードが隠れた時の処理 @objc func keyboardWillHide(_ notification: Notification) { UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [], animations: { self.view.transform = .identity }) } 上記でコードを載せたはいいが他画面では、ほぼ同じ処理で正常に動作しているのでコードが原因ではなさそう・・・ 原因 原因は下記の赤枠部分をstackViewに入れているからでした。 下の部分のコードは、下記画像の赤色部分ように画面上部からdoneBtnの下部までの距離を取得するのが意図でしたが、ボタンをstackViewに入れることで下記画像の緑部分のように、stackViewの上部からdoneBtnの下部までの距離を取得してしまっていたことが今回の症状の原因でした。 二つのボタンをstackViewから出してあげることで正常に動作しました。 // doneBtn(チェックマークのボタン)のmaxYを取得 let btnMaxY = doneBtn.frame.maxY わかったこと UI部品のMaxY, MinY , MaxX, MinXは親viewを基準にしている(間違っていたらすみません) 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIで書くiOSアプリハンズオン(QiitaClientを作ってみよう)

SwiftUIを使ったハンズオン用に記事を作成しました 使用するAPI 今回はQiitaAPiを題材とします。(ドキュメント) 登録不要で使えて、例えば、 https://qiita.com//api/v2/items で、投稿を取得できます。 [{ rendered_body: "...", coediting: false, comments_count: 0, created_at: "2017-11-09T22:50:51+09:00", group: null, id: "fe412a67b64e793d138b", likes_count: 0, private: false, reactions_count: 0, tags: [{ name: "processing", versions: [] }, { name: "openFrameworks", versions: [] } ], title: "【openFrameworks 初心者冒険記5】逃げまくってた...三角関数の扉を再ノック。sin() cos() サインコサイン....。メディアアートで避けては通れない?最初の壁", updated_at: "2017-11-09T22:50:51+09:00", url: "http://qiita.com/39_isao/items/fe412a67b64e793d138b", user: { description: "鮭とメロンパンが好物な奥田民生になりたいボーイです。 最近はてなブログ始めました http://sudara-bluse.hatenablog.com/", facebook_id: "", followees_count: 52, followers_count: 44, github_login_name: null, id: "39_isao", items_count: 103, linkedin_id: "", location: "", name: "", organization: "", permanent_id: 76843, profile_image_url: "https://qiita-image-store.s3.amazonaws.com/0/76843/profile-images/1508030106", twitter_screen_name: "sudara_bluse", website_url: "http://sudara-bluse.hatenablog.com/" } }, {,,,} ] 今回は、以下のデータを使ってiOSアプリを作ってみます。 投稿タイトル ユーザアイコン URL 完成したアプリ プロジェクトを作成する 使うライブラリをインストールする file -> swift package から使用するライブラリを追加します https://github.com/dmytro-anokhin/url-image.git 通信周りのクラスを作成する JSONを変換するための構造体を作成 import Foundation struct Item: Decodable, Identifiable { let id: String let title: String let body: String? let url: String let user: User } struct User: Decodable { let profileImageUrl: String } APIからデータを取得するクラスを実装 SwiftUIの画面から参照するためObservableObject を継承し、画面に表示する値を @Published にする import Foundation class ApiFetcher: ObservableObject { @Published var items = [Item]() private let baseURL = "https://qiita.com/api/v2" func fetchItems(query: String) { let url = URL(string: "\(baseURL)/items?query=\(query)&page=1&per_page=50")! URLSession.shared.dataTask(with: url) {(data,response,error) in do { if let data = data { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase let items = try decoder.decode([Item].self, from: data) DispatchQueue.main.async { self.items = items } } } catch { // ignore } }.resume() } } 一覧画面を作る 一覧のセルを作成する 新しく ItemRow.swift というファイルを作成します import SwiftUI import URLImage struct ItemRow: View { let item: Item var body: some View { HStack { URLImage(URL(string: item.user.profileImageUrl)!) { proxy in proxy.image .resizable() .aspectRatio(contentMode: .fill) .clipped() }.frame(width: 60.0, height: 60.0) Text(item.title) } } } struct ItemRow_Previews: PreviewProvider { static var previews: some View { ItemRow(item: Item( id: "d040cf8b2d15bd7e507d", title: "[Angular] Angular アプリの構成をみる", body: "Angular に関する自身の勉強の復習がてらの備忘録記事。", url: "https://qiita.com/ksh-fthr/items/d040cf8b2d15bd7e507d", user: User( profileImageUrl: "https://qiita-user-profile-images.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F193342%2Fprofile-images%2F1500888159?ixlib=rb-1.2.2&auto=compress%2Cformat&lossless=0&w=300&s=9a22e880d804d67db66a33ac2e7671b5" ) )) } } 一覧画面を作成する 新しく ItemRow.swift というファイルを作成します import SwiftUI struct ItemList: View { @ObservedObject var fetcher = ApiFetcher() @State var keyword: String = "" var body: some View { NavigationView { VStack { TextField("Search", text: $keyword) { self.fetcher.fetchItems(query: self.keyword) }.padding(16) List(fetcher.items) { item in ItemRow(item: item) } } .navigationBarTitle(Text("投稿一覧")) } } } struct ItemList_Previews: PreviewProvider { static var previews: some View { ItemList() } } ここまで作成するとプレビューが以下のように表示されます 起動画面を一覧画面にする もともと作成されている ContentView を修正して一覧画面を表示するようにします struct ContentView: View { var body: some View { ItemList() } } 起動する ここまで作ったら一度起動して入力した文字で検索できることを確認してみましょう 詳細画面を作る 詳細画面は一覧でタップした投稿をWebViewで表示します 新しくItemDetail.swiftを作成します import SwiftUI import WebKit struct ItemDetail: View { let title: String let url: String var body: some View { WebView(url: URL(string: url)!) .navigationBarTitle(Text(title), displayMode: .inline) } } struct WebView : UIViewRepresentable { var url: URL func makeUIView(context: Context) -> WKWebView { return WKWebView(frame: .zero) } func updateUIView(_ uiView: WKWebView, context: Context) { let req = URLRequest(url: url) uiView.load(req) } } struct ItemDetail_Previews: PreviewProvider { static var previews: some View { ItemDetail(title: "テスト", url: "https://www.apple.com") } } 一覧から詳細画面に遷移できるようにする ItemList.swift の List を以下のように修正して画面遷移できるようにします List(fetcher.items) { item in ItemRow(item: item) } List(fetcher.items) { item in NavigationLink( destination: ItemDetail( title: item.title, url: item.url ) ) { ItemRow(item: item) } } 実行してみましょう 最後に 完成はこちらにあります。 https://github.com/decoch/swiftui_example_qiita_client おつかれさまでした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エラーハンドリング throws try do catch Swift

■エラーハンドリング エラーが出る可能性がある処理を行う時に使う これエラーやでってなったらエラーの処理を返す エラーじゃなかったらそのまま処理される。 ■エラーの例: こんな時に使う!! 割り算で引数n2が0の場合エラーになる。 割り算は0で割れないから //n2が0ならprint("0以外を入力してね") //n2が0以外ならreturn n1 / n2 func div2(n1: Int,n2:Int) -> Int? { guard n2 != 0 else { print("0以外を入力してね") return nil } return n1 / n2 } 正直上記の書き方でもできるがそれをぱっと見だけで エラー処理しとるんやなって分かるように書くのがthrows try do catchとかを使ったやつ!! ■throws まず書き方 //enumでまずエラー出た時の処理を書く Errorプロトコルを継承してる enum CalculationError: Error { case divisionBYZero case unknown } func div(n1: Int,n2:Int) throws -> Int { guard n2 != 0 else { throw CalculationError.divisionBYZero } return n1 / n2 } /* 見比べたら何が違うのか結構分かりやすい! func div2(n1: Int,n2:Int) -> Int? { guard n2 != 0 else { print("0以外を入力してね") return nil } return n1 / n2 } メソッドの返り値の矢印の左側に throwsと書く エラー処理にまずthorowと書きその右にenumで作ったエラー処理を書く returnがいらない 後は最初に例で紹介したやつと同じ感じ。 最初の例と見比べたらこう書くのねってのが分かってくると思います! ■do catchで処理しよう! 下記は不適切なメソッドの処理 tryつけなエラー try!クラッシュする try?nilを返すどんなエラーかわからない... div(n1: 3, n2: 0) //tryつけないとエラー try! div(n1: 3, n2: 0) //エラーでたらクラッシュさせる try? div(n1: 3, n2: 0) //エラーならnil ■そこでdo Catchの出番!! 基本これ使って処理する。 catch let error as CalculationErrorで定数errorにenumで作ったエラー処理が入る。 それをswitch文で分ける。 どちらでもないエラーには print("other")を用意しとく。 do { let r3 = try div(n1: 10, n2: 0) print(r3) } catch let error as CalculationError { switch error { case .divisionBYZero: print("divisionBYZero") case .unknown: print("unknown") } }catch { print("other") } //結果 divisionBYZeroがプリントされる。 //divisionBYZeroは0が入力されている時のエラーの言葉みたいなもの エラー処理をユーザーに伝えたい時などに使う。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUI Tutorialsの解説と振り返り

この記事は投稿は何? AppleのSwiftUI Tutorialsを一通り実践したので、振り返ります。 実行環境 macOS 11.3.1 Xcode 12.5 Swift 5.4 SwiftUI Tutorials 全部で3つのチャプターから構成されます。 それぞれのチャプターは、3つのセクションで構成されています。 ここでは最初のチャプターを振り返ります。 Section 1 Create a New Project and Explore the Canvasでは、ビューを構築するための基本的な方法を学びます。 下図は、このSection1で作成したビューをツリー構造にしたものです。 ここでは、画面の表示を更新する仕組みも扱っています。それが@Stateという属性です。 Section 2 Building Lists and Navigationでは、データをリスト形式で表示する画面を構築します。 Section2は、データから動的にビューを作成する方法を理解しておくことも重要なところです。 Section 3 Handling User Inputでは、アプリ全体で共有するデータモデルを導入します。 上図は、理解しやすくするためにだいぶ簡略化して表現したものですが、データモデルを共有する仕組みがわかるかと思います。 動画教材 Udemyという動画学習サービスで、チュートリアルを実践した様子と解説を動画にして、学べるようにしました。 無料のクーポンを発行できたので、こちらで配布させていただきます。 本日(2021/5/12)より3日間以内の登録期限がありますが、どなたでもお気軽にご利用くださいませ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【swift】配列(Array)と辞書(Dictionary)とは??

はじめに こんにちは。 今回はswiftにおける配列と辞書について 違いなどに触れながら記していきます。 配列とは? 配列とは複数のデータを一つにまとめて管理することができます。 管理の方法は添字となる。 添字とは、配列内の個別のデータを管理する数値です。 配列の定義 # 型推論ver var 配列名 = [値1, 値2, 値3,...] # not 型推論ver var 配列名:Array<型名> = [値1, 値2, 値3,...] var 配列名:[型名] = [値1, 値2, 値3,...] 例 var a:Array<Int> = [1, 2, 3] print(a[0]) // => 1 var b:[Int] = [1, 2, 3] print(b[0])// => 1 ## データを取得する際は、、配列名[添字] 辞書とは? 配列が添字という番号で管理していたのに対し、 辞書は、キーというデータで管理します。 以下が定義法と例です。 辞書の定義 # 型推論ver var 辞書名 = [キー1:値1, キー2:値2, キー3:値3,...] # not 型推論ver var 辞書名:Dictionary<キーの型名:値の型名> = [キー1:値1, キー2:値2, キー3:値3,...] var 辞書名:[キーの型名:値の型名] = [キー1:値1, キー2:値2, キー3:値3,...] 例 以下、乗り物とその乗り物のタイヤの数で定義しています。 var numberOfTires = ["車":4, "バイク":2, "船":0] print(numberOfTires["車"]) // => 4 ## データを取得する際は、、辞書名[キー名] まとめ 配列は、添字と値が結びついているもの。 辞書は、キーと値が結びついているもの。 となる。 ここまで、お読みいただきありがとうございます!! 今日はこの辺で!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】FloatingPanelで半モーダルを作ってみる

作ったもの ソースコードはこちら↓ 公式 環境 Xcode:12.4 Swift:5 CocoaPods:1.10.1 作り方 podインストール FloatingPanelをインストール pod 'FloatingPanel' ソースコード ViewController import UIKit import FloatingPanel class ViewController: UIViewController { var fpc = FloatingPanelController() override func viewDidLoad() { super.viewDidLoad() fpc.delegate = self } @IBAction func tappedBtn(_ sender: Any) { // 半モーダル(tip)から下フリックでモーダルを閉じる fpc.isRemovalInteractionEnabled = true // モーダルを角丸にする let appearance = SurfaceAppearance() appearance.cornerRadius = 9.0 fpc.surfaceView.appearance = appearance // 半モーダルにtableviewをセットする let tableView = self.storyboard?.instantiateViewController(withIdentifier: "tableview") as? TableViewController fpc.set(contentViewController: tableView) // 半モーダルを表示する fpc.addPanel(toParent: self) } } extension ViewController: FloatingPanelControllerDelegate { // カスタム半モーダル func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout { return LandscapePanelLayout() } } class LandscapePanelLayout: FloatingPanelLayout { let position: FloatingPanelPosition = .bottom let initialState: FloatingPanelState = .tip var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] { return [ // 全モーダル時のレイアウト .full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea), // ハンモーダル時のレイアウト .tip: FloatingPanelLayoutAnchor(absoluteInset: 130.0, edge: .bottom, referenceGuide: .safeArea), ] } // モーダルの横幅をカスタマイズ // func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] { // return [ // surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0), // surfaceView.widthAnchor.constraint(equalToConstant: 291), // ] // } } 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift だけで Unity の iOS の Native Plugin を作る

Swift だけで Unity の iOS の Native Plugin を作る Unity は Native Plugin を作成することで、Unity から提供されていない、OS 固有の機能を使用することができます 例えば iOS では、Bluetooth や MP4 動画の作成、アルバムの利用などが出来るようになります これらの Native の機能を Swift で実装し、C# から呼び出す手順です Swift Package Manager (以下 SwiftPM) を使って、Framework を作成し、Unity に組み込みます ついでに、ライブラリの開発用の Native のアプリターゲットを作成します Framework 化するメリット PostProcess の設定が不要です Unity の Inspector 上で Embed の設定が完了します 合わせて Native でアプリターゲットを実装する事で、動作確認のために Unity のビルドをせずに、ライブラリの開発が可能です 今回の話のリポジトリです↓ やりたい事 今回は、Swift で文字列型の数値を long 型に変換する Native Plugin を実装します [DllImport("__Internal")] を付けて定義された、関数 swiftPmPlugin_toNumber が Swift で実装された関数です 今回は、C# にも存在する機能ですが、呼び出した先の Swift で iOS 固有の機能を利用することが可能です → https://github.com/fuziki/UnityPluginXcodeTemplate/blob/develop/Examples/UnityExample/Assets/Scripts/Cube.cs public class Cube : MonoBehaviour { // Swift で実装された関数の定義 [DllImport("__Internal")] private static extern long swiftPmPlugin_toNumber(string numberString); void Update() { // 呼び出し Debug.Log("number is: " + swiftPmPlugin_toNumber("30")); } } Native Plugin の実装 Swift Package Manager (SwiftPM) を使って実装し Framework を出力します 上記の swiftPmPlugin_toNumber が実装された SwiftPmPlugin.framework を作成します 環境 maxOS 11.3.1 Xcode 12.5 Swift 5.4.0 Unity 2020.3.5f1 Swift Package Manager type に libraryを、name に 作りたいパッケージの名前 (今回は SwiftPmPlugin) を指定して、package を初期化します library package が作成されるので、Package.swift を開きます Xcode が起動するので、SwiftPmPlugin.swift に実装していきます swift package init --type=library --name=SwiftPmPlugin open Package.swift SwiftPmPlugin の実装 Swift でロジックを実装します SwiftPmPlugin クラスに、文字列を数値に変換する toNumber 関数を実装しました Int(string) ?? 0 として数値変換に失敗したときは、0 を返すようにしました → https://github.com/fuziki/UnityPluginXcodeTemplate/blob/develop/Sources/SwiftPmPlugin/SwiftPmPlugin.swift class SwiftPmPlugin { var text = "Hello, World!" static func toNumber(string: String) -> Int { return Int(string) ?? 0 } } Swift で関数を公開する Swift で公開する関数を作成します @_cdecl を使って、C の関数として定義します char 配列のポインタを受け取って、Swift の String 型に変換します 先ほど作った SwiftPmPlugin.toNumber を使って、String から Int に変換します C# で定義した関数が戻り値が long 型で 64bit 整数なので戻り値も、揃えます @_cdecl("swiftPmPlugin_toNumber") public func swiftPmPlugin_toNumber(_ stringPtr: UnsafePointer<CChar>?) -> Int64 { let str = String(cString: stringPtr!) return Int64(SwiftPmPlugin.toNumber(string: str)) } Framework でビルドする SwiftPM から xcodeproj を出力して xcodebuild を使って出力した xcodeproj から framework を作成します CONFIGURATION_BUILD_DIR に指定したディレクトリに、SwiftPmPlugin.framework 作成されています swift package generate-xcodeproj --skip-extra-files xcodebuild -project SwiftPmPlugin.xcodeproj -scheme SwiftPmPlugin-Package -configuration Release -sdk iphoneos CONFIGURATION_BUILD_DIR=. Framework の Headers を確認すると、先ほど実装した swiftPmPlugin_toNumber が公開されていることが分かります open SwiftPmPlugin.framework/Headers/SwiftPmPlugin-Swift.h Unity でビルドする Framework の設定 Plugins/iOS/ を作成して、SwiftPmPlugin.framework を配置します SwiftPmPlugin.framework の Inspector で、Add To Embedded Binaries を有効にします Add To Embedded Binaries を有効にすることで、ビルドしたときに Framework が自動で Embed され ます iOSアプリをビルドする Unity で、通常通り iOS 向けにビルドします PostProcess などは、設定していないです 作成した target の frameworks に、SwiftPmPlugin.framework があり、 Embed の項目が、Embed & Sign になっていることを確認します 自動で追加されていない場合は、Unity の設定に失敗している可能性があります 手動で追加しても問題ないです 確認できれば通常通り iOS アプリを実行できるはずです Framework 開発用の Native のアプリターゲットを作る Framework の開発が便利になる tips 的なやつです Unity で使う Framework の動作確認のために、Framework のビルド→Unityのビルド→iOSのサイクルを回すのは大変です そこで、SwiftPM で作ったライブラリを取り込んだ、アプリターゲットを作成し、開発すると効率的だと考えています xcworkspace を作成する xcworkspace を作成し、Package を取り込み、アプリターゲットのターゲットを用意します ※ アプリを Objective-C で作る必要はないです、Swift でも問題ないです アプリターゲットの Frameworks に SwiftPmPlugin を追加します コードから呼び出すことが可能なので、呼び出します #import "ViewController.h" @import SwiftPmPlugin; @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. NSLog(@"value: %lld", swiftPmPlugin_toNumber("20")); } @end Swift からも呼び出すことが可能です import SwiftUI import SwiftPmPlugin struct ContentView: View { var body: some View { Text("number: \(swiftPmPlugin_toNumber("20"))") .padding() } } おわりに Native Plugin を実装することで、ゲームの開発は Unity に集中しつつ、OS 固有の機能を十分に利用することが可能です Swift で実装し、SwiftPM で管理し、Native アプリで動作確認することで、効率的な開発を目指しています
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】UserDefaultsを扱いやすくする

はじめに 今回はUserDefaultsをジェネリクスとプロトコルを使って少し工夫します。 やりたいこと 普通は以下のように使うと思います。 UserDefaults.standard.set("サンプル", forKey: "sampleKey") // 書き込み let text = UserDefaults.standard.string(forKey: "sampleKey") // 読み込み 今回は以下のように、forKeyを工夫してアクセスしやすくします。 let userDefault = UserDefault<UserDefautlsKeys.Animal>() userDefault.set(true, forKey: .dog) let bool = userDefault.bool(forKey: .dog) 一番簡単なのは、以下のようにStringを拡張する方法です。 extension String { static var dog: String { return "sampleDogKey" } static var cat: String { return "sampleCatKey" } } しかし、これだとUserDefaultsのkey以外でもこの文字列を使うことができ、あまり意味がありません。 今回はUserDefaultのforKeyを設定する時のみ、このような使い方をするにはどうすればよいのかをやっていこうと思います。 実装 struct Animal { let dog: String let cat: String } struct Drink { let water: String let milkTea: String let cola: String } struct UserDefautlsKeys { enum Animal: String, UserDefautlsKeyProtocol { static let domain: Domain = .animal var id: String { self.rawValue } case dog case cat } enum Drink: String, UserDefautlsKeyProtocol { static let domain: Domain = .drink var id: String { self.rawValue } case water case milkTea case cola } } enum Domain: String { case animal case drink } protocol UserDefautlsKeyProtocol { static var domain: Domain { get } var id: String { get } var key: String { get } } extension UserDefautlsKeyProtocol { var key: String { Self.domain.rawValue + "." + id } } final class UserDefault<R: UserDefautlsKeyProtocol> { private let userDefaults: UserDefaults init(userDefaults: UserDefaults = .standard) { self.userDefaults = userDefaults } func set(_ value: Any?, forKey identifier: R) { userDefaults.set(value, forKey: identifier.key) } func bool(forKey identifier: R) -> Bool? { return userDefaults.bool(forKey: identifier.key) } } 解説 一番のポイントはUserDefaultのジェネリクスです。これでキーの型をUserDefautlsKeyProtocolが準拠したものに限定できます。今回であれば、UserDefautlsKeysのAnimalとDrinkに準拠させているので、これらのkey(今回はkeyを初期設定しているので、enumの中には書かれていないが、domainとidで一意に決まる)に限定できます。 また、プロトコルを使うことでUserDefaultsKeyを抽象化し、キーを扱いやすくしています。 おわりに おわりです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RxSwiftってなんなの?

はじめに そろそろ勉強しなきゃという思いとRxSwiftを触ってみたいなという思いがぶつかってQiita書こうと思いました。 そもそもRxって? Reactive Extensionsの略称でリアクティブプログラミングを実現するための 設計パターン及び、ライブラリを指します。 元々は、マイクロソフトが自社製品に組み込んでいる.NETフレームワークに向けて開発した.NET用のライブラリ(Rx.NET)でしたが、あまりにも強力な技法だったので色んな言語に波及していきました。 それがSwiftにも波及してきてRxSwiftが開発されました。 リアクティブプログラミングとは 難しく感じますが、エクセルやスプレッドシートをイメージすると分かりやすいです。 例えば、このように A1=1 B1=2 の値を入力してC1に=A1+B1という関係を入力します。 すると瞬時に計算が走って値が出力されました。 B1の値を変えたとしても、A1の値を変えたとしてもC1に計算の答えが瞬時に出力されます。 このような動きが正にリアクティブプログラミングと言われています。 リアクティブプログラミングは時間軸を重視したプログラミング方法となっており プログラム内での値の変化を時間軸で捉え、値が変わったというイベントによって処理を行います。 先ほどのエクセルやスプレッドシートの例を図にするとこんな感じ↓ 左から右に時間が流れ、そこにイベントが流れてきます。 ここではAもしくはBの値が変化したというイベントが流れます。 そして、イベントが発生したらA+B=Cという処理が走っています。 上記の図は、正式名称がありマーブルダイアグラムと呼ばれています。 下記のサイトに色んなオペレーター(演算)によるマーブルダイアグラムの図が 載っていますので見てみて下さい↓ RxMarbles このようにマーブルダイアグラムでは時間順に並んだ進行中のイベントが流れており これらをストリームと呼びます。 Observer(オブザーバー)パターン ObserverパターンはRxの根幹となる考え方です。 この考え方には観測対象と観測者の2つの役割が存在しています。 観測対象を観測者が観測していて、観測対象の何らかのイベントを観測者が確認したら特定のアクションを起こすというのが基本の考え方となっています。 ただ実際に実装する際は観測対象に観測者を登録して、観測対象でイベントが発生したら観測者に知らせて特定のアクションを起こすといった流れになります。 ここでいうイベントは、ボタンがタップされた・文字が入力された・APIから値が取得できた等、プログラム内におけるオブジェクトの状態変化すべてを指します。 ObservableとObserver Observerパターンでは次の2つのクラスでストリームを扱います。 ・Observable: ストリームを表現するクラス ・Observer: Observableからストリームを受け取るクラス 図にすると、こんな感じです↓ subscribe Observableはsubscribeメソッドを持っています。 これは何なのかというと、通知先を登録するメソッドです。 つまり、Observableに対してObserverを登録するメソッドですね。 ※Observableを監視することをsubscribeとも呼んでいます。 先ほどの図で表すとこんな感じ↓ Observableが通知するイベント Observableが通知するイベントは3種類あり、enumで定義されています。 これらのイベントをObserverが受け取ります。 public enum Event<Element> { // 通常のイベントを通知、値を渡すことができる // 複数回送れる case next(Element) // エラーの発生を通知 // 発生後はイベントが一切発生しない case error(Error) // 完了を通知、値を渡せない // 発生後はイベントが一切発生しない case completed } 図に表すとこんな感じ↓ 実際にコードで書くとこのようになります↓ let observable = textField.rx.text.orEmpty.asObservable() observable.subscribe(onNext: { text in print(text) }, onError: { error in print(error) }, onCompleted: { _ in print("完了") }) コードを見てみると流れてきたイベントに対する処理をObservableに登録しているという見方もできます。 そして、注意して欲しいのが一度、onCompletedやonErrorが発生すると それ以降はonNextを呼ぶことが出来ません。 Disposable Observableをsubscribeすると、Disposableというものを返します。 これは、subscribeしたObservableをunsubscribeするための仕組みで 無視するとsubscribeした処理が永遠に解放されずにメモリリークに繋がる恐れがあります。 その解決策としてDisposeBagというものがあります。 これは内部でDisposableを貯めておいて 自身が解放された時に保持しているDisposableを廃棄してくれます。 Observableはdisposeメソッドを持っており、呼び出してDisposeBagを代入する事によって subscribe時に返されるDisposableをDisposeBagに貯める処理を定義しています。 public func disposed(by bag: DisposeBag) { // selfがDisposableのこと bag.insert(self) } 実際に先ほどのコードで使用した場合↓ // DisposeBagを初期化する let dispoceBag = DisposeBag() let observable = textField.rx.text.orEmpty.asObservable() observable.subscribe(onNext: { text in print(text) }, onError: { error in print(error) }, onCompleted: { _ in print("完了") // 最後にメソッドを呼ぶ }).disposed(by: dispoceBag) 循環参照に注意する subscribe時の処理の中のクロージャはselfを強参照すると 解放されなくなってしまいます。 なのでselfを弱参照しましょう。 let dispoceBag = DisposeBag() let observable = textField.rx.text.orEmpty.asObservable() // weakキーワードでselfを弱参照にする observable.subscribe(onNext: {[weak self] text in print(text) }, onError: { error in print(error) }, onCompleted: { _ in print("完了") }).disposed(by: dispoceBag) おわりに キリがいいのでここで終わります。次回もいつか書こうと思います。 間違っている部分や説明が足りない部分はコメントして教えて下さると有り難いです。 参考記事 【Swift】RxSwift勉強してみたPart4 【RxSwift】Singleton で DisposeBag を使うことの考察 RxSwiftについてようやく理解できてきたのでまとめることにした(1) SwiftでReactive Programming - Qiita Rx入門 (2) - オブザーバーパターン - xin9le.net RxSwiftでiOSアプリ開発~リアクティブプログラミングを導入する - CodeZine
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む