[Swift] 71 ローマ字を英数モードで打った時のリカバリー AppleScript

[Mac M2 Pro 12CPU, Sonoma 14.3.1]

ローマ字を英数モードで打ってしまった時に、かなキー2回押しでひらがなに変換するようにしました。SwiftとAppleScriptを組み合わせています。

ローマ字はヘボン式なのが難点です。”し”は”shi、”ち”はchiになります。また反応速度がやや遅く、入力モードを切り替えてすぐに入力すると最初の1文字を取りこぼします。

これでは常用できませんね。2回押しで一気に再変換まで出来るようにしたかったのですが、反応速度と合わせて今後の課題とします。

[AI] ローカルLLM検証アプリの製作 llama.cpp SwiftUI

[Mac M2 Pro 12CPU, Sonoma 14.3.1, clang++ 15.0.0]

ローカルLLMの性能を検証するアプリをSwiftUIで製作しました。

modelsディレクトリに入っているggufファイルを選択し、promptを入力してtxtファイル化、llama.cppのmainコマンドを作成します。コマンドはクリップボードに自動コピーされます。あとはターミナルにコマンドを貼り付けて実行し、responseを手動で右側のTextEditorにペーストしてtxtファイルにします。

promptファイルとresponseファイルのプレフィックスはコマンド作成時のタイムスタンプ(yymmdd_hhmmss)になっています。

今回はほとんどclaude-3 opusとgpt-4にコードを考えてもらいました。Buttonラベルの改行表示についてはclaude-3では埒が開かず、gpt-4がテキスト分割を提案して不本意ながら解決しました。SwiftUIに関してはclaude-3とgpt-4はほぼ互角といった印象です。

SwiftUIのmacOS版ではButtonのラベルを改行できないようです。iOS版では問題なく出来るはずですが、macOS版の後進性に若干引いています。

※claude-3利用状況
claude-3のAPIキーを取得して5日経ち、5ドルの無料分がほぼ無くなりました。私の用途ではgpt-4に対する優位性を体感できなかったので、今後は割安なgpt-4メインに戻ります。なおclaude-3 APIの支払いで個人はマイナンバーの入力を求められているため、登録は見送りました。Claude Pro($20/month)への登録もしません。

[AI] ローカルLLM検証 CodeLlama系日本語学習モデル その3 Mac M2 Proで動作確認

[Mac M2 Pro 12CPU, Sonoma 14.3.1, clang++ 15.0.0]
実行方法:llama.cpp

記事その1ではWindows11PCで検証しましたが、GGUF形式であればMacでも動作可能なので早速試してみました。Metalを使用しています。

4bit量子化したGGUF形式のモデルはサイズが4.08GBですから、RAM16GBでも問題なさそうです。

質問によっては無回答で終了することもあるものの、それなりに考えたプロンプトであればSwiftUIの簡単なコードについては正しい答えが返ってきました。

ただし量子化の影響なのか、下図のような簡潔な正答になることもあれば、勝手にチャットのようになったり、誤答を返すことも多く、結構不安定です。

量子化していないGGUF形式のモデルで検証したいところです。

./main -m models/ELYZA-japanese-CodeLlama-7b-instruct-q4_K_M.gguf --temp 1.0 -ngl 1 -t 10 -f ./prompt_jp.txt

参考サイト

[AI] Claude 3 vs GPT-4 / Swiftプログラミング補助 24/03/12

話題のClaude 3 (Sonnet)を即席評価しました。

またもやGPT-4の圧勝でした。2月に評価したGeminiと同じく読解力が不足しています。なお最上級のOpusはサブスクかAPIでないと使えないようです。

OpenAIが法人営業で苦戦しているとの記事を目にしました。用途によるとは言え、買い手はAIの実力を正当に評価できているのか、はなはだ疑問です。他社サービスは文章生成では優れているのでしょうか。

インフルエンサーもインプットとアウトプットの量だけで評価しているふしがあり、質を網羅的にはほとんど見ていないですね。ちゃんと目利きができる方の出現を望みます。

プログラミング補助用途では、GPT-4の牙城は揺るぎないといったところです。

ただOpenAIには殿様商売的な姿勢が垣間見られ、アンチが多い感じがします。昨年11月、GPT-4 Turbo(gpt-4-1106-preview)のリリースについてはメールで案内がありましたが、今年1月のgpt-4-0125-previewリリースでは一切ありませんでした。この点については、かなり不満があります。

グチは置いといて、後発のGemini、Claudeはより大量な情報を処理し要約・創出するのが得意で、GPTの方はインプット側の足りない情報を補完するつまり行間を読む能力に長けている、という私なりの結論に至りました。

Claude 3 (Sonnet)※

※バージョン番号を取得する方法も含めて聞いているのだが、こちらの意図を読み取ることができない。
コード例を示している時点で指示者が中級者であることを察するべき。指示者がTextストラクチャを使えているのにその使い方を回答するのは流石にアウトです。単なる学習データ不足か。

GPT-4 : いつもながら素晴らしい回答
Gemini vs GPT-4の記事

[Swift] 70 Geminiアプリ製作検討 Google AI SDK for Swift

[Mac M2 Pro 12CPU, Sonoma 14.3.1, Xcode 15.2]

Googleの生成AIモデル Bardの後継モデル Geminiの実力を検証しました。今回はプログラミング補助としての評価です。

結果は散々でした。GPT-4 TurboどころかGPT-3.5の足元にも及ばない、という評価です。

料金が安いので期待していたのですが、とても残念です。あのGoogleですから、それなりのものを出してくると思っていました。

2024年2月時点では、GPT(OpenAI) >>> LLama(Meta)、圏外 Gemini(Google)といったところでしょうか。Geminiは比較以前の問題かと。

OpenAIの天下は当分続きそうです。Appleは完全に周回遅れですから、業務提携か買収しか道はないのでは。

gemini-proを使ったが、回答は0点。これでは厳しい。
GPT-4 Turbo:短いプロンプトでも意図を汲み取って完璧な回答

[Swift] 69 watchOS用ColorPicker作成

[Mac M2 Pro 12CPU, Ventura 13.6.1, iOS 17.2.1, Xcode 15.2]

久々の投稿です。

watchOS用に25色ColorPickerを作成しました。

import SwiftUI

struct ColorPickerAW: View {
    @Binding var selectedColor: Color
    var onColorSelected: (() -> Void)?

    // カラーコードをColorに変換する関数
    func colorFromHex(_ hex: String) -> Color {
        var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
        hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
        
        var rgb: UInt64 = 0
        
        Scanner(string: hexSanitized).scanHexInt64(&rgb)
        
        let red = Double((rgb & 0xFF0000) >> 16) / 255.0
        let green = Double((rgb & 0x00FF00) >> 8) / 255.0
        let blue = Double(rgb & 0x0000FF) / 255.0
        
        return Color(red: red, green: green, blue: blue)
    }
    
    // カラーコードの配列
    let colors: [String] = [
        "#FFFFFF", "#C0C0C0", "#808080", "#000000", "#000080",
        "#0000FF", "#00FFFF", "#40E0D0", "#008080", "#808000",
        "#008000", "#00FF00", "#F5F5DC", "#FFFF00", "#FFD700",
        "#FFA500", "#FF7F50", "#FF0000", "#800000", "#A52A2A",
        "#DDA0DD", "#E6E6FA", "#FFC0CB", "#FF00FF", "#800080"
    ]
    
    var body: some View {
        // グリッド表示
        LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 5), spacing: 10) {
            ForEach(colors, id: \.self) { colorCode in
                Rectangle()
                    .fill(colorFromHex(colorCode))
                    .frame(width: 25, height: 25) // 長方形のサイズ
                    .onTapGesture {
                        // タップされた色を選択
                        self.selectedColor = colorFromHex(colorCode)
                        // 親ビューに通知
                        self.onColorSelected?()
                    }
            }
        }
        .padding()
    }
}
VStack(){
    Toggle("Show_Era".localized, isOn: $eraName)
        .frame(width: 140, height: 80, alignment: .center)
        .foregroundColor(.blue)
        .font(.system(size: 24))
        .onChange(of: eraName) { newValue in
            UserDefaults.standard.set(newValue, forKey: eraNameKey)
            print("eraNameが\(newValue)に変更され、UserDefaultsに保存されました。")
        }
    HStack(spacing:2){
        Text("Watch\nRow1")
        Rectangle()
            .fill(colorWatch1)
            .frame(width: 30, height: 30)
            .opacity(1.0)
        Button("Set") {
            showingColorPicker = true
        }
    }
    
    Spacer()
}
.sheet(isPresented: $showingColorPicker) {
    ColorPickerAW(selectedColor: $selectedColor) {
        showingColorPicker = false
        colorWatch1 = selectedColor
    }
}

[Swift] 68 メモアプリ製作 その20 TestFlight移行時のトラブル CoreData / CloudKit Console

[Mac M2 Pro 12CPU, Ventura 13.6.1, iOS 17.2.1, Xcode 15.0]

・現況

2週間ぶりの投稿になります。

visionOS用アプリ・リリースの予行演習として、メモアプリのApp Store登録に没頭していました。

メモアプリ界隈はレッドオーシャンですから収益云々については全く期待していません。そもそもApp StoreアプリiOS版の貧弱な検索機能では私のアプリにたどり着けないでしょう。

・問題発覚

話を戻しますが、デバッグ版がほぼ完成したところで、リリース版を検証するためTestFlightに移行しました。移行当初はSANDBOXを使ったアプリ内課金やサブスクの検証しかしていなかったのですが、しばらくしてiCloudに配置しているCoreDataが使えなくなっていることに気が付きました。

Distribution証明書やProvisioning Profileの問題かと考え、何度も作成しなおしてはXcodeでArchiveをアップロードしていたところ、1日20回のアップロード制限に引っかかってしまいました。踏んだり蹴ったりとはこのことです。

仕方ないので捨てアプリ名でApp Store Connectに登録し直し*、色々試してみたものの解決には至らず1日超でギブアップしました。

・ようやく解決

しばらく頭を冷やしてから何となく検索語”cloudkit coredata release”でGoogle検索したところ、毎度お馴染みStackOverFlowの記事がヒットしました。

CloudKit ConsoleのCloudKit DatabaseでDeploy Schema Changesボタンを押し、次の画面でDeployボタンを押すと開発環境のCloudKitをリリース版で使えるようになる、とのことでした。AppleのCloudKit関連ドキュメントに説明があります。

CloudKitの扱いは慎重に行うべきということでしょう。なお1日後に解除とされていたArchiveのアップロード制限ですが、実際は13時間後解除でした。

結局、問題発覚から解決まで1日半掛かってしまいました。

・感想

開発者・配布者証明書を発行する際に使うキーチェーンアクセスや、Provisioning Profileなどを扱う”Certificates, Identifiers & Profiles”を散々いじり倒して大分使いこなせるようになってきましたし、CloudKitやCoreDataについて理解を深めることができたのは収穫でした。

もちろんChatGPTも駆使しましたが、回答の中でCloudKit Consoleの設定について触れることは1ミリもなかったです。プロンプトに問題がなかったか、時間があれば検証します。まあ雑な質問でもこれ位は答えて欲しいところではあります。

StackOverFlow記事

※ App Store Connectに登録したアプリは削除可能ですが、Bundle IdentifierはAppleサポートに削除してもらわないと再使用できないようです。App Store Connectには安易に登録しないのが無難でしょう。

[Swift] 67 メモアプリ製作 その19 キーボード表示時のウィジェット移動対策

[Mac M2 Pro 12CPU, Ventura 13.6.1, iOS 17.1.2, Xcode 15.0]

アプリへの入力時に画面底部にあるボタンがキーボードによって上に押し上げられてしまう現象への対策です。

キーボード表示時にNotificationCenterから通知を受け取り、Bool値が変わると以下のモディファイアが動作するようにしました。

ボタン使用不可
.disabled(showingKeyboard)
ボタン非表示
.opacity(showingKeyboard ? 0.0 : 1.0)

キーボードの高さを取得してpaddingモディファイアでbottomからオフセットする方法も試しましたが、上手く出来ませんでした。

キーボードがない状態
キーボードが出現すると”バックアップ確認”などが上に押し上げられる
キーボード表示時にボタンを非表示および使用不可にした
@State private var showingKeyboard = false

Button("backupCheck".localized) { // バックアップ確認
    showingBackupView = true
}
.sheet(isPresented: $showingBackupView) {
    BackupView().environment(\.managedObjectContext, self.viewContext)
}
.position(x: geometry.size.width * 0.48, y: geometry.size.height * 0.98)
.frame(width: 128, height: 36)
.disabled(showingKeyboard)
.opacity(showingKeyboard ? 0.0 : 1.0)
.onAppear {
    // キーボードの表示・非表示の通知を購読
    NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { _ in
        showingKeyboard = true
    }
    NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { _ in
        showingKeyboard = false
    }
}
.onDisappear {
    // 通知の購読を解除
    NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}

[Swift] 66 メモアプリ製作 その18 データをエクスポート  info.plist : Application supports iTunes file sharing

[Mac M2 Pro 12CPU, Ventura 13.6.1, iOS 17.1.2, Xcode 15.0]

暗号化の有無切り替えはデータを初期化しないと出来ない仕様にしています。安易に切り替えないための措置です。暗号化解除によりセキュリティは低下しますが、パフォーマンスは向上します。

メモデータを保存したい方のためにCSVエクスポート機能を追加しました。

この機能の実装にはinfo.plistのキーを2つ追加する必要があります。これが分からなくてしばらく右往左往しました。
・Application supports iTunes file sharing : YES
・Supports opening documents in place : YES

暗号化有無の切り替えに伴うCore Data自動変換やCSVインポート機能は実装できなくはないですが、サポート範囲拡大による開発負担増大を避けるため、あえて実装しないことにしました。

iPhoneのファイルアプリで確認できる
func exportData() {
        // CSVデータのヘッダを設定
        var csvString = "Content\n"
        
        for note in contents {
            // Noteのcontentを取得し、CSV形式に整形
            if let content0 = note.content, let decryptedContent = CryptoManager.shared.decrypt(content0) {
                // 特殊文字をエスケープする処理を追加
                let escapedContent = decryptedContent.replacingOccurrences(of: "\"", with: "\"\"")
                // CSV形式の文字列に追加(コンマや改行を含むcontentはダブルクォートで囲む)
                csvString.append("\"\(escapedContent)\"\n")
            }
        }
        
        let fileManager = FileManager.default
        #if os(iOS)
        let docPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
        let fileURL = docPath.appendingPathComponent("cloudMemo.csv")
        #elseif os(macOS)
        let homeDirectory = fileManager.homeDirectoryForCurrentUser
        let fileURL = homeDirectory.appendingPathComponent("cloudMemo.csv")
        #else
        return
        #endif
        
        do {
            // CSVデータをファイルに書き込む
            try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
            print("CSVファイルのエクスポートが成功しました。ファイルパス: \(fileURL.path)")
        } catch {
            print("CSVファイルの書き込みに失敗しました: \(error)")
        }
    }

[Swift] 65 メモアプリ製作 その17 データ暗号化 クロスデバイス対応 / iCloudキーチェーン

[Mac M2 Pro 12CPU, Ventura 13.6.1, iOS 17.1.2, Xcode 15.0]

前回の記事は暗号化キーを通常のキーチェーンに保管するという内容でした。

このアプリではiPadOS版やwatchOS版とも共有するため、iCloudキーチェーンに保管するようにしました。

Xcodeのプロジェクト設定でCapabilityとしてKeychain Sharingを追加し、アプリのBundle Identifierを登録する必要があります。

コードではKeychainManagerクラスにkSecAttrSynchronizableを追加します。

class KeychainManager {
    static func save(key: String, data: Data) -> OSStatus {
        let query = [
            kSecClass as String             : kSecClassGenericPassword as String,
            kSecAttrAccount as String       : key,
            kSecValueData as String         : data,
            kSecAttrSynchronizable as String: kCFBooleanTrue! ] as [String : Any] // この行を追加

        SecItemDelete(query as CFDictionary)
        return SecItemAdd(query as CFDictionary, nil)
    }
    
    static func load(key: String) -> Data? {
        let query = [
            kSecClass as String             : kSecClassGenericPassword,
            kSecAttrAccount as String       : key,
            kSecReturnData as String        : kCFBooleanTrue!,
            kSecMatchLimit as String        : kSecMatchLimitOne,
            kSecAttrSynchronizable as String: kCFBooleanTrue! ] as [String : Any] // この行を追加

        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        if status == noErr {
            return item as? Data
        }
        return nil
    }
}