えきねっとで予約した新幹線を往復割引で買う

こちらのブログで素晴らしくまとめられている。

https://asahinablog.com/archives/910




大事なことは(年末年始だと特にそうだが)、新幹線は予約を急ぐが焦って「乗車券」も一緒に、えきねっとで安易に申し込まないことだ。指定席だけ、えきねっとで確保できていれば、あとはどうにでもなる。

基本的に気をつけないと、往復割引は得られない。

えきねっとを介して、往復割引で新幹線に乗るのは以下の方法がある。

  1. えきねっと上で完結させるなら、上のブログを参照
  2. えきねっとで指定席だけ予約して、指定席の受け取りと乗車券の購入をみどりの窓口でする
  3. えきねっとで指定席だけ予約して、指定席の受け取りと乗車券の購入を販売機でする
    • まず、えきねっとで予約した指定席券を購入
    • つぎに、最初の画面に戻った状態で、乗車券を往復分、割引で買う
    • つまり、指定席券と乗車券は、別々の購入になることに注意したい




そもそも何故こういったややこしい問題が起きるか?

それは、鉄道は複雑だからだ。

じゃあ簡単になるのが良いかといえばそうではない。

その複雑さは歴史からなるものだし、そこから人知を感じられ、芸術性も読み取れるのである。

だからこれは、鉄道が複雑なことが問題ではなく、えきねっとの問題に感ずる。

えきねっと」は、その名前からすると、わかりやすく、親しみやすい砕けたユーザーのインターフェースを人に期待させる。

しかし、実際は複雑な鉄道システムを中途半端にしか簡略できていない。

わたしが思うに、えきねっとは2つモードがあれば良い。

  • イージーモード
    • とにかく簡単に、早く安く楽に移動できるような結果で買わせる
  • アドバンスモード
    • むきだしの複雑鉄道購入システムを、自分で操作できる。いくらでも細かいことができる。大変難しい。




結論

みどりの窓口で買いましょう。 天使はそこに。

Apple のサイトで使用している色

Apple のサイトからスクリーンショットを取り、配色を調査した。

テキストの色(黒)

f:id:daisuke-t-jp:20191128213209p:plain:w800

この色

  • HEX #1E2020
  • RGB 30, 32, 32

テキストの色(灰色)

f:id:daisuke-t-jp:20191128213213p:plain:w800

この色

  • HEX #898989
  • RGB 137, 137, 137

灰色の背景と、その上のテキストの色

f:id:daisuke-t-jp:20191128213217p:plain:w800

この色

背景

  • HEX #F3F3F3
  • RGB 243, 243, 243

テキスト

  • HEX #87878B
  • RGB 135, 135, 139

SwiftUI TextField の onEditingChanged() をハンドルする

SwiftUI TextField の onEditingChanged() を使ってみるサンプル。

今回のサンプルは

2つの TextField の入力を足し算するが TextField 編集中は、=(イコール)ボタンが押せない、という設定で試してみた。

初期状態 ボタンは押せる

編集開始した状態 ボタンは押せない

編集完了した状態 ボタンは押せる


コードはこんな感じ。

onEditingChangedchanged は編集開始(キーボード表示される)すると true になり、編集完了(キーボード閉じる)すると false になる。

onCommit は編集完了した時に呼ばれる感じだった。

TextField("$", value: $inputLHS, formatter: currencyFormatter, onEditingChanged: { changed in
                        print("onEditingChanged \(changed)")
                        self.onEditing = changed },
                      onCommit: {
                        print("onCommit") })

SwiftUI NavigationView の Push/Pop をコードで実行する

SwiftUI の NavigationView の Push(進む)と Pop(戻る)をユーザのアクションではなく、コードで(pragmatically)実行する調査をしたサンプル。

サンプルはボタンアクションで Push/Pop するが、この調査結果により、コードで Push/Pop 実行できることが可能と分かった。


Push する方

NavigationLink の isActive を false -> true にして、次の画面へ行く

struct ContentView: View {
    @State private var navigationLinkIsActive = false
    
    var body: some View {
        NavigationView() {
            VStack {
                Button(action: {
                    self.navigationLinkIsActive = true
                }, label: {
                    Text("Push")
                })
                
                NavigationLink(destination: SubView(),
                               isActive: $navigationLinkIsActive,
                               label: {
                    EmptyView()
                })
            }
        }
        .navigationBarBackButtonHidden(true)
    }
}

Pop する方

presentationMode を使用する

import SwiftUI

struct SubView: View {
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        VStack {
            Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }, label: {
                Text("Pop")
            })
        }
        .navigationBarBackButtonHidden(true)
    }
}

SwiftUI NavigationLink の有効/無効を切り替える

SwiftUI で NavigationLink の有効/無効の切替サンプルを作った。


ざっくりいうと

NavigationLink は hidden にしておいて直接操作させず、代わりに Button のアクションを一枚噛ませて NavigationLink がアクションする、という流れにするとできた。

NavigationLink の $isActive を初期は false にしておいて、ボタンのタッチ後に true すると NavigationLink の画面遷移が動作するが、そのボタンのタッチで true にするかどうかをここでは Toggle で切替して試している。

コードはこんな感じになる。

import SwiftUI

struct ContentView: View {
    
    @State var switchState: Bool = false
    @State var showsAlert = false
    @State var pushActive = false
    
    var body: some View {
        NavigationView {
            VStack {
                
                Toggle(isOn: $switchState) {
                    Text("Enable NavigationLink")
                }.frame(width: 260)
                
                Spacer().frame(height: 20)
                
                Button(action: {
                    guard self.switchState else {
                        self.showsAlert = true
                        return
                    }
                    
                    self.pushActive = true
                }) {
                    Text("Next")
                }
                .alert(isPresented: self.$showsAlert) {
                    Alert(title: Text("Switch is disable."))
                }
                
                NavigationLink(destination: ContentView2(),
                               isActive: self.$pushActive) {
                    EmptyView()
                }.hidden()

                Spacer().frame(height: 32)
            }
            .padding()
            .navigationBarTitle(Text("Index"))
            .navigationBarHidden(false)
            
        }
    }
}

SwiftUI KeyboardObserving を使用して TextField がキーボードに隠れる問題に対応する

入力したい TextField にタッチする→表示されたキーボードにより TextField が隠れる→入力しづらい

という問題。

これは Objective-C 時代からあり、よく対処されてきた問題なのだが SwiftUI の場合は、
GitHub にある KeyboardObserving という OSS で対応できそうだ。

デモ動作はこんなかんじ。

導入のしかた

CocoaPods / Swift Package Manager に対応している。

Xcode を使用して [Project]->[Swift Packages] から登録すると楽だろう。

導入が終わったあとは

import KeyboardObserving

して

struct YourView: View {

  var body: some View {
    VStack {
      // Your Content Here
    }
    .keyboardObserving()
  }
}

こんな感じに .keyboardObserving() を Superview に追加することで完了。

これで動作を試すと、キーボードの上に TextField が来るように自動で調整される。
とても簡単に導入できた、すごい。

ちなみに Keyboard KeyboardObservingView というクラスを使う方法もあるらしく、くわしくはプロジェクトの README を参照すること。

SwiftUI ビュー内に直接配置するビューが多くてビルドエラーになる場合

現象

Xcode 10.15.1 / Swift 5.1.2

ドキュメントのどこに記載があるかは不明だが SwiftUI でビュー内に配置できるサブビューは 10 個までのようだ。

たとえば

struct ContentView: View {
    var body: some View {
        VStack {
            Text("1")
            Text("2")
            Text("3")
            Text("4")
            Text("5")
            Text("6")
            Text("7")
            Text("8")
            Text("9")
            Text("10")
            // Text("11")
        }
    }
}

上記のコードでは Text ビューを 10 個配置していてビルドができる状態である。

しかし、コメントアウトされている Text("11") を有効にして 11 個ビューを配置しようとすると、ビルドエラーになる。

厄介なのが、この時に出力されるビルドエラーは、直接的にビューの限度数について示したものではないから、この制約に気付きにくい。

例えば以下のエラーが出ることを確認した。ここからビュー数の制限に気付くのは不自然である。

'Int' is not convertible to 'CGFloat?'
Argument passed to call that takes no arguments

回避方法

1. ビューごとにまとめる

ビューをグルーピングして、ひとつのビューに直接配置するビューを減らす。

struct ContentView: View {
    var body: some View {
        VStack {
            Group {
                Text("1")
                Text("2")
                Text("3")
                Text("4")
                Text("5")
            }
            
            VStack {
                Text("6")
                Text("7")
                Text("8")
                Text("9")
                Text("10")
            }
            Text("11")
        }
    }
}

2. ForEach を使用する

ForEach を使った記述で、直接配置する方法を避ける。

struct ContentView: View {
    var body: some View {
        VStack {
            ForEach(1..<12) { i in
                Text("\(i)")
            }
        }
    }
}