sql >> データベース >  >> NoSQL >> MongoDB

RealmとSwiftUIを使用して1週間でチャートトッピングアプリを作成する方法

    エルデンリングクエストトラッカーの構築

    Skyrimが大好きでした。私はそれを再生して再生するのに数百時間を楽しく過ごしました。それで、最近新しいゲームについて聞いたとき、2020年代のSkyrim 、私はそれを買わなければなりませんでした。したがって、私の物語は、ジョージR.R.マーティンからのストーリーガイダンスを備えた大規模なオープンワールドRPGであるエルデンリングから始まります。

    ゲームの最初の1時間以内に、私はソウルズの残忍なゲームがいかにあり得るかを学びました。私は興味深い崖の洞窟に忍び込み、死体を取り戻すことができなかったほど内部で死にました。

    私はすべてのルーンを失いました。

    エレベーターに乗ってシオフラ川に降りたとき、私は畏怖の念を抱きましたが、最も近い恵みの場所から遠く離れたところに、恐ろしい死が私を待っていたことがわかりました。私は再び死ぬ前に勇敢に逃げました。

    幽霊のような人物や魅力的なNPCに会い、数行の会話で私を誘惑しました…必要になるとすぐに忘れてしまいました。

    10/10、強くお勧めします。

    特にEldenRingについての1つのことは、私を苛立たせました-クエストトラッカーがありませんでした。良いスポーツである私は、iPhoneでNotesドキュメントを開きました。もちろん、それだけでは十分ではありませんでした。

    RPGプレイスルーの詳細を追跡するのに役立つアプリが必要でした。 App Storeには、私が探していたものと実際に一致するものはなかったので、どうやら私はそれを書く必要があるでしょう。 Shattered Ringと呼ばれ、現在AppStoreで入手できます。

    技術的な選択

    日中、私はRealmSwiftSDKのドキュメントを作成しています。私は最近、Realm用のSwiftUIテンプレートアプリを作成して、ログインフローを備えたSwiftUIスターターテンプレートを開発者に提供しました。 Realm Swift SDKチームは、SwiftUI機能を着実に出荷してきました。これにより、おそらく偏見のある意見では、アプリ開発の非常に単純な出発点になりました。

    私は超高速で構築できるものが欲しかったのです。一部はアプリを作成する代わりにEldenRingのプレイに戻ることができ、一部は他のアプリを打ち負かして、誰もがEldenRingについて話している間に市場に出すことができました。このアプリを作成するのに何ヶ月もかかりませんでした。昨日欲しかった。 Realm+SwiftUIはそれを可能にするつもりでした。

    データモデリング

    ゲーム内のクエストを追跡したいと思っていました。クエストモデルは簡単でした:

    class Quest: Object, ObjectKeyIdentifiable {
        @Persisted(primaryKey: true) var _id: ObjectId
        @Persisted var name = ""
        @Persisted var isComplete = false
        @Persisted var notes = ""
    }
    

    本当に必要だったのは、名前、クエストの完了時に切り替えるブール値、メモフィールド、および一意の識別子だけでした。

    しかし、ゲームプレイについて考えたとき、私はクエストだけでなく、場所も追跡したいと思っていました。私は偶然に遭遇しました-そして私が死に始めたときすぐに-おそらく面白いノンプレイヤーキャラクター(NPC)と素晴らしい戦利品を持っていた非常に多くのクールな場所。場所をクリアしたのか、それとも逃げ出したのかを追跡できるようにしたかったので、ギアと能力が向上したら、後で戻ってチェックすることを忘れないでください。そこで、ロケーションオブジェクトを追加しました。

    class Location: Object, ObjectKeyIdentifiable {
        @Persisted(primaryKey: true) var _id: ObjectId
        @Persisted var name = ""
        @Persisted var isCleared = false
        @Persisted var notes = ""
    }
    

    うーん。それはクエストモデルによく似ていました。本当に別のオブジェクトが必要でしたか?それから私が訪れた初期の場所の1つであるエルレ教会について考えました。そこにはスミスアンビルがありました。ギアを改善するために実際に何もしていませんが、アップグレードを行うためにどこかに行きたいときに、将来どの場所にスミスアンビルがあったかを知ることは良いことかもしれません。そこで、別のブール値を追加しました:

    @Persisted var hasSmithAnvil = false

    それから私はその同じ場所にどのように商人がいたかについて考えました。将来、ある場所に商人がいるかどうかを知りたいと思うかもしれません。そこで、別のブール値を追加しました:

    @Persisted var hasMerchant = false

    素晴らしい!ソートされたロケーションオブジェクト。

    しかし…何か他のものがありました。私はNPCからこれらすべての興味深い話を聞き続けました。そして、クエストを完了したときに何が起こりましたか?報酬を集めるためにNPCに戻る必要がありますか?それには、誰が私にクエストを与えたのか、そして彼らがどこにいるのかを知る必要があります。すべてを結び付ける3番目のモデルであるNPCを追加するときが来ました。

    class NPC: Object, ObjectKeyIdentifiable {
        @Persisted(primaryKey: true) var _id: ObjectId
        @Persisted var name = ""
        @Persisted var isMerchant = false
        @Persisted var locations = List<Location>()
        @Persisted var quests = List<Quest>()
        @Persisted var notes = ""
    }
    

    素晴らしい!これでNPCを追跡できるようになりました。何が展開されるかを確認するのを待っている間、メモを追加して、これらの興味深いストーリーのヒントを追跡するのに役立てることができました。クエストと場所をNPCに関連付けることができます。このオブジェクトを追加した後、これが他のオブジェクトを接続するオブジェクトであることが明らかになりました。 NPCは場所にいます。しかし、オンラインで読んだところ、ゲーム内でNPCが移動することがあるため、場所は複数のエントリ、つまりリストをサポートする必要があることがわかりました。 NPCはクエストを提供します。しかし、私が最初に会ったNPCは私に複数のクエストを与えたので、それもリストであるはずです。あなたが最初にゲームに入ったとき、粉砕された墓地のすぐ外にあるヴァレは、「恵みの糸をたどって」そして「城に行ってください」と私に言いました。そうです、並べ替えました!

    これで、SwiftUIプロパティラッパーでオブジェクトを使用して、UIの作成を開始できます。

    SwiftUIビュー+レルムの魔法のプロパティラッパー

    すべてがNPCにぶら下がっているので、私はNPCビューから始めます。 @ObservedResults プロパティラッパーを使用すると、これを簡単に行うことができます。

    struct NPCListView: View {
        @ObservedResults(NPC.self) var npcs
    
        var body: some View {
            VStack {
                List {
                    ForEach(npcs) { npc in
                        NavigationLink {
                            NPCDetailView(npc: npc)
                        } label: {
                            NPCRow(npc: npc)
                        }
                    }
                    .onDelete(perform: $npcs.remove)
                    .navigationTitle("NPCs")
                }
                .listStyle(.inset)
            }
        }
    }
    

    これで、すべてのNPCのリストを反復処理でき、自動onDeleteができました。 NPCを削除するアクションであり、Realmの.searchableの実装を追加できます 検索とフィルタリングを追加する準備ができたとき。そして、それを私のデータモデルに接続するのは基本的に1行でした。 Realm + SwiftUIが素晴らしいと言いましたか?ロケーションとクエストで同じことを行うのは簡単で、アプリユーザーは任意のパスを介してデータに飛び込むことができます。

    次に、私のNPC詳細ビューは@ObservedRealmObjectで機能する可能性があります NPCの詳細を表示し、NPCを簡単に編集できるようにするプロパティラッパー:

    struct NPCDetailView: View {
        @ObservedRealmObject var npc: NPC
    
        var body: some View {
            VStack {
                HStack {
                Text("Notes")
                     .font(.title2)
                     Spacer()
                if npc.isMerchant {
                    Image(systemName: "dollarsign.square.fill")
                }
            Spacer()
            Text($npc.notes)
            Spacer()
            }
        }
    }
    

    @ObservedRealmObjectのもう1つの利点 $を使用できるということでした クイック書き込みを開始するための表記。メモフィールドは編集可能です。ユーザーはタップしてメモを追加するだけで、レルムは変更を保存するだけです。個別の編集ビューや、メモを更新するための明示的な書き込みトランザクションを開く必要はありません。

    この時点で、動作するアプリがあり、簡単に出荷できたはずです。

    しかし…私は考えました。

    オープンワールドRPGゲームで私が気に入った点の1つは、それらをさまざまなキャラクターとして、さまざまな選択肢で再生することでした。だから多分私は別のクラスとしてエルデンリングを再生したいと思います。または、これは特にElden Ringトラッカーではなかったかもしれませんが、RPGゲームの追跡に使用できるかもしれません。私のD&Dゲームはどうですか?

    複数のゲームを追跡したい場合は、モデルに何かを追加する必要がありました。ゲームやプレイスルーのようなコンセプトが必要でした。

    データモデルの反復

    this の一部であるNPC、ロケーション、クエストを網羅するオブジェクトが必要でした プレイスルーなので、他のプレイスルーから分離しておくことができます。では、それがゲームだったらどうなるでしょうか。

    class Game: Object, ObjectKeyIdentifiable {
        @Persisted(primaryKey: true) var _id: ObjectId
        @Persisted var name = ""
        @Persisted var npcs = List<NPC>()
        @Persisted var locations = List<Location>()
        @Persisted var quests = List<Quest>()
    }
    

    大丈夫!素晴らしい。これで、このゲームに含まれるNPC、場所、クエストを追跡し、他のゲームと区別できるようになりました。

    Gameオブジェクトは簡単に想像できましたが、@ObservedResultsについて考え始めたとき 私の見解では、それはもう機能しないことに気づきました。 @ObservedResults 特定のオブジェクトタイプのすべての結果を返します。したがって、このゲームのNPCのみを表示したい場合は、ビューを変更する必要があります。*

    • Swift SDKバージョン10.24.0では、@ObservedResultsでSwiftクエリ構文を使用する機能が追加されました。 、whereを使用して結果をフィルタリングできます パラメータ。私は間違いなくこれを将来のバージョンで使用するためにリファクタリングしています! Swift SDKチームは、新しいSwiftUIグッズを着実にリリースしています。

    おー。また、このゲームのNPCを他のゲームのNPCと区別する方法が必要です。うーん。今こそ、バックリンクを検討するときかもしれません。 Realm Swift SDK Docsで洞窟探検した後、これをNPCモデルに追加しました:

    @Persisted(originProperty: "npcs") var npcInGame: LinkingObjects<Game>

    これで、NPCをゲームオブジェクトにバックリンクできます。しかし、残念ながら、今では私の見解はより複雑になっています。

    モデル変更のためのSwiftUIビューの更新

    今はオブジェクトのサブセットだけが必要なので(これは@ObservedResultsの前でした 更新)、リストビューを@ObservedResultsから切り替えました @ObservedRealmObjectへ 、ゲームの観察:

    @ObservedRealmObject var game: Game

    今でも、ゲーム内でNPC、ロケーション、クエストを追加および編集するためのクイックライティングのメリットを享受していますが、リストコードを少し更新する必要がありました。

    ForEach(game.npcs) { npc in
        NavigationLink {
            NPCDetailView(npc: npc)
        } label: {
            NPCRow(npc: npc)
        }
    }
    .onDelete(perform: $game.npcs.remove
    

    それでも悪くはありませんが、考慮すべき別のレベルの関係です。そして、これは@ObservedResultsを使用していないためです 、.searchableのレルム実装を使用できませんでした 、しかしそれを自分で実装する必要があります。大したことではありませんが、より多くの作業が必要です。

    凍結されたオブジェクトとリストへの追加

    さて、この時点まで、私は動作するアプリを持っています。これをそのまま発送できます。 Realm Swift SDKプロパティラッパーがすべての作業を行うため、すべてがシンプルです。

    しかし、私は自分のアプリにもっと多くのことをしてもらいたかったのです。

    NPCビューからロケーションとクエストを追加し、それらをNPCに自動的に追加できるようにしたかったのです。そして、クエストビューからクエスト提供者を表示して追加できるようにしたかったのです。そして、ロケーションビューからNPCを表示してロケーションに追加できるようにしたかったのです。

    これらすべてにリストへの多くの追加が必要でした。オブジェクトを作成した後、クイック書き込みでこれを実行しようとすると、うまくいかないことに気付きました。オブジェクトを手動で渡し、追加する必要があります。

    私が欲しかったのは、次のようなことをすることでした。

    func addLocationToNpc(npc: NPC, game: Game, locationName: String) {
        let realm = try! Realm()
        let thisLocation = game.locations.where { $0.name == locationName }.first!
    
        try! realm.write {
            npc!.locations.append(thisLocation)
        }
    }
    

    これは、新しい開発者として私には完全には明らかではなかったことが私の邪魔になり始めた場所です。これまで、スレッド化やフリーズされたオブジェクトについて実際に何もする必要はありませんでしたが、クラッシュが発生し、エラーメッセージによってこれがそれに関連していると思われました。幸い、フリーズしたオブジェクトを解凍して他のスレッドで操作できるようにするためのコード例を書いたことを思い出したので、ドキュメントに戻りました。今回は、フリーズしたオブジェクトをカバーするスレッドページに戻りました。 (私がMongoDBに参加してから、Realm Swift SDKチームが追加したその他の改善点-やった!)

    ドキュメントにアクセスした後、私は次のようなものを持っていました:

    func addLocationToNpc(npc: NPC, game: Game, locationName: String) {
        let realm = try! Realm()
        Let thawedNPC = npc.thaw()
        let thisLocation = game.locations.where { $0.name == locationName }.first!
    
        try! realm.write {
            thawedNPC!.locations.append(thisLocation)
        }
    }
    

    それは正しく見えましたが、それでもクラッシュしていました。しかし、なぜ? (これは、ドキュメントでより完全なコード例を提供しなかったために自分自身を呪ったときです。このアプリで作業することで、いくつかの領域でドキュメントを改善するためのチケットが確実に作成されました!)

    フォーラムで洞窟探検をし、偉大なオラクルGoogleに相談した後、誰かがこの問題について話しているスレッドに出くわしました。結局、追加しようとしているオブジェクトだけでなく、追加しようとしているものも解凍する必要があります。これは経験豊富な開発者には明らかかもしれませんが、しばらくの間私をつまずかせました。だから私が本当に必要だったのは次のようなものでした:

    func addLocationToNpc(npc: NPC, game: Game, locationName: String) {
        let realm = try! Realm()
        let thawedNpc = npc.thaw()
        let thisLocation = game.locations.where { $0.name == locationName     }.first!
        let thawedLocation = thisLocation.thaw()!
    
        try! realm.write {
            thawedNpc!.locations.append(thawedLocation)
        }
    }
    

    素晴らしい!問題が解決しました。これで、オブジェクトの追加(および最終的には削除)を手動で処理するために必要なすべての機能を作成できました。

    その他はすべてSwiftUIです

    この後、アプリを作成するために学ばなければならなかったのは、フィルターの方法、フィルターをユーザーが選択できるようにする方法、独自のバージョンの.searchableを実装する方法など、SwiftUIだけでした。 。

    私がナビゲーションで行っていることには、最適とは言えないことが確かにいくつかあります。私がまだやりたいUXの改善がいくつかあります。そして、私の@ObservedRealmObject var game: Gameを切り替えます @ObservedResultsに戻る 新しいフィルタリング機能を使用すると、これらの改善のいくつかに役立ちます。しかし、全体として、Realm Swift SDKプロパティラッパーにより、このアプリの実装は私でもできるほど簡単になりました。

    合計で、私は2つの週末と数週間の平日の夜にアプリを作成しました。おそらくその時のある週末は、リストへの追加の問題に悩まされ、アプリのWebサイトを作成し、すべてのスクリーンショットをApp Storeに送信し、それに伴うすべての「ビジネス」のものを取得しました。インディーアプリ開発者。

    しかし、私が、私の名前の前に1つのアプリを持っている経験の浅い開発者であり、私のリードからのフィードバックがたくさんあれば、ShatteredRingのようなアプリを作成できることをお伝えします。また、SwiftUI + Realm Swift SDKのSwiftUI機能を使用すると、非常に簡単になります。 SwiftUIクイックスタートをチェックして、それがいかに簡単かを確認する良い例を確認してください。


    1. MongoDB $ ifNull

    2. フィルタされた配列アイテムだけでMongoDBのオブジェクトを取得する必要があります

    3. POSTリクエストからのデータストリームをGridFS、express、mongoDB、node.jsに保存する

    4. 2つの列を互いに一意にすることはできますか?または、redisで複合主キーを使用しますか?