LoginSignup
4
5

More than 1 year has passed since last update.

SwiftUI Tutorial の応用

Last updated at Posted at 2022-03-03

内容

  • SwiftUI Tutorial の応用

開発環境

ハードウエア

項目
PC MacBook Air(M1,2020) メモリ:16GB ストレージ:1TB
実機 iPhoneSE(2nd Generation 128GB iOS 15.3.1)
PCと実機を接続する USB-C Digital AV Multiportアダプタ

ソフトウエア

項目
言語 Swift 5.5.2
IDE Xcode Ver 13.2.1
その他 Visual Studio Code Ver 1.64.2

バージョン管理

GitHub - 実際にコードを確認できます

アプリの内容

画面

  • 駅一覧画面
  • 駅詳細画面

機能

駅一覧画面

  • 路線選択タブで路線を切替

  • お気に入りのみ表示

駅詳細画面

  • お気に入り機能

  • 隣の駅を表示

    • ただし大阪梅田駅は、次の駅のみ(宝塚・神戸三宮・京都河原町方向)を表示
    • ただし宝塚駅、神戸三宮駅および京都河原町駅は、前の駅のみ(大阪梅田方向)を表示

応用

画面上部にタブを追加

  • LNE / MoneryForward ME 等に実装されている
駅一覧画面
import SwiftUI

struct StationListView: View {
    @EnvironmentObject var modelData: ModelData
    @State private var showFavoriteOnly = false
    @State private var selection: Line = .Takarazuka

    var filteredStations: [Station] {
        modelData.stations.filter { station in
            (!showFavoriteOnly || station.isFavorite) && station.line == selection
        }
    }
    
    var body: some View {
        // GeometryReader を使うことがポイント                    
        GeometryReader { geometry in
            NavigationView {
                VStack(spacing: .zero) {
                    // LineTabView() で画面上部のタブを実装
                    LineTabView(selection: $selection, geometrySize: geometry.size)
                    
                    List{
                        Toggle(isOn: $showFavoriteOnly){
                            Text("お気に入りのみ表示")
                        }
                        
                        ForEach(filteredStations) { station in
                            NavigationLink {
                                StationDetail(station: station)
                            } label: {
                                StationRow(station: station)
                            }
                        }
                    }
                    Spacer()
                }
                .navigationBarTitle("駅一覧",displayMode: .inline)
            }
            .navigationViewStyle(.stack)
            // iPad のサイドビューでは画面上部タブのレイアウトが崩れるため、.navigationViewStyle を .stack に指定している
        }
    }
}

struct StationListView_Previews: PreviewProvider {
    static var previews: some View {
        StationListView()
            .environmentObject(ModelData())
    }
}

画面上部のタブ
import SwiftUI
import MapKit

struct LineTabView: View {
    
    @Binding var selection: Line
    var geometrySize: CGSize
    
    var body: some View {
        HStack(spacing: .zero) {
            ForEach(Line.allCases,id: \.self){ line in
                VStack {
                    Button {
                        self.selection = line
                        print("\(line.getLineName())を選択しました")
                    } label: {
                        Text("\(line.getLineName())")
                            .font(.body)
                            .fontWeight(.heavy)
                            .foregroundColor(self.selection == line ? line.getLineColor() : .gray)
                    }
                    .frame(width: geometrySize.width /  CGFloat(Line.allCases.count), height: 25)
                    
                    Rectangle()
                        .fill(self.selection == line ? line.getLineColor() : .gray)
                        .frame(width: geometrySize.width / CGFloat(Line.allCases.count), height: 2)
                }
                
            }
        }
    }
}

struct LineTabView_Previews: PreviewProvider {
    static var geometry: CGSize = CGSize(width: 200, height: 44)
    static var previews: some View {
        LineTabView(selection: .constant(.Takarazuka), geometrySize: geometry)
    }
}

その他

隣の駅を表示する処理のテストコードを書く & XCode でテストを実行

import XCTest
@testable import Sample

class SampleTests: XCTestCase {

    // 省略
    
    func testGetNextStation() throws {
        var count = 0
        let stations = ModelData().stations
        
        stations.map { station in
            print("\(count) : \(station)")
            count += 1
        }
        
        let takarazukaOsakaUmeda = stations[0]
        let takarazukaNakatsu = takarazukaOsakaUmeda.getNextStation()
        // 期待する結果とコードの処理結果が等しければ、SUCCESS
        XCTAssertEqual(takarazukaNakatsu?.id, "takarazuka02")
        
        let takarazukaTakarazuka = stations[18]
        XCTAssertEqual(takarazukaTakarazuka.getNextStation()?.id, nil)
        
        let kobeOsakaUmeda = stations[19]
        let kobeNakatsu = kobeOsakaUmeda.getNextStation()
        XCTAssertEqual(kobeNakatsu?.id, "kobe02")
        
        let kobeKobeSannomiya = stations[34]
        XCTAssertEqual(kobeKobeSannomiya.getNextStation()?.id, nil)
        
        let kyotoOsakaUmeda = stations[35]
        let kyotoJuso = kyotoOsakaUmeda.getNextStation()
        XCTAssertEqual(kyotoJuso?.id, "kyoto03")
        
        let kyotoKyotoKawaramachi = stations[62]
        XCTAssertEqual(kyotoKyotoKawaramachi.getNextStation()?.id, nil)
    }
    
    func testGetPreviousStation() throws {
        let stations = ModelData().stations
        
        let takarazukaOsakaUmeda = stations[0]
        XCTAssertEqual(takarazukaOsakaUmeda.getPreviousStation()?.id, nil)
        
        let takarazukaNakatsu = stations[1]
        XCTAssertEqual(takarazukaNakatsu.getPreviousStation()?.id, takarazukaOsakaUmeda.id)
        
        let takarazukaTakarazuka = stations[18]
        XCTAssertEqual(takarazukaTakarazuka.getPreviousStation()?.id, "takarazuka55")
    }

    // 省略
    
}

テスト実行結果
Test Successed

今後

  • おでかけスポット画面を追加
  • GitHub Actions を試してみる

感想

  • 慣れたら UIKit よりもラク
  • ダークモードが自動で反映されるのでラク
  • Enum を上手く使うと改修がラク

参考資料

SwiftUI Tutorial

画面上部のタブを実装する際に参考にした記事

4
5
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
4
5