- 投稿日:2021-08-29T22:11:54+09:00
enum Associated Values と Raw Values を両立させる
背景 Swift の enum においては、 Associated Values と Raw Values を利用することができます。 これらはそれぞれ単独での利用は問題ないのですが、両方を同時に利用するとすると、コンパイルエラーとなってしまいます。 それぞれ単独での利用は OK Associated Values enum Screen { case home(tabIndex: Int) case search case setting } Raw Values enum Screen: String { case home = "Home" case search = "Search" case setting = "Setting" } 同時利用が NG Associated Values & Raw Values enum Screen: String { case home(tabIndex: Int) // コンパイルエラー "Enum with raw type cannot have cases with arguments" case search case setting } コンパイルエラーを回避して、 Associated Values と Raw Values を両立させようというのが今回の内容です。 ユースケースとしては、次のように iOS アプリの画面を enum で表現することを想定します。 ホーム/検索/設定の3画面をタブ( UITabbar を利用した下タブ)で構成 ホーム画面は可変個のタブストリップ(上タブ)で子画面を保持 Associated Values を利用 スクリーンビュー測定のため、 enum 毎に特定の文字列を定義 Raw Valuesを利用 どうするのか Associated Values はそのまま。 Raw Values を protocol RawRepresentable に適合させることで実現します。 For any enumeration with a string, integer, or floating-point raw type, the Swift compiler automatically adds RawRepresentable conformance. When defining your own custom enumeration, you give it a raw type by specifying the raw type as the first item in the enumeration’s type inheritance list. You can also use literals to specify values for one or more cases. とあるように、 Raw Values 自体は Swift コンパイラが protocol RawRepresentable に自動的に適合してくれた結果なので、これを自分で実装することとなります。 具体的には、以下のイニシャライザとプロパティ実装が要求されます。 ( associatedtype RawValue については、型推論が効くため省略可能です) init?(rawValue: Self.RawValue) var rawValue: Self.RawValue 上述の想定ユースケースにおける enum Screen を例にした実装だと、次の通りです。 enum Screen: RawRepresentable { case home(tabIndex: Int) case search case setting init?(rawValue: String) { switch rawValue { case Screen.search.rawValue: self = .search case Screen.setting.rawValue: self = .setting default: if let regex = try? NSRegularExpression(pattern: "^Home_Tab(.+)$"), let match = regex.firstMatch(in: rawValue, range: NSRange(location: 0, length: rawValue.count)), let range = Range(match.range(at: 1), in: rawValue), let tabIndex = Int(rawValue[range]) { // `case home(tabIndex: Int)` の rawValue フォーマットに合致 self = .home(tabIndex: tabIndex) } else { return nil } } } var rawValue: String { switch self { case .home(let tabIndex): return "Home_Tab\(tabIndex)" case .search: return "Search" case .setting: return "Setting" } } } メリット・デメリット メリットとしては、 Associated Values と Raw Values を両立可能という結果そのものがあります。 もう少し具体的な例だと、 Raw Values 実装に依存した既存コードが大量にあるプロジェクトにおいて、 enum に Associated Values を利用した case を追加する必要性が発生した場合に、影響を最小限に抑えることが可能となってきます。 デメリットとしては、 init?(rawValue: Self.RawValue)の実装が煩雑になる enum の表現力 / 記述容易性が失われる といったことが挙げられます。 特に後者において、通常の enum 実装では、列挙子と rawValue の一意性を少ない記述で簡潔に表現でき、コンパイラの静的解析による保証もついてくる enum Screen: String { case home = "Home" case search = "Search" case setting = "Search" // コンパイルエラー "Raw value for enum case is not unique" } のですが、自分で protocol RawRepresentable への適合を実装するとなると、このあたりの恩恵が失われてしまいます。
- 投稿日:2021-08-29T14:30:53+09:00
iOSアーキテクチャ勉強
MVC(Cocoa MVC) View 表示、入出力 Controller 入力に基づくModelとViewの制御 Model データの保持、通信 ViewControllerがViewとControllerの役割を両方持つため肥大化しがち Massive View Controllerとか呼ばれる 普通言われるMVCとはModelがデータの変更をViewに通知すると言う点で違う MVP MVCと違い入力を受け付けるのがControllerではなくViewになるViewControllerはViewとして扱われる - Supervising Controller - データバインディングによりViewがModelを監視し、データ更新があれば表示も更新する - データを加工せずに表示できるのでPresenterの負担を軽減できるがModelがViewのレイアウトに依存する - Passive View - 必ずmodelとViewの間はPresenterが介すのでViewとModelが分離するがPresenterの負担増 MVVM ViewとViewModelをデータバインディング。MVPと違うのはMVPはデリゲートやインターフェイスで処理を移譲・更新処理を行うのに対して、MVVMではデータバインディングでViewとViewModelを紐づける クリーンアーキテクチャ GUIアーキテクチャ MVC,MVP,MVVMなどUIに関するロジック クリーンアーキテクチャはUIに関するロジック(プレゼンテーション層)とシステムに関するロジック(ドメイン)を分離するのが目的 3階層システム プレゼンテーション層(ユーザーインターフェース層)html,swift, ページの表示、ページ間の遷移、ユーザーが入力した内容をビジネスロジック層に受け渡す処理などを行う、 ビジネスロジック層(アプリケーション層)php,python,ruby プレゼンテーション層、データアクセス層がすること以外が全部ここ。 データアクセス層 sql CRUDのしょりをおこなう よくMVCとかMVVMとかは全部プレゼンテーション層の話 参考 :https://qiita.com/os1ma/items/7a229585ebdd8b7d86c2 https://www.ibm.com/jp-ja/cloud/learn/three-tier-architecture https://qiita.com/navitime_tech/items/602d3286f23952ae0149#mvp-model-view-presenter
- 投稿日:2021-08-29T08:10:48+09:00
SwiftUIのボタンで少し凝ったハイライト表現をするには?
ButtonStyleによるハイライト表現 SwiftUI においてボタンのタップ中のハイライト表現をしたい場合、ButtonStyle を作り、configuration.isPressed によってハイライト時の見た目に変更します。以下の例ですと、タップ中はボタンの背景色が変わるようになっています。 struct CustomButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .padding() .foregroundColor(Color.white) // タップ中かどうかで背景色を変更する .background(configuration.isPressed ? Color.red : Color.blue) .cornerRadius(4) } } Button(action: {}, label: { Text("Button") }) // 作ったボタンスタイルを適用する .buttonStyle(CustomButtonStyle()) 少し凝ったハイライト表現をするには? しかし、タップ中かどうかによってボタンの見た目を大胆に変えたい場合、上記のような ButtonStyle による方法だと限界があります。そこで、次のような感じでタップ中かどうかによってボタンの label を簡単に変更できるボタンコンポーネントを作ってみました。 HighlightableButton( action: { // Nothing to do. }, label: { configuration in CharacterView(character: .wolverine, isPressed: configuration.isPressed) }) isPressed の値によって View の中身を自由自在に変更できるので、こんな凝ったハイライト表示も実現できてしまいます。MARVEL API を使って SwiftUI の勉強中だったので、こんなこともしてみたくなってしまいました。 今回作った HighlightableButton は次のように内部で ButtonStyle を使いつつ、label については外部から設定できるようにしています。NavigationLink 版もありますよ。 import SwiftUI struct HighlightableButton<Label>: View where Label: View { private let action: () -> Void private let label: (ButtonStyleConfiguration) -> Label init(action: @escaping () -> Void, @ViewBuilder label: @escaping (ButtonStyleConfiguration) -> Label) { self.label = label self.action = action } var body: some View { Button(action: action, label: {}) .buttonStyle(HighlightableButtonStyle { config in label(config) }) } } struct HighlightableNavigationLink<Label, Destination>: View where Label: View, Destination: View { private let destination: () -> Destination private let label: (ButtonStyleConfiguration) -> Label init(destination: @escaping () -> Destination, @ViewBuilder label: @escaping (ButtonStyleConfiguration) -> Label) { self.destination = destination self.label = label } var body: some View { NavigationLink(destination: destination, label: {}) .buttonStyle(HighlightableButtonStyle { config in label(config) }) } } private struct HighlightableButtonStyle<Label>: ButtonStyle where Label: View { private var label: (ButtonStyleConfiguration) -> Label init(@ViewBuilder label: @escaping (ButtonStyleConfiguration) -> Label) { self.label = label } func makeBody(configuration: Configuration) -> some View { label(configuration) } }
- 投稿日:2021-08-29T07:09:22+09:00
【iOS】Metal Best Practicesの解説(7) 画面スケール
Metal Best Practicesは、iOS/MacOS/tvOSのAPIであるMetalを用いた設計のベストプラクティスガイドです。 本稿では、何回かに分けてこのガイドを読み解き、コード上での実験を交えて解説していきます。 読んでそのまま理解できそうなところは飛ばしますので、原文を読みながら原文のガイドとしてご利用下さい。 また、iOSの記事なので他のOS(MacOS, tvOS)についての記載は割愛します。 他の記事の一覧は、初回記事よりご覧下さい。 Native Screen Scale (iOS and tvOS) (画面スケール) ベストプラクティス: ターゲットディスプレイの正確なピクセルサイズでドローアブルをレンダリングします。 不要なレンダリングや、テクスチャーサンプリングによるパフォーマンス低下を避けるために、ドローアブルのピクセルサイズはデバイスの物理的なピクセルサイズと同じにしましょう、という話です。 前提として、iOSにおいては、画面の描画のサイズは基本的にポイント単位で指定しますが、Metalではピクセル単位で指定するということがあります。 そのため、ドローアブルのピクセルサイズを計算するためには、UIScreenのプロパティから、ネイティブの画面サイズをnativeBoundsから、スケールファクター(1ポイントが何ピクセルに対応するか)をnativeScaleから取得する必要があります。 なお、MTKViewを用いた場合、ドロアーブルのサイズはビューのサイズと常に一致することが保証されるので、このあたりの計算を自分でする必要はありません。 結論 ドローアブルの領域が広ければ、レンダリングに時間がかかることは自明なので、まぁそうだろうなというところです。自明なことなので、今回は、サンプルコードを作って試していません。 軽く手元で試してみた限りでは、同じ1000x1000の画像を1000x1000のドローアブルに描画するのと、2000x2000のドローアブルに描画する(余った領域は空白になる)のでは、やはり1000x1000の方が高速に描画していました。 最後に iOSを使った3D処理やAR、ML、音声処理などの作品やサンプル、技術情報を発信しています。 作品ができたらTwitterで発信していきますのでフォローをお願いします? Twitterは作品や記事のリンクを貼っています。 https://twitter.com/jugemjugemjugem Qiitaは、iOS開発、とくにARや機械学習、グラフィックス処理、音声処理について発信しています。 https://qiita.com/TokyoYoshida Noteでは、連載記事を書いています。 https://note.com/tokyoyoshida Zennは機械学習が多めです。 https://zenn.dev/tokyoyoshida
- 投稿日:2021-08-29T01:34:52+09:00
イニシャライザとは? -init()
イニシャライザとは、 型(クラス、構造)のインスタンスを初期化(僕のイメージでは再設定)するメソッドのこと class User { let name: String // funcが不要(initキーワードのみ) init(name: String) { // 全てのプロパティを初期化する前にインスタンスメソッドを実行することはできない // printName() → コンパイルエラー self.name = name printName() // OK } // インスタンスメソッド func printName() { print(name) } } let user1 = User.init(name: "hoge") // 呼び出し時のメソッド名が省略可能 let user2 = User(name: "hoge") イニシャライザの順序 イニジャライザは、プロパティの初期化をしてからインスタンスメソッド(インスタンスプロパティにアクセス、変更するためのインスタンスの目的に関連した機能としてサポートするメソッド)を実行するという順序が存在します。 参考文献 ・https://qiita.com/shtnkgm/items/8b7979fc84a3cc065238