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

[Swift]Array から Dictionary への変換

Struct Array から Dictionaryに変換処理(メモ) 1。reduce を利用し、変換 reduce(::) 指定されたクロージャを使用してシーケンスの要素を組み合わせた結果を返します。 func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result reduce(::) Developer Document  code dic1.swift items.reduce([String: Item]()) { (dic, item) in var resultDic = dic resultDic[item.id] = item return resultDic } 結果 [String, Item] ["3": Item(id: "3", title: "title3"), "1": Item(id: "1", title: "title1"), "2": Item(id: "2", title: "title2")] 2。Dictionary(uniqueKeysWithValues:) を利用し、変換① init(uniqueKeysWithValues:) 指定された順序でキーと値のペアから新しい辞書を作成します。 //Creates a new dictionary from the key-value pairs in the given sequence. init<S>(uniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) init(uniqueKeysWithValues:) Developer Document code dic2.swift Dictionary(uniqueKeysWithValues: items.map { item in (item.id, items.filter { $0.id == item.id }.first) }) 結果 [String, Item?] ["2": Optional(Item(id: "2", title: "title2")), "3": Optional(Item(id: "3", title: "title3")), "1": Optional(Item(id: "1", title: "title1"))] 3。Dictionary(uniqueKeysWithValues:) を利用し、変換② collections zip(::) 2つの基礎となるシーケンスから構築されたペアのシーケンスを作成します。 func zip<Sequence1, Sequence2>(_ sequence1: Sequence1, _ sequence2: Sequence2) -> Zip2Sequence<Sequence1, Sequence2> where Sequence1 : Sequence, Sequence2 : Sequence collections zip(::) Developer Document code dic3.swift Dictionary(uniqueKeysWithValues: zip(items.map {$0.id}, items)) 結果 [String, Item] ["3": Item(id: "3", title: "title3"), "1": Item(id: "1", title: "title1"), "2": Item(id: "2", title: "title2")]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift5][Combine] collect()を使ってPublisherの配列から出力をまとめた配列を返すPublisherを作成する

TL;DR Publisherの配列があり、全てのPublisherの出力をまとめた配列を返すPublisherが欲しかったらcollect()を使おう。 結果が2次元配列になってしまう場合はflatMapで1次元化しよう。 モチベーション Publisherの配列から結果をまとめて返してくれるPublisherを作ろうと思いました。 例えばFirebaseのStorageからReferenceを取得する場面があるとします。 Referenceは以下のパスで画像を保存しています。 root └── Store // お店の画像を保存するパス ├── Header // ヘッダー画像のパス │ └──{ID} // 画像 └── Image // その他の画像のパス ├── UserA // Userごとのパス │ └──{ID} // 画像 ├── UserB │ └──{ID} └── UserC └──{ID} お店というエンティティに対し、ヘッダー画像とその他の画像があります。 その他の画像はいろんなユーザーが投稿できるためユーザーごとにディレクトリを分けています。 (この設計が正しいかどうかは分かりません。。。) Headerのようにreferenceから直下の画像を取得する時はこんな感じでReferenceを取得するFutureを作ってやれば可能です。 extension StorageReference { func getReferences() -> AnyPublisher<[StorageReference], Error> { Deferred { Future<[StorageReference], Error> { [weak self] promise in guard let self = self else { return } self.listAll { result, error in if let error = error { promise(.failure(error)) return } promise(.success(result.items)) } } }.eraseToAnyPublisher() } } // Usage Storage.storage().reference(withPath: "パス") .getReferences() .sink( receiveCompletion: { result in // なんかする }, receiveValue: { references in // なんかする } ) .store(in: &cancellables) けどImageのreferenceを取得してくるのはちょっとめんどくさいです。 単純にImage直下のReferenceを取得してきても、UserA,B,CのReferenceがPrefixとして取得されるだけなので、画像のReferenceは取得できません。 画像を取得するには取得したUserA、B、CのReferenceそれぞれに対して、listAllを呼ぶ必要があります。 forEachで愚直にやってみた場合 まずはImage配下から取得できるのがitemではなくprefixなので、prefixが取得できた時はPrefixを返すように先程のFutureを変更します。 func getReferences() -> AnyPublisher<[StorageReference], Error> { Deferred { Future<[StorageReference], Error> { [weak self] promise in guard let self = self else { return } self.listAll { result, error in if let error = error { promise(.failure(error)) return } if result.prefixes.isEmpty { promise(.success(result.items)) } else { promise(.success(result.prefixes)) // UserA, B, Cのreferenceが返ってくる。 } } } }.eraseToAnyPublisher() } こうして返されるのがRefenrenceの配列で、これらをまた画像の取得用のPublisherに変換するとPublisherの配列ができます。 そのPublisherの配列をforEachでそれぞれsubscribeしていくと以下のようになります。 Storage.storage().reference(withPath: "パス") .getReferences() .catch { _ -> Just<[StorageManager.Reference]> in return .init([]) } .sink { prefixes in prefixes .map { $0.getReferences() } .forEach { $0.catch { _ -> Just<[StorageManager.Reference]> in return .init([]) } .sink { [weak self] references in guard let self = self else { return } self.imageReferences.append(contentsOf: references) } .store(in: &self.cancellables) } } .store(in: &cancellables) 上記の書き方でもUserA、B、Cのパス全ての画像を取得してくることは可能です。 でもちょっと嫌ですよね。。。 ネスト深いし、.store(in: )を何度も呼んでいてどこで何をsubscribeしてるのか読みにくいです。 collectというOperatorを使おう 上記のコードはcollect()というOperatorを使うと解決できます。 LGTM! collect()は複数の出力を配列にして出力してくれるOperatorです。 ※0~10を出力するPublisherをIntの配列を出力するPublisherに変換する例 import Combine var cancellables = Set<AnyCancellable>() (0...10).publisher .handleEvents(receiveOutput: { print($0) }) .collect() .sink { print("\($0)") } .store(in: &cancellables) 結果 0 1 2 3 4 5 6 7 8 9 10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 途中までは数字を個別に出力していますが、collectを噛ませると全てまとめて配列にして出力してくれることが分かります。 これを先程のStorageReferenceを使うケースで使ってみました。 結論以下の形になります。 Storage.storage().reference(withPath: "パス") .getReferences() .catch { _ -> Just<[StorageManager.Reference]> in return .init([]) } .flatMap { $0.publisher // [StorageReference]の要素を出力するpublisherを作成 .flatMap { $0.getReferences() } // 配下の画像のreferenceを取得するAnyPublisherに変換 .collect() // collectで全ての出力をまとめて配列にするPublisherに変換 } .map { $0.flatMap { $0 } } // [[StorageReference]]として返ってくるのでflatMapで1次元配列にする。 .sink( receiveCompletion: { result in // なんかする }, receiveValue: { references in // なんかする } ) .store(in: &cancellables) コメントで説明しているように、StorageReferenceを個別にSinkするのではなく、Collectを使って出力を配列化して返すPublisherを作成し、flatMapで変換しています。 今回はcollectでまとめた出力が元々配列だったことで2次元配列になってしまったため、出力結果をflatMapにかけるmapを噛ませて(ややこしい。。。)1次元配列に直しています。 これで個別にPublisherをSinkしなくても結果をまとめてSubscribeできるようになりました。 まとめ Publisherの配列の出力をまとめるときはcollectが使える。 配列が多次元になる時はflatMapで1次元になおそう。 おまけ 毎回flatMapとcollectで変換するのも大変なのでArrayのextensionで変換したPublisherを作成できるようにしてみました。 extension Array where Element: StorageReference { func getReferences() -> AnyPublisher<[StorageReference], Error> { publisher .flatMap { $0.getReferences() } .collect() .map { $0.flatMap { $0 } } .eraseToAnyPublisher() } } Storage.storage().reference(withPath: "パス") .getReferences() .catch { _ -> Just<[StorageManager.Reference]> in return .init([]) } .flatMap { $0.getReferences() } .sink( receiveCompletion: { result in // なんかする }, receiveValue: { references in // なんかする } ) .store(in: &cancellables) だいぶスッキリしたと思います。 欲を言えばstorageを取得した時にprefixがあれば再帰的に画像を取得するようにしたかったんですが、難しくて諦めました。。。 アルゴリズムに強くなりたい。 元気がある時に再チャレンジしてみようと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift5][Combine] Publisherの配列から出力をまとめた配列を返すPublisherを作成する

TL;DR Publisherの配列があり、全てのPublisherの出力をまとめた配列を返すPublisherが欲しかったらcollect()を使おう。 結果が2次元配列になってしまう場合はflatMapで1次元化しよう。 モチベーション Publisherの配列から結果をまとめて返してくれるPublisherを作ろうと思いました。 例えばFirebaseのStorageからReferenceを取得する場面があるとします。 Referenceは以下のパスで画像を保存しています。 root └── Store // お店の画像を保存するパス ├── Header // ヘッダー画像のパス │ └──{ID} // 画像 └── Image // その他の画像のパス ├── UserA // Userごとのパス │ └──{ID} // 画像 ├── UserB │ └──{ID} └── UserC └──{ID} お店というエンティティに対し、ヘッダー画像とその他の画像があります。 その他の画像はいろんなユーザーが投稿できるためユーザーごとにディレクトリを分けています。 (この設計が正しいかどうかは分かりません。。。) Headerのようにreferenceから直下の画像を取得する時はこんな感じでReferenceを取得するFutureを作ってやれば可能です。 extension StorageReference { func getReferences() -> AnyPublisher<[StorageReference], Error> { Deferred { Future<[StorageReference], Error> { [weak self] promise in guard let self = self else { return } self.listAll { result, error in if let error = error { promise(.failure(error)) return } promise(.success(result.items)) } } }.eraseToAnyPublisher() } } // Usage Storage.storage().reference(withPath: "パス") .getReferences() .sink( receiveCompletion: { result in // なんかする }, receiveValue: { references in // なんかする } ) .store(in: &cancellables) けどImageのreferenceを取得してくるのはちょっとめんどくさいです。 単純にImage直下のReferenceを取得してきても、UserA,B,CのReferenceがPrefixとして取得されるだけなので、画像のReferenceは取得できません。 画像を取得するには取得したUserA、B、CのReferenceそれぞれに対して、listAllを呼ぶ必要があります。 forEachで愚直にやってみた場合 まずはImage配下から取得できるのがitemではなくprefixなので、prefixが取得できた時はPrefixを返すように先程のFutureを変更します。 func getReferences() -> AnyPublisher<[StorageReference], Error> { Deferred { Future<[StorageReference], Error> { [weak self] promise in guard let self = self else { return } self.listAll { result, error in if let error = error { promise(.failure(error)) return } if result.prefixes.isEmpty { promise(.success(result.items)) } else { promise(.success(result.prefixes)) // UserA, B, Cのreferenceが返ってくる。 } } } }.eraseToAnyPublisher() } こうして返されるのがRefenrenceの配列で、これらをまた画像の取得用のPublisherに変換するとPublisherの配列ができます。 そのPublisherの配列をforEachでそれぞれsubscribeしていくと以下のようになります。 Storage.storage().reference(withPath: "パス") .getReferences() .catch { _ -> Just<[StorageManager.Reference]> in return .init([]) } .sink { prefixes in prefixes .map { $0.getReferences() } .forEach { $0.catch { _ -> Just<[StorageManager.Reference]> in return .init([]) } .sink { [weak self] references in guard let self = self else { return } self.imageReferences.append(contentsOf: references) } .store(in: &self.cancellables) } } .store(in: &cancellables) 上記の書き方でもUserA、B、Cのパス全ての画像を取得してくることは可能です。 でもちょっと嫌ですよね。。。 ネスト深いし、.store(in: )を何度も呼んでいてどこで何をsubscribeしてるのか読みにくいです。 collectというOperatorを使おう 上記のコードはcollect()というOperatorを使うと解決できます。 collect()は複数の出力を配列にして出力してくれるOperatorです。 ※0~10を出力するPublisherをIntの配列を出力するPublisherに変換する例 import Combine var cancellables = Set<AnyCancellable>() (0...10).publisher .handleEvents(receiveOutput: { print($0) }) .collect() .sink { print("\($0)") } .store(in: &cancellables) 結果 0 1 2 3 4 5 6 7 8 9 10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 途中までは数字を個別に出力していますが、collectを噛ませると全てまとめて配列にして出力してくれることが分かります。 これを先程のStorageReferenceを使うケースで使ってみました。 結論以下の形になります。 Storage.storage().reference(withPath: "パス") .getReferences() .catch { _ -> Just<[StorageManager.Reference]> in return .init([]) } .flatMap { $0.publisher // [StorageReference]の要素を出力するpublisherを作成 .flatMap { $0.getReferences() } // 配下の画像のreferenceを取得するAnyPublisherに変換 .collect() // collectで全ての出力をまとめて配列にするPublisherに変換 } .map { $0.flatMap { $0 } } // [[StorageReference]]として返ってくるのでflatMapで1次元配列にする。 .sink( receiveCompletion: { result in // なんかする }, receiveValue: { references in // なんかする } ) .store(in: &cancellables) コメントで説明しているように、StorageReferenceを個別にSinkするのではなく、Collectを使って出力を配列化して返すPublisherを作成し、flatMapで変換しています。 今回はcollectでまとめた出力が元々配列だったことで2次元配列になってしまったため、出力結果をflatMapにかけるmapを噛ませて(ややこしい。。。)1次元配列に直しています。 これで個別にPublisherをSinkしなくても結果をまとめてSubscribeできるようになりました。 まとめ Publisherの配列の出力をまとめるときはcollectが使える。 配列が多次元になる時はflatMapで1次元になおそう。 おまけ 毎回flatMapとcollectで変換するのも大変なのでArrayのextensionで変換したPublisherを作成できるようにしてみました。 extension Array where Element: StorageReference { func getReferences() -> AnyPublisher<[StorageReference], Error> { publisher .flatMap { $0.getReferences() } .collect() .map { $0.flatMap { $0 } } .eraseToAnyPublisher() } } Storage.storage().reference(withPath: "パス") .getReferences() .catch { _ -> Just<[StorageManager.Reference]> in return .init([]) } .flatMap { $0.getReferences() } .sink( receiveCompletion: { result in // なんかする }, receiveValue: { references in // なんかする } ) .store(in: &cancellables) だいぶスッキリしたと思います。 欲を言えばstorageを取得した時にprefixがあれば再帰的に画像を取得するようにしたかったんですが、難しくて諦めました。。。 アルゴリズムに強くなりたい。 元気がある時に再チャレンジしてみようと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIScrollViewを使うときに注意することメモ(AutoLayout関係)

はじめに UIScrollViewを使うと急なButtonの追加やレイアウトの変更が起こったときに、画面からはみ出てしまってもスクロールすれば表示されるからひとまずいいか、となって安心できるのでUIScrollViewを下地に貼るのがおすすめらしいです。 実際サンプルアプリの改修をしているときにボタンが増えてレイアウトに困ったときに、ScrollView貼っとけばよかったなと思いました。 が、下地に貼る作業がちょっと難しいので手順のメモを残しておきます。 今回はUISclollViewでできるシンプルなスクロールできるページの実装とStackViewを用いた実践的(?)(というか自分で助かった実装)をやってみます。 目次 -実装1.シンプルなスクロールページ -実装2.シンプルなスクロールページをStackViewを使って実装 -実装3.便利だなと思ったUIScrollViewとStackViewの合わせ技 実装1.シンプルなスクロールページ 今回目指すのはこんな感じのviewです。 やっていきます。 Step1.UIScrollViewをviewの全面に貼る 右上の+ボタンからUIScrollViewをドラッグアンドドロップしてきたら、親viewに対して画像のように制約をつけていきます。 これで、UIScrollViewが全面に貼れたことになります。エラーが出てきますが気にしなくて大丈夫です。 Step2.UIScrollView内にViewを貼り付ける 先ほどと同じようにしてViewをドラッグアンドドロップして、制約をつけていきます。名前はContentsViewをしておいてください。 ContentsViewから同じ階層にあるContentLayoutGuideに向かってcontrollキーを押したままにょーんとドラッグアンドドロップしたらメニューが表示されます。 その中の4つの項目をcommandキーを押しながら選択します。画像のようになっていればOKです。 このとき今つけた制約一つ一つについてConstantの値が0になっていることを確認してください。 ついでに下のMultiplierの値も1になっていることを確認しましょう。(左側のメニューからConstraintsを選択して個々の項目についてAttributeInspecterで確認します。) 正直ここが一番大切なのではという気もします。制約を貼り付けたはいいがエラーが出ていたり、一見予想したレイアウトになってなくて心が折られることが多いですが、だいたいconstantかMultiplierの値が適当なものになっているのが原因だと思います。 詳しく知りたい方は下のリンクのApple公式ドキュメントが参考になると思います。 https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html#//apple_ref/doc/uid/TP40010853-CH9-SW1 そして同じようにしてContentsViewとFrameLayoutGuideに対しても制約をつけていきます。 チェックするのは Equal Heights の項目です。(縦スクロールさせたい時はEqual Widths) Step3.ContentsView内にPage1になるviewを貼り付ける 先ほどと同じようにしてViewをドラッグアンドドロップします。名前はPage1にしておきます。Page1とContentsViewの間につける制約は TopとBottom、Leading です。画像のようにチェックします。 また、Page1とFrameLayoutGuideの間には EqualWidths をつけておきます。 Step4.ContentsView内にPage2になるViewを貼り付ける。 これも先ほどとほぼ同様ですが、Page2とContentsViewの間の制約は TopとBotttomとTrailing です。 そしてPage2とFrameLayoutGuideの間には EqualWidths の制約をつけます。 Step5.Page1とPage2を水平方向に並べる。 Page1とPage2の間に制約をつけます。controllキーを押しながらドラッグアンドドロップして、 Horizontal Spacing にチェックをつけます。 このとき今つけた制約を左のメニューから選択して(constantとMultiplierの値を確認するときみたいに) 右側のFirst ItemとSecond Itemの値を確認してください。画像のように Page1.TrailingとPage2.Leading がつながっていれば大丈夫です。 Step6.完成! 最後につけた制約のconstantの値とMultiplierの値を見直してみてください。 スクロールできるはずです。 あとはPageの背景色を変えてみたりしてわかりやすくするのもいいかもしれません。 実装2.上と同じものをStackViewを使って作る。 Step1.UIScrollViewをviewの全面に貼る ここは先ほどと全く同じです。 Step2.UIScrollView内にHorizontalStackViewを貼り付ける ここも先ほどのViewを貼り付ける作業と全く同じです。StackViewの名前をConentsViewと変更しておきます。 StackView内にViewを貼り付ける際にStackViewの利点が明らかになります。 Step3.ContentsView内にPage1になるviewを貼り付ける やることは一緒なのでviewをContentsViewまでドラッグ&ドロップします。名前をPage1としておきます。 そしてPage1とFrameLayoutGuideとの間に制約をつけます。 チェックを入れる項目はEqualWidthsとEqualHeightsです。画像のようになっていれば大丈夫です。 ここでConstantとMultiplierの値を確認しておきましょう。 Step4.ContentsView内にPage2になるViewを貼り付ける。 ここまでと同じようにviewをドラッグアンドドロップします。 そしてPage1でやったのと同じようにPage2もFrameLayoutGuideとの間に EqualWidths、EqualHeights の項目にチェックを入れます。 最後にConstantとMultiplierの値を確認して。。。 Step5.完成! StackViewを使わない方より少ない手順で出来上がりましたね。 実際使用したConstraintsもこれくらい少なく済みました。 左が実装2のStackViewを用いたもの、右が実装1のものです。 とこんな感じでUIScrollViewの実装をします。 実装3.便利だなと思ったUIScrollViewとStackViewの合わせ技 こんな感じにいくつかScrollViewの使い方をまとめたサンプルアプリを作っている途中、ボタンを追加するときにいちいち今までの制約を外して動かして制約つけて。。。とやるのは手間がかかるので、ボタンを追加したときに自動でいい感じに並んでくれたらいいのになということを考えました。 というわけでボタンを並べたときにViewからはみ出てしまってもスクロールできるようにしておく方法を実装していきます。 Step1.UIScrollViewをViewの全面に貼る 省略 Step2.UIScrollView内にViewを貼り付ける 省略 ContentsViewという名前にしておきます。同じです。 Step3.ContentsView内にVerticalStackViewを貼る。 StackViewにつける制約はHorizontallyinContainerと、ContantsViewに対してTopとBottomをつけます。画像のようになっていれば大丈夫です。 Step4.StackView内にボタンを置きまくる。 6個くらいボタンを配置します。ボタン同士の間隔はAttributeInspectorのSpacingという項目で変更します。150くらいにしておきます。 最後に一番上のボタンとNavigation Barの間が狭いので間隔を開けます。 StackViewのTopのConstrainのconstantを100とします。同様にBottomのConstantも100にしておきます。 画像を参考にしてみてください。 Step5.完成! こういう風にしておけばボタンを追加するときにStackViewに入れるだけで、きちんとUIに表示されるようになるので楽チンです。もっと細かいレイアウトを要求される場合はここまで単純ではないかもしれませんが、うまくScrollViewやStackViewを使うことで改修が楽になると考えられますね。 最後に 間違っている点や、ここはこうした方が良いなどありましたら教えてくださるとありがたいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

?UIScrollViewを使うときに注意することメモ(AutoLayout関係)

はじめに UIScrollViewを使うと急なButtonの追加やレイアウトの変更が起こったときに、画面からはみ出てしまってもスクロールすれば表示されるからひとまずいいか、となって安心できるのでUIScrollViewを下地に貼るのがおすすめらしいです。 実際サンプルアプリの改修をしているときにボタンが増えてレイアウトに困ったときに、ScrollView貼っとけばよかったなと思いました。 が、下地に貼る作業がちょっと難しいので手順のメモを残しておきます。 今回はUISclollViewでできるシンプルなスクロールできるページの実装とStackViewを用いた実践的(?)(というか自分で助かった実装)をやってみます。 目次 -実装1.シンプルなスクロールページ -実装2.シンプルなスクロールページをStackViewを使って実装 -実装3.便利だなと思ったUIScrollViewとStackViewの合わせ技 実装1.シンプルなスクロールページ 今回目指すのはこんな感じのviewです。 やっていきます。 Step1.UIScrollViewをviewの全面に貼る 右上の+ボタンからUIScrollViewをドラッグアンドドロップしてきたら、親viewに対して画像のように制約をつけていきます。 これで、UIScrollViewが全面に貼れたことになります。エラーが出てきますが気にしなくて大丈夫です。 Step2.UIScrollView内にViewを貼り付ける 先ほどと同じようにしてViewをドラッグアンドドロップして、制約をつけていきます。名前はContentsViewをしておいてください。 ContentsViewから同じ階層にあるContentLayoutGuideに向かってcontrollキーを押したままにょーんとドラッグアンドドロップしたらメニューが表示されます。 その中の4つの項目をcommandキーを押しながら選択します。画像のようになっていればOKです。 このとき今つけた制約一つ一つについてConstantの値が0になっていることを確認してください。 ついでに下のMultiplierの値も1になっていることを確認しましょう。(左側のメニューからConstraintsを選択して個々の項目についてAttributeInspecterで確認します。) 正直ここが一番大切なのではという気もします。制約を貼り付けたはいいがエラーが出ていたり、一見予想したレイアウトになってなくて心が折られることが多いですが、だいたいconstantかMultiplierの値が適当なものになっているのが原因だと思います。 そして同じようにしてContentsViewとFrameLayoutGuideに対しても制約をつけていきます。 チェックするのは Equal Heights の項目です。(縦スクロールさせたい時はEqual Widths) Step3.ContentsView内にPage1になるviewを貼り付ける 先ほどと同じようにしてViewをドラッグアンドドロップします。名前はPage1にしておきます。Page1とContentsViewの間につける制約は TopとBottom、Leading です。画像のようにチェックします。 また、Page1とFrameLayoutGuideの間には EqualWidths をつけておきます。 Step4.ContentsView内にPage2になるViewを貼り付ける。 これも先ほどとほぼ同様ですが、Page2とContentsViewの間の制約は TopとBotttomとTrailing です。 そしてPage2とFrameLayoutGuideの間には EqualWidths の制約をつけます。 Step5.Page1とPage2を水平方向に並べる。 Page1とPage2の間に制約をつけます。controllキーを押しながらドラッグアンドドロップして、 Horizontal Spacing にチェックをつけます。 このとき今つけた制約を左のメニューから選択して(constantとMultiplierの値を確認するときみたいに) 右側のFirst ItemとSecond Itemの値を確認してください。画像のように Page1.TrailingとPage2.Leading がつながっていれば大丈夫です。 Step6.完成! 最後につけた制約のconstantの値とMultiplierの値を見直してみてください。 スクロールできるはずです。 あとはPageの背景色を変えてみたりしてわかりやすくするのもいいかもしれません。 実装2.上と同じものをStackViewを使って作る。 Step1.UIScrollViewをviewの全面に貼る ここは先ほどと全く同じです。 Step2.UIScrollView内にHorizontalStackViewを貼り付ける ここも先ほどのViewを貼り付ける作業と全く同じです。StackViewの名前をConentsViewと変更しておきます。 StackView内にViewを貼り付ける際にStackViewの利点が明らかになります。 Step3.ContentsView内にPage1になるviewを貼り付ける やることは一緒なのでviewをContentsViewまでドラッグ&ドロップします。名前をPage1としておきます。 そしてPage1とFrameLayoutGuideとの間に制約をつけます。 チェックを入れる項目はEqualWidthsとEqualHeightsです。画像のようになっていれば大丈夫です。 ここでConstantとMultiplierの値を確認しておきましょう。 Step4.ContentsView内にPage2になるViewを貼り付ける。 ここまでと同じようにviewをドラッグアンドドロップします。 そしてPage1でやったのと同じようにPage2もFrameLayoutGuideとの間に EqualWidths、EqualHeights の項目にチェックを入れます。 最後にConstantとMultiplierの値を確認して。。。 Step5.完成! StackViewを使わない方より少ない手順で出来上がりましたね。 実際使用したConstraintsもこれくらい少なく済みました。 左が実装2のStackViewを用いたもの、右が実装1のものです。 とこんな感じでUIScrollViewの実装をします。 実装3.便利だなと思ったUIScrollViewとStackViewの合わせ技 こんな感じにいくつかScrollViewの使い方をまとめたサンプルアプリを作っている途中、ボタンを追加するときにいちいち今までの制約を外して動かして制約つけて。。。とやるのは手間がかかるので、ボタンを追加したときに自動でいい感じに並んでくれたらいいのになということを考えました。 というわけでボタンを並べたときにViewからはみ出てしまってもスクロールできるようにしておく方法を実装していきます。 Step1.UIScrollViewをViewの全面に貼る 省略 Step2.UIScrollView内にViewを貼り付ける 省略 ContentsViewという名前にしておきます。同じです。 Step3.ContentsView内にVerticalStackViewを貼る。 StackViewにつける制約はHorizontallyinContainerと、ContantsViewに対してTopとBottomをつけます。画像のようになっていれば大丈夫です。 Step4.StackView内にボタンを置きまくる。 6個くらいボタンを配置します。ボタン同士の間隔はAttributeInspectorのSpacingという項目で変更します。150くらいにしておきます。 最後に一番上のボタンとNavigation Barの間が狭いので間隔を開けます。 StackViewのTopのConstrainのconstantを100とします。同様にBottomのConstantも100にしておきます。 画像を参考にしてみてください。 Step5.完成! こういう風にしておけばボタンを追加するときにStackViewに入れるだけで、きちんとUIに表示されるようになるので楽チンです。もっと細かいレイアウトを要求される場合はここまで単純ではないかもしれませんが、うまくScrollViewやStackViewを使うことで改修が楽になると考えられますね。 最後に 間違っている点や、ここはこうした方が良いなどありましたら教えてくださるとありがたいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【algorithm】アルゴリズムまとめ

はじめに algorithmの記事をまとめていこうと思います algorithm おわりに アルゴリズム楽しい!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【algorithm】単純交換法、単純選択法、単純挿入法

はじめに 今回は整列(ソート)アルゴリズムについてまとめていこうと思います! 単純交換法(バブルソート) 全ての隣り合った値を比べていき、小さい方が前に移動するように交換する方法 総当たりてきなアルゴリズムなので、大量データには向かない var nums = [5, 3, 7, 9, 1, 2, 4, 8, 6] for i in 0..<nums.count { for j in stride(from: nums.count-1, to: i, by: -1) { if nums[j] < nums[j-1] { let t = nums[j] nums[j] = nums[j-1] nums[j-1] = t } } } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9] まず、前からソート済みを省いた要素にアクセスするためfor文と後ろから二つを比較して行くためのfor文の二つのfor文が必要です。 stride(from: nums.count-1, to: i, by: -1)で配列を逆順でアクセスできます。 あとは、二つを比較していき、左の方が大きい場合は交換のアルゴリズムを使って交換します。交換のアルゴリズム 単純選択法(選択ソート) 最小値を探して先頭から順番に並べていく方法 最小値をを入れる位置を「選択」し、最小値と交換していくので、選択ソートという 最小値と交換のアルゴリズムを使います。最小値のアルゴリズム, 交換のアルトリズム var nums = [5, 3, 7, 9, 1, 2, 4, 8, 6] for i in 0..<nums.count { var min = nums[i] var k = i for j in (i+1)..<nums.count { if min > nums[j] { min = nums[j] k = j } } let t = nums[i] nums[i] = nums[k] nums[k] = t } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9] ポイントは、最小値の位置を表す変数も用意してその位置を保存しておくことです。 単純挿入法(挿入ソート) データを抜き出して正しい位置に挿入していく方法 ・整列していない部分から挿入する値を順番に一つずつ取り出す繰り返しをする ・その中で取り出した整列した部分のどこに挿入すればよいかをみていく繰り返しをする var nums = [5, 3, 14, 15, 7, 9, 13, 1, 2, 4, 12, 11, 8, 6, 10] for i in 1..<nums.count { let t = nums[i] var insert = 0 for j in stride(from: i-1, through: 0, by: -1) { if nums[j] > t { nums[j+1] = nums[j] } else { insert = j + 1 break } } nums[insert] = t } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] おわりに 今回は単純なソート方法を紹介しました!次回は少し難しいシェルソートとクイックソートについてまとめていこうと思います!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【algorithm】単純交換法、単純選択法、単純挿入法、シェルソート

はじめに 今回は整列(ソート)アルゴリズムについてまとめていこうと思います! 単純交換法(バブルソート) 全ての隣り合った値を比べていき、小さい方が前に移動するように交換する方法 総当たりてきなアルゴリズムなので、大量データには向かない var nums = [5, 3, 7, 9, 1, 2, 4, 8, 6] for i in 0..<nums.count { for j in stride(from: nums.count-1, to: i, by: -1) { if nums[j] < nums[j-1] { let t = nums[j] nums[j] = nums[j-1] nums[j-1] = t } } } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9] まず、前からソート済みを省いた要素にアクセスするためfor文と後ろから二つを比較して行くためのfor文の二つのfor文が必要です。 stride(from: nums.count-1, to: i, by: -1)で配列を逆順でアクセスできます。 あとは、二つを比較していき、左の方が大きい場合は交換のアルゴリズムを使って交換します。交換のアルゴリズム 単純選択法(選択ソート) 最小値を探して先頭から順番に並べていく方法 最小値をを入れる位置を「選択」し、最小値と交換していくので、選択ソートという 最小値と交換のアルゴリズムを使います。最小値のアルゴリズム, 交換のアルトリズム var nums = [5, 3, 7, 9, 1, 2, 4, 8, 6] for i in 0..<nums.count { var min = nums[i] var k = i for j in (i+1)..<nums.count { if min > nums[j] { min = nums[j] k = j } } let t = nums[i] nums[i] = nums[k] nums[k] = t } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9] ポイントは、最小値の位置を表す変数も用意してその位置を保存しておくことです。 単純挿入法(挿入ソート) データを抜き出して正しい位置に挿入していく方法 ・整列していない部分から挿入する値を順番に一つずつ取り出す繰り返しをする ・その中で取り出した整列した部分のどこに挿入すればよいかをみていく繰り返しをする var nums = [5, 3, 14, 15, 7, 9, 13, 1, 2, 4, 12, 11, 8, 6, 10] for i in 1..<nums.count { let t = nums[i] var insert = 0 for j in stride(from: i-1, through: 0, by: -1) { if nums[j] > t { nums[j+1] = nums[j] } else { insert = j + 1 break } } nums[insert] = t } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] シェルソート 間隔を空けて挿入ソートを行い、その間隔をだんだん狭めていく方法 特徴は ・少ないデータであればソートは高速になる ・大雑把でも順番に並んでいる部分が多いと挿入ソートは高速にソートできる 考え方として、 ・グループ分けの間隔を半分にしていく繰り返しを行う ・その中で各グループに対して挿入ソートを行う var nums = [5, 3, 14, 15, 7, 9, 13, 1, 2, 4, 12, 11, 8, 6, 10] var step = Int(nums.count / 2) while step > 0 { for i in step..<nums.count { let t = nums[i] var j = i while j >= step { if nums[j-step] > t { nums[j] = nums[j-step] j -= step } else { break } } nums[j] = t } step = Int(step / 2) } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] おわりに 今回は単純なソート方法を紹介しました!次回は少し難しいクイックソートについてまとめていこうと思います!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

protocolを使ってenumが特定のcaseを持っていることを表す

APIレスポンスをenumにマッピングする際、当てはまるものがなければunknownにしたい時がありました。 愚直に書くと以下のようになります。 enum Enum: String { case a case b case unknown } extension Enum { init(rawValue: RawValue) { self = Self(rawValue: rawValue) ?? .unknown } } しかし、以下のようなProtocolに準拠すると特定のcaseが存在することを明示的に表すことができます。 protocol HasUnknown { static var unknown: Self { get } } enum Enum: String, HasUnknown { case a case b case unknown } そして、以下のようにprotocol extensionでinitを宣言しておけば、enumごとにマッピング処理を書く必要がなくなります。 extension HasUnknown where Self: RawRepresentable, RawValue: Equatable { init(rawValue: RawValue) { self = Self(rawValue: rawValue) ?? .unknown } } 以下のようにinitができます let a = Enum(rawValue: "a") // case a let unknown = Enum(rawValue: "others") // case unknown Revision 2021/05/09 @takehito-koshimizu さんのアドバイスでマッピング処理がシンプルになり、CaseIterableに準拠する必要もなくなりました。ありがとうございます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む