エキスプレスカードが反応せず、メインカードが反応する場合、確認すること

iPhoneのウォレットに楽天カード(JCB)、Suica(定期券)、dCARD(mastercard)、VIEW CARD、MERCARI(mastercard)を登録し、支払いのメインカードは、楽天カードを利用して、エキスプレスカードは、Suica(定期券)を利用しています。

Suica(定期券)は、定期券有効期間外のものにチャージを行い、Suica支払いを利用しています。
ですが、JRの改札を通過する際にエキスプレスカードに設定しているSuica(定期券)が支払いに利用されず、支払いのメインカードである楽天カードが反応してしまい、改札で支払いが出来ない事象が頻発して困っていました。

この事象は回避できないものと諦めていたのですが、最近、以下の設定で様子を見ています。
検証期間は約一ヶ月程度ですが、JR改札でSuica(定期券)が反応しないという問題は起きていないので、改善があったのかもしれません。
同様の事象でお困りの方は、一度試されてはいかがでしょうか?

【設定内容】
「定期券有効期間外のSF利用」設定で、「SFを利用する」に設定する
使用期間後の定期券情報が残るSuicaは設定が必要とのこと。

【設定手順】

  1. Suicaアプリケーションを起動し、ログインする
  2. Suica一覧画面で「チケット購入・Suica管理」タブを選択する
  3. 「その他の設定」を選択する
  4. 「定期券有効期間外のSF利用」を「SFを利用する」

JRの改札で自動で支払いをSuicaにするには、エキスプレスカード設定を行っておくと言うのは理解できるのですが、上記の「定期券有効期間外のSF利用」設定は、誰を救うための機能なのでしょう?

また、このような事象を回避する情報をお持ちの方は是非お知らせ下さい。
では!

SwiftUIの個人的なメモ

SwiftUIもそろそろ業務で利用されるかと思い、少しずつ情報を整理しています。
というわけで個人的なメモを以下に記します。

2022年秋には、iOS16が提供されることにより市場のアプリのサポートバージョンは、iOS14、15、16が一般的になると思います。結果、SwiftUIもそろそろ積極的に採用されるとの判断です。
実際調べてみると、SwiftUIはiOSバージョンにより挙動の差異があるようです。
この点は、以下のページで詳しく報告されています。

iOS バージョン による SwiftUI の機能差分・制限まとめ

SwiftUIを採用するということは、UIKitからの移行ということだと改めて認識しました。現行のままでは、移行しきれないUIデザインというのもあるのでしょうね。
UIKitのあのコントロールは、SwiftUIではどのコントロールに該当するのかというのは、以下のページが参考になりました。

[Swift] SwiftUIのチートシート

SwiftUIサンプルコードを見ていて、新しく見つけたProperty Wrapperは以下の通り。

@mainエントリポイント
@PublishedCombineフレームワークが提供。Publisherを生成する。
$を付加して、Publisherにアクセスできる。
@ObservedObjectSwiftUIの監視対象となり、参照しているViewが自動的に再描画される。
@State変数の変更が監視され、変更時にViewが再描画される。
アクセスは宣言されたView内のみとなり、変更時には$を付与する必要がある。
@StateObject再描画されても値は保持される。
@EnvironmentObjectアプリ内でデータを共有する。
@Environment環境値を取得する。
SwiftUI Property Wrapper

リアクティブプログラミング手法では、UIとモデルのバインディングがどのように行うのか?ということが一つのテーマであったと思うが、SwiftUIはProperty Wrapperという形で提供している。その点も踏まえて、今後はSwiftUIと相性の良いアーキテクチャについても考えたい。

リアクティブプログラミング、Combine の個人的なメモ

時間があるとリアクティブプログラミングの調査は行っているのですが、毎回まとめる前に忘れてしまうということを繰り返していたので、今回はまとめてみた。
業務でいつも最新の手法のみを使っているわけではないので、習得してもまとめておかないと忘れちゃうんですよね。(笑)

リアクティブプログラミングの定義

非同期イベントの処理を宣言的な記述で書くプログラミング。

もう少しイメージしやすい表現だと、データ流れをイベントのストリームとして表現し、ストリームに応じた振る舞いをさせる考え方。

Combine概要

Combine が登場したのは 2019 年 6 月の WWDC。
iOS 13.0以上で使用できる。
オブジェクト間でイベントを伝える仕組みを提供する。

主要な要素と役割は、以下の通り。

  • Publisher – 時間の経過に沿ってイベントを伝達する役割
  • Operator – イベントを作成、もしくは処理する役割
  • Subscriber – Publisher から送信されたイベントを処理する役割
  • Cancellable – イベントをキャンセルできることを表現する役割
  • Subscription – Publisher と Subscriber の購読を表現する役割

subjectに対して、複数のsubscribeを行うことができる。
複数のsubscriptionを扱う場合、storeメソッドでまとめて保持する。
Publisherは、sendメソッドでイベントをpublishする。

参考: Apple Developer Documentation | Combine

Publisher イベントを発行する

Publisher を subscribe することでイベントを受信できる。
PassthroughSubject クラス
CurrentValueSubject クラス

Justとは 値を1回だけ出力するPublisher。
Futureは、通信処理などで利用していたコールバック処理をCombineで実装する場合に使用する。

Subscriber イベントを購読する

イベントを受信する(購読する)subscribe処理
assignメソッド
sinkメソッド

var subscriptions = Set()

Operator 流れてくる値を加工する

map
filter
compactMap   
combineLatest など

APIリクエストを行い、結果集合をメンバ変数に格納するサンプル

import Foundation
import Combine

final class SearchUserViewModel: ObservableObject {
    @Published var text = ""
    @Published var users = [User]()
    private lazy var subject = PassthroughSubject<Void, Never>()
    private var cancellables = Set<AnyCancellable>()
    private let searchUserModel: SearchUserModelProtocol
    
    init(searchUserModel: SearchUserModelProtocol) {
        self.searchUserModel = searchUserModel
        
        subject.sink(receiveValue: { _ in
            searchUserModel.fetchUser(query: self.text)
                .receive(on: RunLoop.main)
                .sink(receiveCompletion: { completion in
                    switch completion {
                    case .finished:
                        break
                    case .failure(let error):
                        print(error)
                    }
                }, receiveValue: {
                    self.users = $0
                })
                .store(in: &self.cancellables)
            })
            .store(in: &self.cancellables)
    }
    
    func searchButtonTapped() {
        subject.send()
    }
}

参考:【Combine】APIとの通信処理にCombineを取り入れる

Macに外部マイクを接続する場合、注意すること

Macでビデオ会議に参加する際などにAirPodsProもしくは、iPhoneに付属するヘッドホンを利用しています。利便性だけを考えればそれで良いのですが、今回は動画のアフレコを行うため、外部マイクを接続しようとして、認識されず、少しハマったのでメモしておきます。

まず最初に、Macのオーディオ端子は、4極(CTIA)という規格であるようです。

私が使用している「iMac (Retina 5K, 27-inch, Late 2014)」の仕様ページを参照しても「ヘッドフォンポート」としか記載されておらず、適当に4極プラグを購入したのですが、結果、4極(OMTP)であったため、認識されなかったようです。
4極プラグには2種類の規格があり、製品によっては規格が明記されていなかったり、外観上の違いがないことが混乱の原因のようです。

参考: iMac (Retina 5K, 27-inch, Late 2014) – 技術仕様

iMac (Retina 5K, 27-inch, Late 2014) と接続できたケーブル、変換アダプターは、以下の製品です。

  • YOUZIPPER ユージッパー
    P-34G [変換アダプター 3極→4極 150mm]
  • エレコム ELECOM
    DH-MMIP10WH [AUDIOケーブル 直径3.5ステレオミニ 1.0m ホワイト]
  • Ulanzi VM-Q1 ビデオマイク

プラグの規格については以下のページで解説されています。
参考: MacBookでイヤホンジャックにさすタイプの外付けマイクを使う際の注意点

ブログシステムを判断する方法

ブログシステムを判定するには、Chrome拡張を利用する

「Wappalyzer」などの拡張機能を利用する。

Word Pressテーマの確認方法は?

ページのソースを「wp-content/themes/」で検索し、テーマを判断する。
「wp-content/themes/○○/←がテーマの名前になる。

参考: 他サイトがどのWordPressテーマを使っているか調べる方法【最も簡単な調べ方・調査方法】

非同期処理で待ち合わせ制御を行っている場合、自由にキャンセル処理を行うにはNSOperationを選択した方がよい

APIリクエスト処理の不具合に対処していて、非同期処理について調べたのでメモしておきます。

iOSアプリをSwiftで実装している場合、いくつかの実装方法があり、お手軽に対処するためにGCD(Grand Central Dispatch)、DispatchGroup、DispatchSemaphoreを選択したのですが、以下のような【非同期処理の直列化】を行おうとした場合、処理途中で自由に処理をキャンセルできず(割り込めない)、結局、Foundation.Operation(NSOperation)で実装することとなりました。
キャンセル処理が出来ないことなんてあるのか?と思いますが、実際にキャンセル処理は処理完了時にしか出来ないようでした。

【非同期処理】
タスク1 --->
タスク2 --->
タスク3 --->
後処理  --->

【非同期処理の直列化】
タスク1 --->
タスク2     --->
タスク3         --->
後処理              --->

※ 後処理は全タスクの終了を待ち合わせてから処理する

参考にしたURLは以下の通り。

【参考URL】
Foundation.Operationの並列オペレーションがよくわからない人向けの説明
【Swift】非同期処理「Operation」について
swift で直列に非同期処理

Swift5 で文字列検索

久しぶりに Swift で文字列検索処理を書こうとして、 index(of:) を使おうとしたら、思うような結果が得られず、ネットを検索しても新旧の情報が入り交じっていて、よく分からなかったので、まとめた。

配列の最初に一致したインデックス番号を取得

index(of:) → firstIndex(of:)
メソッド名が変更されているようだが、結果を利用することができなかった。
文字列から文字を検索して、単純にインデックス番号が取得したかったのだが、そういうものではないらしい。 😂
Index とか Range とかの意味を整理する必要があって、数分で内容を理解して使用することができなかった。

文字列から文字を検索できるけど…

let str = "abcabc"
let firstIndex = str.firstIndex(of: "c")
print(type(of: firstIndex), firstIndex ?? 0)

出力結果 :
Optional Index(_rawBits: 131329)

Index型は、文字列の範囲指定に使用するものらしい…

文字列から文字を検索して、出現するインデックスを取得

以下の方法であれば、インデックス番号が取得できる。

let str = "abcabc"
let indexes = str.enumerated().compactMap { $0.element == "c" ? $0.offset : nil }
print(type(of: indexes), indexes)
// compactMap は配列のデータから nil を取り除きたい場合に使用する関数

出力結果 :
Array [2, 5]

NSRange で文字列中で指定した文字列が最初に出現する範囲を検索して取得

NSRange で取得するのが便利そう。

var str = "abcdef"
let range = NSString(string: str).range(of: "cde")
print("location:", range.location, "length:", range.length)

出力結果 :
location: 2 length: 3

文字の出現回数を正規表現で取得

こちらも便利です。

let string = "abcdefgabcdefg"
let pattern = NSRegularExpression.escapedPattern(for: "a")
let regex = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive)
print(regex.numberOfMatches(in: string, range: NSRange(0..<string.utf16.count)))

出力結果 :
2

Swiftの範囲についてもよく知らなかったので、まとめた。

これだけ知っていれば良いと思う。

Range0..<30 ~ 2
ClosedRange0…30 ~ 3
let range1 = 0..<3
print("type: \(type(of: range1))")
print("lower:\(range1.lowerBound) upper:\(range1.upperBound) count:\(range1.count)")
print("range1.contains(0) \(range1.contains(0))")
print("range1.contains(1) \(range1.contains(1))")
print("range1.contains(2) \(range1.contains(2))")
print("range1.contains(3) \(range1.contains(3))")
print("range1.contains(4) \(range1.contains(4))")

出力結果 :
type: Range<Int>
lower:0 upper:3 count:3
range1.contains(0) true
range1.contains(1) true
range1.contains(2) true
range1.contains(3) false
range1.contains(4) false
let range2 = 0...3
print("type: \(type(of: range2))")
print("lower:\(range2.lowerBound) upper:\(range2.upperBound) count:\(range2.count)")
print("range2.contains(0) \(range2.contains(0))")
print("range2.contains(1) \(range2.contains(1))")
print("range2.contains(2) \(range2.contains(2))")
print("range2.contains(3) \(range2.contains(3))")
print("range2.contains(4) \(range2.contains(4))")

出力結果 :
type: ClosedRange<Int>
lower:0 upper:3 count:4
range2.contains(0) true
range2.contains(1) true
range2.contains(2) true
range2.contains(3) true
range2.contains(4) false

R1000の走行動画に後方カメラを追加した件

趣味としてサーキット走行時の動画を撮影し、映像編集を楽しんでいるのですが、映像の画角が一つではどうしても飽きてくるので、バイク後方にもカメラを追加マウントすることにしました。
これで、MotoGPのようなアングルの撮影を実現することができます。
まずは、GoPro Hero Sessionを標準マウントで取り付けて撮影してみました。
(脱落防止ワイヤー付き)

意外に振動の影響を受けることなく、視聴に耐えうる動画が撮影できることが分かりました。
実際にバイクにカメラをマウントして撮影してみると、走行時の振動が影響して、視聴に耐えない場合もあるのですが、Sessionの軽さが功を奏したのか、このマウント方法でも綺麗に動画が撮影できるようです。

二つの映像のタイミングを同期したり、録音した音の処理など考慮すべき点はありますが、個人の趣味でもMotoGPのような映像編集が容易に行えるのは大変楽しいですね。

GSX-R1000R の ZiiX タイムアタッカーを移設しました

サーキット走行時のタイム計測に ZiiX タイムアタッカーを利用しているのですが、走行中はタイムを確認できないことが多く、また、フロントフォークのプリロード調整と干渉する位置にマウントしていたので、移設しました。
フロントフォークのプリロード調整をするたびにボルトのトルク管理など少し面倒な作業があることと、どうしても走行中に撮影しているメーターパネルと同時にタイム計測を撮影したかったというのが理由です。
ですが、メーターパネルと同じ画角に納められる位置にマウントするには利用できそうなボルトがなく、やむを得ずフロントフォークに重なる位置にマウントしていたのですが、偶然、車体のセンター付近に配置するステーが見つかったので、移設してみました。

サーキット走行は走行中はもちろんですが、そのあと反省点を確認するために動画をチェックする際にもタイム表示があると比較しやすいし、なによりも単純に楽しいんですよね。
ということで以下のように移設してみました。(私はショップにステーを持ち込み、依頼しただけですが…笑)

この位置にマウントする方法を知ったのは以下のページでの紹介です。

気ままにヘタレバイクライフ GSX-Rを快適化

使用したステーは以下の商品です。

アルキャンハンズ(ALCAN HANDS) クランプバーブラケット ブラック F00028F
デイトナ バイク用 メーターステー ミラー穴(M10ボルト) 左用 ブラック デジタルメーター 取付ステー 37059

結果、ステムホールを使ったスマホホルダーが取り付けられなくなりましたが、この1年間でR1000Rにスマホをマウントしなかったので、私の場合は大丈夫でしょう。
ショップの信頼できるメカさんによると、「タイムアタッカーの配線、ステーのマウントがハンドルをきった際、干渉しないよう考慮すると左側に配置するのがベスト」であったそうです。(実際はカラーを入れて、設置位置の高さを調整しています)
後は以下の GoPro 延長アームでカメラ位置を少し高く設置することでメーターパネルとタイム計測を同じ画角に納めることができました。

HSU アルミ延長アーム 延長アダプター GoPro全般のカメラ対応 アクションカメラ対応 ネジ付き (ブラック)

参考になれば幸いです。

Apple (AirPods Pro) 電話サポートについて

先日、愛用している AirPods Pro が不調になったのでサポート情報を調べたところ、メーカー保証として対応して頂けるとのこと。
具体的には、片側の AirPods Pro から異音 (「パチパチ」という音など) がして、再生音が聞き取れないという状態でした。

その際、電話番号を調べるのに苦労したので、URLをメモしておきます。
また、この修理サービスプログラムは、「対象となる AirPods Pro に対し、その最初の小売販売日から 2 年間適用されます。」とのことなので、お早めに確認されることをお勧めいたします。

AirPods Pro の音の問題に対する修理サービスプログラム
ページのリンクから自分のスマホにコールバックをされます。