LoginSignup
0
0

[SwiftUI + MVVM]メンテナンス・機能拡張しやすいコードの書き方の検討(メモ書き)

Last updated at Posted at 2023-06-17

この記事の内容

SwiftUI と MVVM を題材に、メンテナンス・機能を拡張しやすいコードの書き方を考える
※ あくまで個人のメモ書き程度と捉えてください

開発するアプリ

機能

  • とある鉄道会社のとある路線の駅を一覧で表示する
  • 駅番号・駅名(日本語)・駅名(英語)を表示する

GitHub

画面数

  • 1つのみ

画面イメージ

ファイル

ファイル名 内容 備考
Station_Kobe.json 駅に関する情報を記録
JSONLoader.swift JSON ファイルからデータを取得・解析 SwiftUI チュートリアルで JSON ファイルからデータを取得・解析するコードを流用した
Station.swift データモデル(駅)を定義
Line.swift 路線に関する情報を列挙型で定義 神戸線のラインカラーは青
StationViewModel.swift JSON ファイルから取得したデータを画面に渡す
KobeLineApp.swift アプリを実行すると、まず実行する(エントリーポイント)
ContentView.swift アプリの画面を定義
StationNumberView.swift 駅番号に関するビューを定義
StationView.swift 駅名に関するビューを定義

クラス図

クラス図を観察するとわかること
矢印 (→) の方向が一方方向であること

  • JSONLoaderStationViewModel のことを知らない
  • StationViewModelKobeLineApp のことを知らない ... 以下同様

考えること

  1. 現在は JSON ファイルからデータを取得し、解析している。データの取得先をデータベースに変更する際に発生する問題はあるか
  2. 現在は 神戸線のみ JSON ファイルからデータを取得している。仮に、宝塚線・京都線のデータも表示したいとする。その拡張にその変更に耐えられるか

現時点での反省点

  • アプリの名前は KobeLineApp にしない方が良かった。理由:今後、宝塚線・京都線のデータも表示するかもしれないから

コード

Station_Kobe.json
[
    {
        "id": "kobe01",
        "numbering": "01",
        "line": "Kobe",
        "name": "大阪梅田",
        "nameEnglish": "Osaka-umeda",
        "latitude": "34.705326",
        "longitude": "135.498398",
        "isFavorite": true
    },
    {
        "id": "kobe02",
        "numbering": "02",
        "line": "Kobe",
        "name": "中津",
        "nameEnglish": "Nakatsu",
        "latitude": "34.709851",
        "longitude": "135.492499",
        "isFavorite": false
    },
    {
        "id": "kobe03",
        "numbering": "03",
        "line": "Kobe",
        "name": "十三",
        "nameEnglish": "Juso",
        "latitude": "34.72049",
        "longitude": "135.482198",
        "isFavorite": true
    },
    {
        "id": "kobe04",
        "numbering": "04",
        "line": "Kobe",
        "name": "神崎川",
        "nameEnglish": "Kanzakigawa",
        "latitude": "34.732349",
        "longitude": "135.472782",
        "isFavorite": false
    },
    {
        "id": "kobe05",
        "numbering": "05",
        "line": "Kobe",
        "name": "園田",
        "nameEnglish": "Sonoda",
        "latitude": "34.751903",
        "longitude": "135.448145",
        "isFavorite": false
    },
    {
        "id": "kobe06",
        "numbering": "06",
        "line": "Kobe",
        "name": "塚口",
        "nameEnglish": "Tsukaguchi",
        "latitude": "34.752968",
        "longitude": "135.416376",
        "isFavorite": false
    },
    {
        "id": "kobe07",
        "numbering": "07",
        "line": "Kobe",
        "name": "武庫之荘",
        "nameEnglish": "Mukonoso",
        "latitude": "34.751596",
        "longitude": "135.393428",
        "isFavorite": false
    },
    {
        "id": "kobe08",
        "numbering": "08",

        "line": "Kobe",
        "name": "西宮北口",
        "nameEnglish": "Nishinomiya-kitaguchi",
        "latitude": "34.745956",
        "longitude": "135.356597",
        "isFavorite": true
    },
    {
        "id": "kobe09",
        "numbering": "09",
        "line": "Kobe",
        "name": "夙川",
        "nameEnglish": "Shukugawa",
        "latitude": "34.742262",
        "longitude": "135.328128",
        "isFavorite": true
    },
    {
        "id": "kobe10",
        "numbering": "10",

        "line": "Kobe",
        "name": "芦屋川",
        "nameEnglish": "Ashiyagawa",
        "latitude": "34.736568",
        "longitude": "135.300937",
        "isFavorite": false
    },
    {
        "id": "kobe11",
        "numbering": "11",
        "line": "Kobe",
        "name": "岡本",
        "nameEnglish": "Okamoto",
        "latitude": "34.729151",
        "longitude": "135.275827",
        "isFavorite": true
    },
    {
        "id": "kobe12",
        "numbering": "12",
        "line": "Kobe",
        "name": "御影",
        "nameEnglish": "Mikage",
        "latitude": "34.724559",
        "longitude": "135.252254",
        "isFavorite": false
    },
    {
        "id": "kobe13",
        "numbering": "13",
        "line": "Kobe",
        "name": "六甲",
        "nameEnglish": "Rokko",
        "latitude": "34.719652",
        "longitude": "135.23429",
        "isFavorite": false
    },
    {
        "id": "kobe14",
        "numbering": "14",
        "line": "Kobe",
        "name": "王子公園",
        "nameEnglish": "Ojikoen",
        "latitude": "34.710262",
        "longitude": "135.218527",
        "isFavorite": false
    },
    {
        "id": "kobe15",
        "numbering": "15",
        "line": "Kobe",
        "name": "春日野道",
        "nameEnglish": "Kasuganomichi",
        "latitude": "34.703103",
        "longitude": "135.205507",
        "isFavorite": false
    },
    {
        "id": "kobe16",
        "numbering": "16",
        "line": "Kobe",
        "name": "神戸三宮",
        "nameEnglish": "Kobe-sannnomiya",
        "latitude": "34.693143",
        "longitude": "135.192847",
        "isFavorite": true
    }
]
JSONLoader.swift
import Foundation

class JSONLoader {
    private static var instance: JSONLoader = JSONLoader()
    private init(){}
    public static func getInstance() -> JSONLoader { return JSONLoader.instance }
    
    func load<T: Decodable>(_ filename: String) throws -> T {
        let data: Data

        guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            throw JSONLoaderError.cantFindFile
        }

        do {
            data = try Data(contentsOf: file)
        } catch {
            throw JSONLoaderError.cantLoadFile
        }

        do {
            let decoder = JSONDecoder()
            return try decoder.decode(T.self, from: data)
        } catch {
            throw JSONLoaderError.cantParseFile
        }
    }
    
    enum JSONLoaderError:Error {
        case cantFindFile
        case cantLoadFile
        case cantParseFile
    }
}
Station.swift
import Foundation
import SwiftUI

struct Station: Hashable, Codable,Identifiable {
        let id, numbering: String
        let line: Line
        let name, nameEnglish, latitude, longitude: String
        let isFavorite: Bool
    
        enum Line: String, Codable {
            case kobe = "Kobe"
        }
}
Line.swift
import Foundation

enum Line: String {
    case Kobe
    
    var description: String {
        switch(self){
        case .Kobe: return "神戸線"
        }
    }
    
    var color: Color {
        switch(self){
        case .Kobe: return .blue
        }
    }
}
StationViewModel.swift
import Foundation
import SwiftUI

class StationViewModel: ObservableObject {
    private var loader: JSONLoader = JSONLoader.getInstance()
    @Published var stations: [Station] = []
    
    init(){
        do {
            stations = try loader.load("Station_Kobe.json")
        } catch {
            stations = []
        }
    }
}
KobeLineApp.swift
import SwiftUI

@main
struct KobeLineApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView(viewModel: StationViewModel())
        }
    }
}
ContentView.swift
import SwiftUI

struct ContentView: View {
    @ObservedObject var viewModel: StationViewModel
    init(viewModel: StationViewModel) {
        self.viewModel = viewModel
    }
    
    var body: some View {
        VStack {
            NavigationView {
                listView // ネストが深くなるため listView に分けた
            }
        }
    }
    
    private var listView: some View {
        List(viewModel.stations){ station in
            HStack {
                StationNumberView(station: station)
                StationView(station: station)
            }
        }
    }
}
StationView.swift
import SwiftUI

struct StationView: View {
    var station: Station
    var body: some View {
        VStack(alignment: .leading) {
            Text(station.name)
                .font(.body)
                .fontWeight(.bold)
            Text(station.nameEnglish)
                .font(.caption)
        }
    }
}
StationNumberView.swift
import SwiftUI

struct StationNumberView: View {
    var station:Station
    var body: some View {
        ZStack{
            Circle()
                .stroke(style: StrokeStyle(lineWidth: 2))
                .frame(width: 30,height: 30)
                .foregroundColor(Line.Kobe.color)
            VStack(spacing: 0) {
                Text("HK")
                    .font(.caption2)
                    .fontWeight(.bold)
                    .foregroundColor(Line.Kobe.color)
                    .padding(0)
                Text(station.numbering)
                    .font(.caption2)
                    .fontWeight(.bold)
                    .foregroundColor(Line.Kobe.color)
                    .padding(0)
            }
        }
    }
}

参考資料

SwiftUI チュートリアル

阪急電鉄

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0